java:均值哈希实现图像内容相似度比较

这阵子发现我的图像数据库中有不少内容一样的图像需要剔除,这些内容一样的图像可能尺寸不一样,通道数也可能不一样(灰度/彩色),如下三张图内容完全一样,只是亮度或色彩通道数不同,
这里写图片描述这里写图片描述这里写图片描述
于是想到了用google或baidu的识图功能所用到的“感知哈希算法”来搜索数据库内容一样的图像。
通过这篇文章搞清楚了“感知哈希算法”的基本原理,
《三种基于感知哈希算法的相似图像检索技术》,发现原理很简单,很适合我等粗人,呵呵,于是在java下实现了这个算法的代码 :
#java实现

package net.gdface.image;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.util.Arrays;

/**
 * 均值哈希实现图像指纹比较
 * @author guyadong
 *
 */
public final class FingerPrint {
	/**
	 * 图像指纹的尺寸,将图像resize到指定的尺寸,来计算哈希数组 
	 */
	private static final int HASH_SIZE=16;
	/**
	 * 保存图像指纹的二值化矩阵
	 */
	private final byte[] binaryzationMatrix;
	public FingerPrint(byte[] hashValue) {
		if(hashValue.length!=HASH_SIZE*HASH_SIZE)
			throw new IllegalArgumentException(String.format("length of hashValue must be %d",HASH_SIZE*HASH_SIZE ));
		this.binaryzationMatrix=hashValue;
	}
	public FingerPrint(String hashValue) {
		this(toBytes(hashValue));
	}
	public FingerPrint (BufferedImage src){
		this(hashValue(src));
	}
	private static byte[] hashValue(BufferedImage src){
		BufferedImage hashImage = resize(src,HASH_SIZE,HASH_SIZE);
		byte[] matrixGray = (byte[]) toGray(hashImage).getData().getDataElements(0, 0, HASH_SIZE, HASH_SIZE, null);	
		return  binaryzation(matrixGray);
	}
	/**
	 * 从压缩格式指纹创建{@link FingerPrint}对象
	 * @param compactValue
	 * @return
	 */
	public static FingerPrint createFromCompact(byte[] compactValue){
		return new FingerPrint(uncompact(compactValue));
	}

	public static boolean validHashValue(byte[] hashValue){
		if(hashValue.length!=HASH_SIZE)
			return false;
		for(byte b:hashValue){
			if(0!=b&&1!=b)return false;			
		}
		return true;
	}
	public static boolean validHashValue(String hashValue){
		if(hashValue.length()!=HASH_SIZE)
			return false;
		for(int i=0;i<hashValue.length();++i){
			if('0'!=hashValue.charAt(i)&&'1'!=hashValue.charAt(i))return false;			
		}
		return true;
	}
	public byte[] compact(){
		return compact(binaryzationMatrix);
	}

	/**
	 * 指纹数据按位压缩
	 * @param hashValue
	 * @return
	 */
	private static byte[] compact(byte[] hashValue){
		byte[] result=new byte[(hashValue.length+7)>>3];
		byte b=0;
		for(int i=0;i<hashValue.length;++i){
			if(0==(i&7)){
				b=0;
			}
			if(1==hashValue[i]){
				b|=1<<(i&7);
			}else if(hashValue[i]!=0)
				throw new IllegalArgumentException("invalid hashValue,every element must be 0 or 1");
			if(7==(i&7)||i==hashValue.length-1){
				result[i>>3]=b;
			}
		}
		return result;
	}

	/**
	 * 压缩格式的指纹解压缩
	 * @param compactValue
	 * @return
	 */
	private static byte[] uncompact(byte[] compactValue){
		byte[] result=new byte[compactValue.length<<3];
		for(int i=0;i<result.length;++i){
			if((compactValue[i>>3]&(1<<(i&7)))==0)
				result[i]=0;
			else
				result[i]=1;
		}
		return result;		
	}
	/**
	 * 字符串类型的指纹数据转为字节数组
	 * @param hashValue
	 * @return
	 */
	private static byte[] toBytes(String hashValue){
		hashValue=hashValue.replaceAll("\\s", "");
		byte[] result=new byte[hashValue.length()];
		for(int i=0;i<result.length;++i){
			char c = hashValue.charAt(i);
			if('0'==c)
				result[i]=0;
			else if('1'==c)
				result[i]=1;
			else
				throw new IllegalArgumentException("invalid hashValue String");
		}
		return result;
	}
	/**
	 * 缩放图像到指定尺寸
	 * @param src
	 * @param width
	 * @param height
	 * @return
	 */
	private static BufferedImage resize(Image src,int width,int height){
		BufferedImage result = new BufferedImage(width, height,  
                BufferedImage.TYPE_3BYTE_BGR); 
		 Graphics g = result.getGraphics();
		 try{
			 g.drawImage(src.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
		 }finally{
			 g.dispose();
		 }
		return result;		
	}
	/**
	 * 计算均值
	 * @param src
	 * @return
	 */
	private static  int mean(byte[] src){
		long sum=0;
		// 将数组元素转为无符号整数
		for(byte b:src)sum+=(long)b&0xff;
		return (int) (Math.round((float)sum/src.length));
	}
	/**
	 * 二值化处理
	 * @param src
	 * @return
	 */
	private static byte[] binaryzation(byte[]src){
		byte[] dst = src.clone();
		int mean=mean(src);
		for(int i=0;i<dst.length;++i){
			// 将数组元素转为无符号整数再比较
			dst[i]=(byte) (((int)dst[i]&0xff)>=mean?1:0);
		}
		return dst;
		
	}
	/**
	 * 转灰度图像
	 * @param src
	 * @return
	 */
	private static BufferedImage toGray(BufferedImage src){
		if(src.getType()==BufferedImage.TYPE_BYTE_GRAY){
			return src;
		}else{
			// 图像转灰
			BufferedImage grayImage = new BufferedImage(src.getWidth(), src.getHeight(),  
	                BufferedImage.TYPE_BYTE_GRAY);
			new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(src, grayImage);
		    return grayImage;		
		}
	}

	@Override
	public String toString() {
		return toString(true);
	}
	/**
	 * @param multiLine 是否分行
	 * @return
	 */
	public String toString(boolean multiLine) {
		StringBuffer buffer=new StringBuffer();
		int count=0;
		for(byte b:this.binaryzationMatrix){
			buffer.append(0==b?'0':'1');
			if(multiLine&&++count%HASH_SIZE==0)
				buffer.append('\n');
		}
		return buffer.toString();
	}
	@Override
	public boolean equals(Object obj) {
		if(obj instanceof FingerPrint){
			return Arrays.equals(this.binaryzationMatrix,((FingerPrint)obj).binaryzationMatrix);
		}else
			return super.equals(obj);
	}

	/**
	 * 与指定的压缩格式指纹比较相似度
	 * @param compactValue
	 * @return
	 * @see #compare(FingerPrint)
	 */
	public float compareCompact(byte[] compactValue){
		return compare(createFromCompact(compactValue));
	}
	/**
	 * @param hashValue
	 * @return
	 * @see #compare(FingerPrint)
	 */
	public float compare(String hashValue){
		return compare(new FingerPrint(hashValue));
	}
	/**
	 * 与指定的指纹比较相似度
	 * @param hashValue
	 * @return
	 * @see #compare(FingerPrint)
	 */
	public float compare(byte[] hashValue){
		return compare(new FingerPrint(hashValue));
	}
	/**
	 * 与指定图像比较相似度
	 * @param image2
	 * @return
	 * @see #compare(FingerPrint)
	 */
	public float compare(BufferedImage image2){
		return compare(new FingerPrint(image2));
	}
	/**
	 * 比较指纹相似度
	 * @param src
	 * @return 
	 * @see #compare(byte[], byte[])
	 */
	public float compare(FingerPrint src){
		if(src.binaryzationMatrix.length!=this.binaryzationMatrix.length)
			throw new IllegalArgumentException("length of hashValue is mismatch");
		return compare(binaryzationMatrix,src.binaryzationMatrix);
	}
	/**
	 * 判断两个数组相似度,数组长度必须一致否则抛出异常
	 * @param f1
	 * @param f2
	 * @return 返回相似度(0.0~1.0)
	 */
	private static float compare(byte[] f1,byte[] f2){
		if(f1.length!=f2.length)
			throw new IllegalArgumentException("mismatch FingerPrint length");
		int sameCount=0;
		for(int i=0;i<f1.length;++i){
			if(f1[i]==f2[i])++sameCount;
		}
		return (float)sameCount/f1.length;
	}
	public static float compareCompact(byte[] f1,byte[] f2){
		return compare(uncompact(f1),uncompact(f2));
	}
	public static float compare(BufferedImage image1,BufferedImage image2){
		return new FingerPrint(image1).compare(new FingerPrint(image2));
	}
}

#调用示例
junit测试代码

package test;

import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.junit.Test;
import net.gdface.image.FingerPrint;
import net.gdface.image.NotImage;
import net.gdface.image.UnsupportedFormat;

public class TestFingerPrint {

	@Test
	public void testCompare() throws IOException{
		FingerPrint fp1 = new FingerPrint(ImageIO.read(new File("d:\\tmp\\he049-black.jpg")));
		FingerPrint fp2 =new FingerPrint(ImageIO.read(new File("d:\\tmp\\he049-gray.jpg")));
		System.out.println(fp1.toString(true));
		System.out.printf("sim=%f",fp1.compare(fp2));
	}
}

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
以下是一种基于哈希函数比较图片相似度Java 实现: 1. 首先,我们需要读取两张图片并将其转换为灰度图像: ```java import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; public class ImageCompare { public static void main(String[] args) throws IOException { // 读取图片1 File file1 = new File("image1.jpg"); BufferedImage image1 = ImageIO.read(file1); BufferedImage grayImage1 = toGrayImage(image1); // 读取图片2 File file2 = new File("image2.jpg"); BufferedImage image2 = ImageIO.read(file2); BufferedImage grayImage2 = toGrayImage(image2); // TODO: 计算哈希值并比较相似度 } // 将彩色图像转换为灰度图像 private static BufferedImage toGrayImage(BufferedImage image) { int width = image.getWidth(); int height = image.getHeight(); BufferedImage grayImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int rgb = image.getRGB(x, y); int r = (rgb >> 16) & 0xFF; int g = (rgb >> 8) & 0xFF; int b = rgb & 0xFF; int gray = (int) (0.299 * r + 0.587 * g + 0.114 * b); grayImage.setRGB(x, y, (gray << 16) | (gray << 8) | gray); } } return grayImage; } } ``` 2. 接下来,我们可以使用 DCT(离散余弦变换)算法计算哈希值。DCT 算法可以将图像分解成若干个频率分量,我们只需要保留其中的高频分量,然后将其转化为 64 位二进制数即可。在计算哈希值时,我们可以将每个像素的灰度值作为 DCT 算法的输入,然后将输出结果的前 64 个元素进行二值化(即大于平均值为 1,小于平均值为 0),得到一个 64 位的二进制数,作为该图像哈希值。 ```java import org.apache.commons.math3.complex.Complex; import org.apache.commons.math3.transform.*; import java.util.Arrays; public class ImageCompare { public static void main(String[] args) throws IOException { // 读取图片1 File file1 = new File("image1.jpg"); BufferedImage image1 = ImageIO.read(file1); BufferedImage grayImage1 = toGrayImage(image1); // 读取图片2 File file2 = new File("image2.jpg"); BufferedImage image2 = ImageIO.read(file2); BufferedImage grayImage2 = toGrayImage(image2); // 计算哈希值并比较相似度 long hash1 = calculateHash(grayImage1); long hash2 = calculateHash(grayImage2); double similarity = compareHash(hash1, hash2); System.out.println("Hash1: " + Long.toBinaryString(hash1)); System.out.println("Hash2: " + Long.toBinaryString(hash2)); System.out.println("Similarity: " + similarity); } // 计算哈希值 private static long calculateHash(BufferedImage grayImage) { int width = grayImage.getWidth(); int height = grayImage.getHeight(); double[][] data = new double[height][width]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { data[y][x] = grayImage.getRGB(x, y) & 0xFF; } } DctNormalization normalization = DctNormalization.ORTHOGONAL_DCT_I; RealMatrix2D matrix = new DenseRealMatrix2D(data); RealMatrix2D dct = new FastCosineTransformer2D(normalization).transform(matrix); double[] vector = new double[64]; double sum = 0; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { vector[i * 8 + j] = dct.getEntry(i, j); sum += vector[i * 8 + j]; } } double mean = sum / 64; long hash = 0; for (int i = 0; i < 64; i++) { if (vector[i] > mean) { hash |= (1L << i); } } return hash; } // 比较哈希值的相似度 private static double compareHash(long hash1, long hash2) { int diff = Long.bitCount(hash1 ^ hash2); return 1.0 - (double) diff / 64.0; } } ``` 3. 在上面的代码中,我们使用了 Apache Commons Math 库中的 DCT 实现。我们首先将图像数据以二维数组的形式传递给 DCT 算法,然后将其转化为一维数组。在计算哈希值时,我们只需要保留前 64 个元素,然后将其转化为 64 位的二进制数即可。在比较哈希值时,我们可以使用 Hamming 距离,即两个二进制数对应位不同的数量。相似度可以用 1 减去 Hamming 距离除以总位数的结果得到。 需要注意的是,哈希函数比较图片相似度并不是一个精确的方法,可能会存在误差。在实际应用中,我们可以根据具体需求调整阈值,以达到较好的效果。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值