/* * This file is part of the LIRE project: http://lire-project.net * LIRE is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * LIRE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with LIRE; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * We kindly ask you to refer the any or one of the following publications in * any publication mentioning or employing Lire: * * Lux Mathias, Savvas A. Chatzichristofis. Lire: Lucene Image Retrieval – * An Extensible Java CBIR Library. In proceedings of the 16th ACM International * Conference on Multimedia, pp. 1085-1088, Vancouver, Canada, 2008 * URL: http://doi.acm.org/10.1145/1459359.1459577 * * Lux Mathias. Content Based Image Retrieval with LIRE. In proceedings of the * 19th ACM International Conference on Multimedia, pp. 735-738, Scottsdale, * Arizona, USA, 2011 * URL: http://dl.acm.org/citation.cfm?id=2072432 * * Mathias Lux, Oge Marques. Visual Information Retrieval using Java and LIRE * Morgan & Claypool, 2013 * URL: http://www.morganclaypool.com/doi/abs/10.2200/S00468ED1V01Y201301ICR025 * */ package net.semanticmetadata.lire.imageanalysis.features.global; import net.semanticmetadata.lire.builders.DocumentBuilder; import net.semanticmetadata.lire.imageanalysis.features.GlobalFeature; import net.semanticmetadata.lire.imageanalysis.features.LireFeature; import net.semanticmetadata.lire.imageanalysis.features.global.accid.PMasks; import net.semanticmetadata.lire.imageanalysis.features.global.cedd.Fuzzy10Bin; import net.semanticmetadata.lire.imageanalysis.features.global.cedd.Fuzzy24Bin; import net.semanticmetadata.lire.imageanalysis.features.global.cedd.RGB2HSV; import net.semanticmetadata.lire.utils.ImageUtils; import net.semanticmetadata.lire.utils.MetricsUtils; import net.semanticmetadata.lire.utils.SerializationUtils; import java.awt.image.BufferedImage; import java.util.Arrays; /** * ACCID global feature as designed by Chrysanthi Iakovidou and implemented by Nektarios Anagnostopoulos. Main idea * is to find the most important edges on different scales and store them in a histogram along with a fuzzy color * scheme taken from the CEDD descriptor. * * @author Chrysanthi Iakovidou * @author Nektarios Anagnostopoulos * @author Mathias Lux */ public class ACCID implements GlobalFeature { double[] feature = new double[120]; private boolean doQuantize = true; @Override public void extract(BufferedImage image) { int width = 600; int height = 600; image = ImageUtils.scaleImage(image, width, height); image = ImageUtils.get8BitRGBImage(image); PMasks myPMasks = new PMasks(); double[][] smapF = createSmap(image, myPMasks.getPmasks(), myPMasks.getMaskWhite()); double[][] ThresSmall = filterF(smapF); double ThresImg = 0; double[] ThresBig = new double[6]; double[] CVBigArea = new double[6]; Arrays.fill(ThresBig, 0d); for (int ii = 0; ii < ThresSmall.length; ii++) { for (int jj = 0; jj < ThresSmall[ii].length; jj++) { ThresSmall[ii][jj] /= 100.0; ThresBig[ii] += ThresSmall[ii][jj]; } ThresBig[ii] /= 6; ThresImg += ThresBig[ii]; } ThresImg /= 6; double num; // double standDevImg = 0; for (int ii = 0; ii < CVBigArea.length; ii++) { num = 0; for (int jj = 0; jj < ThresSmall[ii].length; jj++) { num += (ThresSmall[ii][jj] - ThresBig[ii]) * (ThresSmall[ii][jj] - ThresBig[ii]); } num = Math.sqrt(num / (ThresSmall[ii].length - 1)); if (ThresBig[ii] > 0) CVBigArea[ii] = num / ThresBig[ii]; else CVBigArea[ii] = 0; // standDevImg += (ThresBig[ii] - ThresImg) * (ThresBig[ii] - ThresImg); } // standDevImg = Math.sqrt(standDevImg / (ThresBig.length - 1)); double meanSmapF = 0; for (int i = 0; i < smapF.length; i++) { meanSmapF += smapF[i][1]; } meanSmapF /= smapF.length; double standDevSmapF = 0; for (int i = 0; i < smapF.length; i++) { standDevSmapF += (smapF[i][1] - meanSmapF) * (smapF[i][1] - meanSmapF); } standDevSmapF = Math.sqrt(standDevSmapF / (smapF.length - 1)); double CVImg = 0; if (meanSmapF > 0) CVImg = standDevSmapF / meanSmapF; BufferedImage bimg = ImageUtils.scaleImage(image, (int) (0.5 * image.getWidth()), (int) (0.5 * image.getHeight())); double[][] smapM = createSmap(bimg, myPMasks.getPmasks(), myPMasks.getMaskWhite()); double[] ThresBigm = filterM(smapM); // double ThresImgm = 0; // for (int i = 0; i < ThresBigm.length; i++) { // ThresImgm += ThresBigm[i]; // } // ThresImgm /= ThresBigm.length; double ThresImgm = 0; for (int i = 0; i < smapM.length; i++) { ThresImgm += smapM[i][1]; } ThresImgm /= smapM.length; // double CVImgB = 0; // for (int i = 0; i < ThresBigm.length; i++) { // CVImgB += (ThresBigm[i] - ThresImgm) * (ThresBigm[i] - ThresImgm); // } // CVImgB = Math.sqrt(CVImgB / (ThresBigm.length - 1))/ThresImgm; double CVImgB = 0; for (int i = 0; i < smapM.length; i++) { CVImgB += (smapM[i][1] - ThresImgm) * (smapM[i][1] - ThresImgm); } if (ThresImgm > 0) CVImgB = Math.sqrt(CVImgB / (smapM.length - 1)) / ThresImgm; else CVImgB = 0; BufferedImage bimg2 = ImageUtils.scaleImage(bimg, (int) (0.5 * bimg.getWidth()), (int) (0.5 * bimg.getHeight())); double[][] smapS = createSmap(bimg2, myPMasks.getPmasks(), myPMasks.getMaskWhite()); double CVImgS = filteringMethodS(smapS); filteringMethodF(smapF, ThresSmall, ThresBig, ThresImg, CVImg, CVImgB, CVImgS); filteringMethodM(smapM, ThresBigm, ThresImgm, CVImg, CVImgB, CVImgS); double[][] smap = ScaleFiltering(smapF, smapM, smapS); feature = ComputeDesc(image, smap); } private double[][] createSmap(BufferedImage img, int[][][] pmasks, int[] whiteMasks) { int width = img.getWidth(); int height = img.getHeight(); double[][] smap = new double[(height / 10) * (width / 10)][2]; int pixel, i, j, winMask, counter = 0; double w, maxR, TempW, TempB; int[][] myTile = new int[10][10]; for (int a = 0; a < height; a += 10) { for (int b = 0; b < width; b += 10) { i = 0; for (int x = a; x < a + 10; x++) { j = 0; for (int y = b; y < b + 10; y++) { pixel = img.getRGB(y, x); myTile[j][i] = (int) Math.round(0.299d * ((pixel >> 16) & 0xff) + 0.5870d * ((pixel >> 8) & 0xff) + 0.1140d * ((pixel) & 0xff)); j++; } i++; } maxR = 0; winMask = 58; for (int k = 0; k < 58; k++) { TempW = 0; TempB = 0; for (int x = 0; x < 10; x++) { for (int y = 0; y < 10; y++) { if (pmasks[k][x][y] > 0) TempW += myTile[x][y]; else TempB += myTile[x][y]; } } TempW /= whiteMasks[k]; TempB /= (100 - whiteMasks[k]); w = ((Math.abs(TempW - TempB) * 100) / 255.0); if (w > maxR) { maxR = w; winMask = k; } } smap[counter][0] = winMask; smap[counter][1] = maxR; counter++; } } return smap; } private double[][] filterF(double[][] smapF) { int blocks = smapF.length; int blocks6 = blocks / 6; int step = (int) Math.sqrt(blocks); int step6 = step / 6; int z, start, end; double[][] smallAreas = new double[6][6]; for (int a = 0; a < 6; a++) { start = a * blocks6; end = start + blocks6; for (int x = start; x < end; x += step) { for (int y = 0; y < step6; y++) { z = x + y; if (a < 3) { smallAreas[0][a * 2] += smapF[z][1]; smallAreas[0][a * 2 + 1] += smapF[z + step6][1]; smallAreas[1][a * 2] += smapF[z + 2 * step6][1]; smallAreas[1][a * 2 + 1] += smapF[z + 3 * step6][1]; smallAreas[2][a * 2] += smapF[z + 4 * step6][1]; smallAreas[2][a * 2 + 1] += smapF[z + 5 * step6][1]; } else { smallAreas[3][(a - 3) * 2] += smapF[z][1]; smallAreas[3][(a - 3) * 2 + 1] += smapF[z + step6][1]; smallAreas[4][(a - 3) * 2] += smapF[z + 2 * step6][1]; smallAreas[4][(a - 3) * 2 + 1] += smapF[z + 3 * step6][1]; smallAreas[5][(a - 3) * 2] += smapF[z + 4 * step6][1]; smallAreas[5][(a - 3) * 2 + 1] += smapF[z + 5 * step6][1]; } } } } return smallAreas; } private double[] filterM(double[][] smapM) { int z, Blocks = smapM.length; int Blocks2 = Blocks / 2; int Blocks4 = Blocks / 4; int step = (int) Math.sqrt(Blocks); int step2 = step / 2; double[] AreasQuarters = new double[4]; Arrays.fill(AreasQuarters, 0d); for (int x = 0; x < Blocks2; x += step) { for (int y = 0; y < step2; y++) { z = y + x; AreasQuarters[0] += smapM[z][1]; AreasQuarters[1] += smapM[z + step2][1]; AreasQuarters[2] += smapM[z + Blocks2][1]; AreasQuarters[3] += smapM[z + step2 + Blocks2][1]; } } AreasQuarters[0] /= Blocks4; AreasQuarters[1] /= Blocks4; AreasQuarters[2] /= Blocks4; AreasQuarters[3] /= Blocks4; return AreasQuarters; } private void filteringMethodF(double[][] smap, double[][] ThresSmall, double[] ThresBig, double ThresImg, double CVImg, double CVImgB, double CVImgS) { int blocks = smap.length; int blocks6 = blocks / 6; int step = (int) Math.sqrt(blocks); int step6 = step / 6; if (CVImg < 1) { ThresImg = ThresImg * (1 + ((CVImg + CVImgB + CVImgS) / 3)); } else { ThresImg = ThresImg * CVImg; } for (int x = 0; x < 6; x++) { if (ThresImg >= ThresBig[x]) { ThresBig[x] = ThresBig[x] * (1 + (1 - (ThresBig[x] / ThresImg))); } else { ThresBig[x] = ThresImg * (1 + (1 - (ThresImg / ThresBig[x]))); } for (int y = 0; y < 6; y++) { if (ThresSmall[x][y] < ThresBig[x]) { if (ThresBig[x] < 5) { ThresSmall[x][y] = ThresBig[x] + 1; } else { ThresSmall[x][y] = ThresBig[x]; } } } } int start, end, k, l; for (int a = 0; a < 6; a++) { start = a * blocks6; end = start + blocks6; for (int x = start; x < end; x += 60) { for (int y = 0; y < step6; y++) { k = x + y; if (a < 3) { l = a * 2; if (smap[k][1] < ThresSmall[0][l]) smap[k][0] = 58; if (smap[k + step6][1] < ThresSmall[0][l + 1]) smap[k + step6][0] = 58; if (smap[k + 2 * step6][1] < ThresSmall[1][l]) smap[k + 2 * step6][0] = 58; if (smap[k + 3 * step6][1] < ThresSmall[1][l + 1]) smap[k + 3 * step6][0] = 58; if (smap[k + 4 * step6][1] < ThresSmall[2][l]) smap[k + 4 * step6][0] = 58; if (smap[k + 5 * step6][1] < ThresSmall[2][l + 1]) smap[k + 5 * step6][0] = 58; } else { l = (a - 3) * 2; if (smap[k][1] < ThresSmall[3][l]) smap[k][0] = 58; if (smap[k + step6][1] < ThresSmall[3][l + 1]) smap[k + step6][0] = 58; if (smap[k + 2 * step6][1] < ThresSmall[4][l]) smap[k + 2 * step6][0] = 58; if (smap[k + 3 * step6][1] < ThresSmall[4][l + 1]) smap[k + 3 * step6][0] = 58; if (smap[k + 4 * step6][1] < ThresSmall[5][l]) smap[k + 4 * step6][0] = 58; if (smap[k + 5 * step6][1] < ThresSmall[5][l + 1]) smap[k + 5 * step6][0] = 58; } } } } } private void filteringMethodM(double[][] smap, double[] ThressBigm, double ThressImgm, double CVImg, double CVImgB, double CVImgS) { int Blocks = smap.length; int Blocks2 = Blocks / 2; int step = (int) Math.sqrt(Blocks); int step2 = step / 2; if (CVImgB < 1) { ThressImgm = ThressImgm * (1 + ((CVImg + CVImgB + CVImgS) / 3)); } else { ThressImgm = ThressImgm * CVImgB; } for (int x = 0; x < 4; x++) { if (ThressImgm >= ThressBigm[x]) { ThressBigm[x] = ThressBigm[x] * (1 + (1 - (ThressBigm[x] / ThressImgm))); } else { ThressBigm[x] = ThressImgm * (1 + (1 - (ThressImgm / ThressBigm[x]))); } } int z; for (int x = 0; x < Blocks2; x += step) { for (int y = 0; y < step2; y++) { z = x + y; if (smap[z][1] < ThressBigm[0]) smap[z][0] = 58; if (smap[z + step2][1] < ThressBigm[1]) smap[z + step2][0] = 58; if (smap[z + Blocks2][1] < ThressBigm[2]) smap[z + Blocks2][0] = 58; if (smap[z + Blocks2 + step2][1] < ThressBigm[3]) smap[z + Blocks2 + step2][0] = 58; } } } private double filteringMethodS(double[][] smap) { double meanImgS = 0; for (int i = 0; i < smap.length; i++) { meanImgS += smap[i][1]; } meanImgS /= smap.length; // meanImgS = meanImgS -(meanImgS / 10); double CVImgS = 0; for (int i = 0; i < smap.length; i++) { CVImgS += (smap[i][1] - meanImgS) * (smap[i][1] - meanImgS); } CVImgS = Math.sqrt(CVImgS / (smap.length - 1)) / meanImgS; meanImgS = meanImgS * 0.9; for (int x = 0; x < smap.length; x++) { if (smap[x][1] < meanImgS) { smap[x][0] = 58; } } return CVImgS; } private double[][] ScaleFiltering(double[][] smapF, double[][] smapM, double[][] smapS) { double[][] smapFsm = new double[smapF.length][smapF[0].length]; double[][] smapFm = new double[smapF.length][smapF[0].length]; double[][] smapUn = new double[smapF.length][smapF[0].length]; for (int i = 0; i < smapF.length; i++) { smapFsm[i][0] = smapF[i][0]; smapFsm[i][1] = smapF[i][1]; smapFm[i][0] = smapF[i][0]; smapFm[i][1] = smapF[i][1]; smapUn[i][0] = smapF[i][0]; smapUn[i][1] = smapF[i][1]; } int z, f; for (int b = 0; b < 15; b++) { for (int a = 0; a < 60; a++) { z = a + b * 4 * 60; f = (int) Math.floor(a / 4) + 15 * b; if (smapS[f][0] == 58) { smapF[z][0] = 58; smapF[z + 60][0] = 58; smapF[z + 2 * 60][0] = 58; smapF[z + 3 * 60][0] = 58; } } } for (int b = 0; b < 30; b++) { for (int a = 0; a < 60; a++) { z = a + b * 2 * 60; f = (int) Math.floor(a / 2) + 30 * b; if (smapM[f][0] == 58) { smapFm[z][0] = 58; smapFm[z + 60][0] = 58; } } } for (int w = 0; w < smapF.length; w++) { if ((smapFm[w][0] == 58) && (smapF[w][0] == 58) && (smapUn[w][0] != 58)) smapFsm[w][0] = 58; if (smapFsm[w][1] == 0) smapFsm[w][1] = 30; } return smapFsm; } private double[] ComputeDesc(BufferedImage image, double[][] smap) { double[] desc = new double[120]; int j, num; Fuzzy10Bin Fuzzy10 = new Fuzzy10Bin(false); Fuzzy24Bin Fuzzy24 = new Fuzzy24Bin(false); RGB2HSV HSVConverter = new RGB2HSV(); int[] HSV; double[] Fuzzy10BinResultTable; double[] Fuzzy24BinResultTable; int width = image.getWidth(); int height = image.getHeight(); int pixel, R, G, B, counter = 0; for (int a = 0; a < height; a += 10) { for (int b = 0; b < width; b += 10) { R = 0; G = 0; B = 0; for (int xx = a; xx < a + 10; xx++) { for (int yy = b; yy < b + 10; yy++) { pixel = image.getRGB(yy, xx); R += (pixel >> 16) & 0xff; G += (pixel >> 8) & 0xff; B += (pixel) & 0xff; } } HSV = HSVConverter.ApplyFilter(R / 100, G / 100, B / 100); Fuzzy10BinResultTable = Fuzzy10.ApplyFilter(HSV[0], HSV[1], HSV[2], 2); Fuzzy24BinResultTable = Fuzzy24.ApplyFilter(HSV[0], HSV[1], HSV[2], Fuzzy10BinResultTable, 2); num = (int) smap[counter][0]; if ((num >= 0) && (num <= 4)) j = 0; else if ((num >= 5) && (num <= 8)) j = 0; else if ((num >= 9) && (num <= 13)) j = 1; else if ((num >= 14) && (num <= 19)) j = 1; else if ((num >= 20) && (num <= 24)) j = 1; else if ((num >= 25) && (num <= 28)) j = 2; else if ((num >= 29) && (num <= 33)) j = 2; else if ((num >= 34) && (num <= 37)) j = 2; else if ((num >= 38) && (num <= 42)) j = 3; else if ((num >= 43) && (num <= 48)) j = 3; else if ((num >= 49) && (num <= 53)) j = 3; else if ((num >= 54) && (num <= 57)) j = 0; else j = 4; for (int ii = 0; ii < 24; ii++) { desc[j * Fuzzy24BinResultTable.length + ii] += Fuzzy24BinResultTable[ii] * (smap[counter][1] / 100); } counter++; } } if (doQuantize) desc = quantizeFeature(desc); return desc; } private double[] quantizeFeature(double[] desc) { desc = MetricsUtils.normalizeMax(desc); for (int i = 0; i < desc.length; i++) { desc[i] = Math.floor(desc[i] * (double) Short.MAX_VALUE); } return desc; } @Override public byte[] getByteArrayRepresentation() { byte[] result = new byte[feature.length * 2]; for (int i = 0; i < feature.length; i += 1) { // convert short to byte ... result[2 * i] = (byte) ((((short) feature[i]) >> 8) & 0xff); result[2 * i + 1] = (byte) (((short) feature[i]) & 0xff); } return result; } @Override public void setByteArrayRepresentation(byte[] in) { setByteArrayRepresentation(in, 0, in.length); } @Override public void setByteArrayRepresentation(byte[] in, int offset, int length) { for (int i = 0; i < feature.length; i++) { feature[i] = (in[2 * i + offset] << 8) | in[2 * i + 1 + offset]&0xff; } } @Override public double[] getFeatureVector() { return feature; } @Override public double getDistance(LireFeature f) { if (!(f instanceof ACCID)) throw new UnsupportedOperationException("Wrong descriptor."); return MetricsUtils.jsd(feature, ((ACCID) f).feature); } @Override public String getFeatureName() { return "ACCID"; } @Override public String getFieldName() { return DocumentBuilder.FIELD_NAME_ACCID; } }