图片检索开发过程实录

package test;

import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

/**
 * 相似图片的搜索技术
 * 
 * 通过把一张图片压缩简化,把像素点经过离散余弦变换(表示为DCT( Discrete Cosine Transformation))算取均值求的图片指纹,
 * 然后通过汉明距离来判别图片是否想似的
 * 
 * 步骤: (1)缩小尺寸:pHash以小图片开始,但图片大于8*8,32*32是最好的。这样做的目的是简化了DCT的计算,而不是减小频率。
 * (2)简化色彩:将图片转化成灰度图像,进一步简化计算量。 
 * (3)计算DCT:计算图片的DCT变换,得到32*32的DCT系数矩阵。
 * (4)缩小DCT:虽然DCT的结果是32*32大小的矩阵,但我们只要保留左上角的8*8的矩阵,这部分呈现了图片中的最低频率。
 * (5)计算平均值:如同均值哈希一样,计算DCT的均值。
 * (6)计算hash值:这是最主要的一步,根据8*8的DCT矩阵,设置0或1的64位的hash值,大于等于DCT均值的设为
 * ”1”,小于DCT均值的设为“0”。组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。 名词定义pHash:加强版的哈希感知技术
 * 
 * @author 江锦泰
 *
 */
public class ImageAnalogial {
	// 缩小尺寸
	BufferedImage img = null;

	public ImageAnalogial(String imagePath) throws IOException {
		img = ImageIO.read(new File(imagePath));
	}

	public  String doAnalogial(){
		return this.doAverage(this.doDCT(this.simplify(this.doBeSmall())));
	}
	/**
	 * 
	 * 
	
	 * 采用局部均值法压缩图片
	 * 
	 */
	private BufferedImage doBeSmall() {
		int newWidth = 32;
		int newHeight = 32;
		float kx = this.img.getWidth() / newWidth;
		float ky = this.img.getHeight() / newHeight;
		int[] pix = new int[this.img.getWidth() * this.img.getHeight()
				+ this.img.getWidth()];
		img.getRGB(0, 0, this.img.getWidth(), this.img.getHeight(), pix, 0,
				this.img.getWidth());
		int[] newPix = new int[newWidth * newHeight + newWidth];

		// 遍历旧数组取得新数组
		for (int y = 0; y < newHeight; y++) {
			for (int x = 0; x < newWidth; x++) {
				int r = 0, g = 0, b = 0;
				ColorModel cm = ColorModel.getRGBdefault();
				for (int my = 0; my < (int) ky; my++) {
					for (int mx = 0; mx < (int) kx; mx++) {
						r = r
								+ cm.getRed(pix[(int) ((y * ky + my)
										* this.img.getWidth() + (x * kx + mx))]);
						g = g
								+ cm.getGreen(pix[(int) ((y * ky + my)
										* this.img.getWidth() + (x * kx + mx))]);
						b = b
								+ cm.getBlue(pix[(int) ((y * ky + my)
										* this.img.getWidth() + (x * kx + mx))]);
					}
				}
				// 算取均值
				r = (int) (r / (kx * ky));
				g = (int) (g / (kx * ky));
				b = (int) (b / (kx * ky));
				newPix[(int) (y * newWidth) + x] = 255 << 24 | r << 16 | g << 8
						| b;
				// 255<<24 | r<<16 | g<<8 | b 这个公式解释一下,颜色的RGB在内存中是
				// 以二进制的形式保存的,从右到左1-8位表示blue,9-16表示green,17-24表示red
				// 所以"<<24" "<<16" "<<8"分别表示左移24,16,8位
			}
		}
		
		BufferedImage imgOut = new BufferedImage((int) 32, (int) 32,
				this.img.getType());
		imgOut.setRGB(0, 0, 32, 32, newPix, 0, (int) 32);
		return imgOut;
	}

	/**
	 * 简化色彩,将缩小后的图片,转为64级灰度,
	 * 
	 * 
	 */
	private BufferedImage simplify(BufferedImage bufferedImage) {
		int newPix[] = new int[32 * 32 + 32];
		for (int y = 0; y < 32; y++) {
			for (int x = 0; x < 32; x++) {
				int pixPotin = bufferedImage.getRGB(x, y);

				int _red = (pixPotin >> 16) & 0xFF;
				int _green = (pixPotin >> 8) & 0xFF;
				int _blue = (pixPotin) & 0xFF;
				int newPixPoint = (int) (0.3 * _red + 0.59 * _green + 0.11 * _blue);
				newPix[y * 32 + x] = newPixPoint;
			}
		}
	
		BufferedImage imgOut = new BufferedImage((int) 32, (int) 32,
				this.img.getType());

		imgOut.setRGB(0, 0, 32, 32, newPix, 0, (int) 32);
		return imgOut;
	}

	/**
	 * 将图片进行DCT变换
	 * 
		1.获得图像的二维数据矩阵f(x,y);
		2.求离散余弦变换的系数矩阵[A];
		3.求系数矩阵对应的转置矩阵[A]T;
		4.根据公式(7)[F(u,v)]=[A][f(x,y)][A]T 计算离散余弦变换;
	 * @param bufferedImage
	 * @return
	 * @throws IOException 
	 */
	private BufferedImage doDCT(BufferedImage bufferedImage) {
		double[][] iMatrix = new double[32][32];
		int pix[] = new int[32 * 32 + 32];
		bufferedImage.getRGB(0, 0, 32, 32, pix, 0, 32);
		for (int i = 0; i < 32; i++) {
			for (int j = 0; j < 32; j++) {
				iMatrix[i][j] = (double) (pix[i * 32 + j]);
			}
		}
		double[][] quotient = Matrix.coefficient(32); // 求系数矩阵
		double[][] quotientT =Matrix.transposingMatrix(quotient, 32); // 转置系数矩阵
		double[][] temp = new double[32][32];//一个暂时的二维数组用来存储系数矩阵与原矩阵的积
		temp = Matrix.matrixMultiply(quotient, iMatrix, 32);
		iMatrix = Matrix.matrixMultiply(temp, quotientT, 32);
		
		int newpix[] = new int[8*8];//转换为像素的一维数组,这里只需要左上角的8*8矩阵
		for (int i = 0; i < 8; i++) {
			for (int j = 0; j < 8; j++) {
				newpix[i * 8 + j] = (int) iMatrix[i][j];
			}
		}
		
		BufferedImage imgOut = new BufferedImage((int)8, (int) 8,
				this.img.getType());

		imgOut.setRGB(0, 0, 8, 8, newpix, 0, (int) 8);
		return imgOut;

	}
	/**
	 * 计算均值,并构建只有0和1 的一维数组,并组成哈希值
	 * @param bufferedImage
	 * @return 取得只有0和1的一维数组
	 */
	private String doAverage(BufferedImage bufferedImage){
		int[] pix = new int[8*8];
		bufferedImage.getRGB(0, 0,8, 8, pix, 0, 8);
		int count = 0;
		for(int i = 0;i<pix.length;i++){
			count = count +pix[i];
		}
		int avgPixel = count/pix.length;
		for (int i = 0; i < pix.length; i++) {  
		    if(pix[i] >= avgPixel) {  
		    	pix[i]= 1;  
		    }else {  
		    	pix[i]= 0;  
		    }  
		}  
		StringBuffer sb = new StringBuffer();
		for(int i:pix){
			sb.append(i);
		}
		return Binary2Hex.binaryString2hexString(sb.toString());
	}
}

</pre><pre>


注:因为上面在编写程序的过程中有时候要测试生成图像,所以这里就用了ImageBuffered 作为数据传输工具

下面是两个开发的工具类

1.矩阵工具类

package test;

public class Matrix {
	//矩阵相乘
	public static double[][] matrixMultiply(double[][] A, double[][] B,
			int n) {
		// TODO 自动生成的方法存根
		  double nMatrix[][] = new double[n][n];  
	        double t = 0.0;  
	        for(int i=0; i<n; i++) {  
	            for(int j=0; j<n; j++) {  
	                t = 0;  
	                for(int k=0; k<n; k++) {  
	                    t += A[i][k]*B[k][j];  
	                }  
	                nMatrix[i][j] = t;          }  
	        }  
	        return nMatrix;  

	}

	// 转置系数矩阵
	public static double[][] transposingMatrix(double[][] matrix, int n) {
		// TODO 自动生成的方法存根
		   double nMatrix[][] = new double[n][n];  
	        for(int i=0; i<n; i++) {  
	            for(int j=0; j<n; j++) {  
	                nMatrix[i][j] = matrix[j][i];  
	            }  
	        }  
	        return nMatrix;  

	}

	// 求系数矩阵
	public static double[][] coefficient(int n) {
		// TODO 自动生成的方法存根
		double[][] coeff = new double[n][n];  
        double sqrt = 1.0/Math.sqrt(n);  
        for(int i=0; i<n; i++) {  
            coeff[0][i] = sqrt;  
        }  
        for(int i=1; i<n; i++) {  
            for(int j=0; j<n; j++) {  
                coeff[i][j] = Math.sqrt(2.0/n) * Math.cos(i*Math.PI*(j+0.5)/(double)n);  
            }  
        }  
        return coeff;  
	}
}
2.哈希转换类

package test;

public class Binary2Hex {
	 public static String binaryString2hexString(String bString)  
	    {  
	        if (bString == null || bString.equals("") || bString.length() % 8 != 0)  
	            return null;  
	        StringBuffer tmp = new StringBuffer();  
	        int iTmp = 0;  
	        for (int i = 0; i < bString.length(); i += 4)  
	        {  
	            iTmp = 0;  
	            for (int j = 0; j < 4; j++)  
	            {  
	                iTmp += Integer.parseInt(bString.substring(i + j, i + j + 1)) << (4 - j - 1);  
	            }  
	            tmp.append(Integer.toHexString(iTmp));  
	        }  
	        return tmp.toString();  
	    }  
		public static String hexString2binaryString(String hexString)  
	    {  
	        if (hexString == null || hexString.length() % 2 != 0)  
	            return null;  
	        String bString = "", tmp;  
	        for (int i = 0; i < hexString.length(); i++)  
	        {  
	            tmp = "0000"  
	                    + Integer.toBinaryString(Integer.parseInt(hexString  
	                            .substring(i, i + 1), 16));  
	            bString += tmp.substring(tmp.length() - 4);  
	        }  
	        return bString;  
	    }  
		
}
开发好了之后,进行测试

我在网上用百度搜图拿了16张图片:

大家可以依稀看的到图片的内容,写了一个测试方法

@Test
	public void testAnalogial() throws IOException{
		ImageAnalogial my1= new ImageAnalogial("G:\\1.jpg");
		ImageAnalogial my2 = new ImageAnalogial("G:\\2.jpg");
		ImageAnalogial my3 = new ImageAnalogial("G:\\3.jpg");
		ImageAnalogial my4 = new ImageAnalogial("G:\\4.jpg");
		ImageAnalogial my5 = new ImageAnalogial("G:\\5.jpg");
		ImageAnalogial my6 = new ImageAnalogial("G:\\6.jpg");
		ImageAnalogial my7 = new ImageAnalogial("G:\\7.jpg");
		ImageAnalogial my8 = new ImageAnalogial("G:\\8.jpg");
		ImageAnalogial my9 = new ImageAnalogial("G:\\9.jpg");
		ImageAnalogial my10 = new ImageAnalogial("G:\\10.jpg");
		ImageAnalogial my11 = new ImageAnalogial("G:\\11.jpg");
		ImageAnalogial my12 = new ImageAnalogial("G:\\12.jpg");
		ImageAnalogial my13 = new ImageAnalogial("G:\\13.jpg");
		ImageAnalogial my14 = new ImageAnalogial("G:\\14.jpg");
		ImageAnalogial my15 = new ImageAnalogial("G:\\15.jpg");
		ImageAnalogial my16 = new ImageAnalogial("G:\\16.jpg");
		List<String> list=new ArrayList<String>();
		String s1=my1.doAnalogial();
		String s2=my2.doAnalogial();
		String s3=my3.doAnalogial();
		String s4=my4.doAnalogial();
		String s5=my5.doAnalogial();
		String s6=my6.doAnalogial();
		String s7=my7.doAnalogial();
		String s8=my8.doAnalogial();
		String s9=my9.doAnalogial();
		String s10=my10.doAnalogial();
		String s11=my11.doAnalogial();
		String s12=my12.doAnalogial();
		String s13=my13.doAnalogial();
		String s14=my14.doAnalogial();
		String s15=my15.doAnalogial();
		String s16=my16.doAnalogial();
		list.add(s1);
		list.add(s2);
		list.add(s3);
		list.add(s4);
		list.add(s5);
		list.add(s6);
		list.add(s7);
		list.add(s8);
		list.add(s9);
		list.add(s10);
		list.add(s11);
		list.add(s12);
		list.add(s13);
		list.add(s14);
		list.add(s15);
		list.add(s16);
		 long starTime=System.currentTimeMillis();
		 //拿去哈希值转为二进制值
		for(int i=0;i<list.size();i++){
			list.set(i, Binary2Hex.hexString2binaryString(list.get(i)));
		}
		Map<Integer,Integer> map = new HashMap<Integer,Integer>();//key为图片的标记
		//拿去每一个二进制值并比对,记录下不同的位数
		for(int i=0;i<list.size();i++){
			int count =0;
			for(int x=0;x<list.get(i).length();x++){
				if(list.get(14).charAt(x)!=list.get(i).charAt(x)){//在这里进行比较,用15.jpg与全部图片进行比较
					count=count+1;
				}
			}
	
			map.put(i, count);
		}
		Map newMap = sortMap(map);
		//升序排练,与原图片相似度高的图片排列越前
		Set<Entry<Integer, Integer>> es = newMap.entrySet();
		for(Entry e :es){
			System.out.println("key:"+((int)e.getKey()+1)+"  value:"+e.getValue());
		}
	    long endTime=System.currentTimeMillis();
		long Time=endTime-starTime;
		  System.out.println("消耗时间:"+Time+"毫秒");

	}
	//Map排序方法
	 public static Map sortMap(Map oldMap) {  
	        ArrayList<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(oldMap.entrySet());  
	        Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {  
	  
	            @Override  
	            public int compare(Entry<java.lang.String, Integer> arg0,  
	                    Entry<java.lang.String, Integer> arg1) {  
	                return arg0.getValue() - arg1.getValue();  
	            }  
	        });  
	        Map newMap = new LinkedHashMap();  
	        for (int i = 0; i < list.size(); i++) {  
	            newMap.put(list.get(i).getKey(), list.get(i).getValue());  
	        }  
	        return newMap;  
	    }  
下面是运行的结果:

从图中可以看出对于15.jpg的搜索是很精确的。

在用我用11.jpg去百度图片搜索,拿到了12.jpg,13.jpg图片,那我们用11.jpg进行测试

中间有一个草泥马乱入了,这个算法本来就只是支持原图片放大缩小,模糊对原图片的匹配的,不过对相似度的排序,就可以对站内图片的图片检索用了,对于百度那一些,自行百度sift算法0.0,等完善好再开源一个jar包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值