LIRe图像检索:Tamura纹理特征算法源码分析

时间:2022-10-23 09:20:24

1 Tamura概述

Tamura纹理特征包括了粗糙度(coarseness)、对比度(contrast)、方向度(directionality)、线性度(linelikeness)、规则度(regularity)、粗略度(roughness)。最原始的Tamura论文有《Textural Features Correspondingto Visual Perception》。Tamura纹理特征要比灰度共生矩阵得到的纹理特征更直观,在视觉效果上更有优势。LIRe实现了Tamura纹理特征,包括粗糙度、对比度和方向。

2 源码分析

在lire.jar中Tamura源码的位置如下:

LIRe图像检索:Tamura纹理特征算法源码分析

以下为我对源码的分析和解读。

public class Tamura implements GlobalFeature {
private static final int MAX_IMG_HEIGHT = 64;
private int[][] grayScales;
private int imgWidth;
private int imgHeight;
private double[] histogram;
private static final double[][] filterH = new double[][]{{-1.0D, 0.0D, 1.0D}, {-1.0D, 0.0D, 1.0D}, {-1.0D, 0.0D, 1.0D}};
private static final double[][] filterV = new double[][]{{-1.0D, -1.0D, -1.0D}, {0.0D, 0.0D, 0.0D}, {1.0D, 1.0D, 1.0D}};
private static final String TAMURA_NAME = "tamura";

public Tamura() {
}

//第一个指标coarseness 粗糙度
public double coarseness(int n0, int n1) {
double result = 0.0D;

for(int i = 1; i < n0 - 1; ++i) {
for(int j = 1; j < n1 - 1; ++j) {
result += Math.pow(2.0D, (double)this.sizeLeadDiffValue(i, j));
}
}

result = 1.0D / (double)(n0 * n1) * result;
return result;
}

public double averageOverNeighborhoods(int x, int y, int k) {
double result = 0.0D;
double border = Math.pow(2.0D, (double)(2 * k));
boolean x0 = false;
boolean y0 = false;

for(int i = 0; (double)i < border; ++i) {
for(int j = 0; (double)j < border; ++j) {
int var12 = x - (int)Math.pow(2.0D, (double)(k - 1)) + i;
int var13 = y - (int)Math.pow(2.0D, (double)(k - 1)) + j;
if(var12 < 0) {
var12 = 0;
}

if(var13 < 0) {
var13 = 0;
}

if(var12 >= this.imgWidth) {
var12 = this.imgWidth - 1;
}

if(var13 >= this.imgHeight) {
var13 = this.imgHeight - 1;
}

result += (double)this.grayScales[var12][var13];
}
}

result = 1.0D / Math.pow(2.0D, (double)(2 * k)) * result;
return result;
}

public double differencesBetweenNeighborhoodsHorizontal(int x, int y, int k) {
double result = 0.0D;
result = Math.abs(this.averageOverNeighborhoods(x + (int)Math.pow(2.0D, (double)(k - 1)), y, k) - this.averageOverNeighborhoods(x - (int)Math.pow(2.0D, (double)(k - 1)), y, k));
return result;
}

public double differencesBetweenNeighborhoodsVertical(int x, int y, int k) {
double result = 0.0D;
result = Math.abs(this.averageOverNeighborhoods(x, y + (int)Math.pow(2.0D, (double)(k - 1)), k) - this.averageOverNeighborhoods(x, y - (int)Math.pow(2.0D, (double)(k - 1)), k));
return result;
}

public int sizeLeadDiffValue(int x, int y) {
double result = 0.0D;
int maxK = 1;

for(int k = 0; k < 3; ++k) {
double tmp = Math.max(this.differencesBetweenNeighborhoodsHorizontal(x, y, k), this.differencesBetweenNeighborhoodsVertical(x, y, k));
if(result < tmp) {
maxK = k;
result = tmp;
}
}

return maxK;
}

//第二个指标Contrast,对比度
public double contrast() {
double result = 0.0D;
double my4 = 0.0D;
double alpha4 = 0.0D;
double my = this.calculateMy();
double sigma = this.calculateSigma(my);
if(sigma <= 0.0D) {
return 0.0D;
} else {
for(int x = 0; x < this.imgWidth; ++x) {
for(int y = 0; y < this.imgHeight; ++y) {
my4 += Math.pow((double)this.grayScales[x][y] - my, 4.0D);
}
}

alpha4 = my4 / Math.pow(sigma, 4.0D);
result = sigma / Math.pow(alpha4, 0.25D);
return result;
}
}

public double calculateMy() {
double mean = 0.0D;

for(int x = 0; x < this.imgWidth; ++x) {
for(int y = 0; y < this.imgHeight; ++y) {
mean += (double)this.grayScales[x][y];
}
}

mean /= (double)(this.imgWidth * this.imgHeight);
return mean;
}

public double calculateSigma(double mean) {
double result = 0.0D;

for(int x = 0; x < this.imgWidth; ++x) {
for(int y = 0; y < this.imgHeight; ++y) {
result += Math.pow((double)this.grayScales[x][y] - mean, 2.0D);
}
}

result /= (double)(this.imgWidth * this.imgHeight);
return Math.sqrt(result);
}

//第三个指标 Directionality,方向度
public double[] directionality() {
double[] histogram = new double[16];
double maxResult = 3.0D;
double binWindow = maxResult / (double)(histogram.length - 1);
boolean bin = true;

for(int x = 1; x < this.imgWidth - 1; ++x) {
for(int y = 1; y < this.imgHeight - 1; ++y) {
int var9 = (int)((1.5707963267948966D + Math.atan(this.calculateDeltaV(x, y) / this.calculateDeltaH(x, y))) / binWindow);
++histogram[var9];
}
}

return histogram;
}

public double calculateDeltaH(int x, int y) {
double result = 0.0D;

for(int i = 0; i < 3; ++i) {
for(int j = 0; j < 3; ++j) {
result += (double)this.grayScales[x - 1 + i][y - 1 + j] * filterH[i][j];
}
}

return result;
}

public double calculateDeltaV(int x, int y) {
double result = 0.0D;

for(int i = 0; i < 3; ++i) {
for(int j = 0; j < 3; ++j) {
result += (double)this.grayScales[x - 1 + i][y - 1 + j] * filterV[i][j];
}
}

return result;
}

public double getDistance(double[] targetFeature, double[] queryFeature) {
double result = 0.0D;

for(int i = 2; i < targetFeature.length; ++i) {
result += Math.pow(targetFeature[i] - queryFeature[i], 2.0D);
}

return result;
}

public void extract(BufferedImage image) {
this.histogram = new double[18];
ColorConvertOp op = new ColorConvertOp(image.getColorModel().getColorSpace(), ColorSpace.getInstance(1003), new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY));
BufferedImage bimg = op.filter(image, (BufferedImage)null);
bimg = ImageUtils.scaleImage(bimg, 64);
WritableRaster raster = bimg.getRaster();
int[] tmp = new int[3];
this.grayScales = new int[raster.getWidth()][raster.getHeight()];

int i;
for(i = 0; i < raster.getWidth(); ++i) {
for(int j = 0; j < raster.getHeight(); ++j) {
raster.getPixel(i, j, tmp);
this.grayScales[i][j] = tmp[0];
}
}

this.imgWidth = bimg.getWidth();
this.imgHeight = bimg.getHeight();
//第一个指标 Coarseness,粗糙度
this.histogram[0] = this.coarseness(bimg.getWidth(), bimg.getHeight());
//第二个指标 Contrast,对比度
this.histogram[1] = this.contrast();
//第三个指标 Directionality,方向度
double[] directionality = this.directionality();

for(i = 2; i < this.histogram.length; ++i) {
this.histogram[i] = directionality[i - 2];
}

}

public byte[] getByteArrayRepresentation() {
return SerializationUtils.toByteArray(this.histogram);
}

public void setByteArrayRepresentation(byte[] in) {
this.histogram = SerializationUtils.toDoubleArray(in);
}

public void setByteArrayRepresentation(byte[] in, int offset, int length) {
this.histogram = SerializationUtils.toDoubleArray(in, offset, length);
}

public double[] getFeatureVector() {
return this.histogram;
}

public double getDistance(LireFeature feature) {
if(!(feature instanceof Tamura)) {
throw new UnsupportedOperationException("Wrong descriptor.");
} else {
Tamura tamura = (Tamura)feature;
return this.getDistance(tamura.histogram, this.histogram);
}
}

public String getFeatureName() {
return "Tamura Features";
}

public String getFieldName() {
return "TAMURA";
}
}
Reference:

http://blog.sina.com.cn/s/blog_5ae7a1de01012r03.html

http://blog.csdn.net/jzwong/article/details/51584535