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包