图片相似度计算 1----(ahash--c++实现)

本文深入探讨了图像去重算法中的ahash、phash和dhash等基于哈希的方法,重点介绍了ahash算法的实现过程,包括图片缩放、灰度化、计算均值和汉明距离等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

	最近接触到图像去重算法,有phash、dhash和ahash等基于哈希方法的去重算法。

phash全称是感知哈希算法(Perceptual hash algorithm),使用这玩意儿可以对每个图片生成一个值,然后计算他们的hamming distance,简单的说就是数一数二进制之后有几位不同。整个处理流程有点像对文章去重时先算simhash再算hamming distance,很多东西都可以直接套用过来。
首先介绍ahash算法(全称average hash)是phash算法的一种,是基于图像内容搜索最简单的,因此也有很多的局限性,主要用于图像的缩略图搜原图,对于图像的旋转、平移、对比度和微变形等都无能为力,所以很局限。
其整个算法的步骤:

  1. 图片缩放—将图片缩放到8*8大小(这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异)

  2. 灰度化—将缩小后的图片,转为64级灰度,也就是说所有的像素点总共有64种颜色

  3. 计算均值—计算所有像素的灰度平均值

  4. 得到88图像的ahash—88的像素值中大于均值的则用1表示,小于的用0表示,这样就得到一个64位二进制码最为该图像的ahash值

  5. 计算两幅图像ahash值的汉明距离,距离越小,表明两幅图像越相似;距离越大,表明两幅图像距离越大(将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了)

    得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。在理论上,这等同于计算"汉明距离"(Hamming distance)。如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。这种算法的优点是简单快速,不受图片大小缩放的影响,缺点是图片的内容不能变更。如果在图片上加几个文字,它就认不出来了。所以,它的最佳用途是根据缩略图,找出原图。实际应用中,往往采用更强大的pHash算法和SIFT算法,它们能够识别图片的变形。只要变形程度不超过25%,它们就能匹配原图。这些算法虽然更复杂,但是原理与上面的简便算法是一样的,就是先将图片转化成Hash字符串,然后再进行比较。
    具体代码可以参见Wote用python写的代码:
    
import glob
import os
import sys

from PIL import Image

EXTS = 'jpg', 'jpeg', 'JPG', 'JPEG', 'gif', 'GIF', 'png', 'PNG'

def avhash(im):
    if not isinstance(im, Image.Image):
        im = Image.open(im)
    im = im.resize((8, 8), Image.ANTIALIAS).convert('L')
    avg = reduce(lambda x, y: x + y, im.getdata()) / 64.
    return reduce(lambda x, (y, z): x | (z << y),
                  enumerate(map(lambda i: 0 if i < avg else 1, im.getdata())),
                  0)

def hamming(h1, h2):
    h, d = 0, h1 ^ h2
    while d:
        h += 1
        d &= d - 1
    return h

if __name__ == '__main__':
    if len(sys.argv) <= 1 or len(sys.argv) > 3:
        print "Usage: %s image.jpg [dir]" % sys.argv[0]
    else:
        im, wd = sys.argv[1], '.' if len(sys.argv) < 3 else sys.argv[2]
        h = avhash(im)

        os.chdir(wd)
        images = []
        for ext in EXTS:
            images.extend(glob.glob('*.%s' % ext))

        seq = []
        prog = int(len(images) > 50 and sys.stdout.isatty())
        for f in images:
            seq.append((f, hamming(avhash(f), h)))
            if prog:
                perc = 100. * prog / len(images)
                x = int(2 * perc / 5)
                print '\rCalculating... [' + '#' * x + ' ' * (40 - x) + ']',
                print '%.2f%%' % perc, '(%d/%d)' % (prog, len(images)),
                sys.stdout.flush()
                prog += 1

        if prog: print
        for f, ham in sorted(seq, key=lambda i: i[1]):
            print "%d\t%s" % (ham, f)
下面的是根据opencv用c++将其实现
  1. 图片缩放+灰度化
    直接调用opencv的函数就可以了

Mat img = imread("C:\\Users\\zs\\Desktop\\1.jpg", 1);
	if(!img.data){
		cout << "the image is not exist" << endl;
		return 0;
	}
	int size = 8;                             // 图片缩放后大小
	
	resize(img, img, Size(size,size));        // 缩放到8*8
	cvtColor(img, img, COLOR_BGR2GRAY);       // 灰度化

  1. 计算灰度化的均值

// 计算8*8图像的平均灰度
float calcAverage(Mat_<uchar> image, const int &size){
	float sum = 0;
	for(int i = 0 ; i < size; i++){
		for(int j = 0; j < size; j++){
			sum += image(i, j);
		}
	}
	return sum/(size*size);
}
  1. 得到8*8图像的ahash

/* 计算hash值
	image:8*8的灰度图像
	size: 图像大小  8*8
	ahahs:存放64位hash值
	averagePix: 灰度值的平均值
*/
void fingerPrint(Mat_<uchar> image, const int &size, bitset<hashLength> &ahash, const float &averagePix){
	for(int i = 0; i < size; i++){
		int pos = i * size;
		for(int j = 0; j < size; j++){
			ahash[pos+j] = image(i, j) >= averagePix ? 1:0;
		}
	}
}
  1. 计算汉明距离

/*计算汉明距离*/
int hammingDistance(const bitset<hashLength> &query, const bitset<hashLength> &target){
	int distance = 0;
	for(int i = 0; i < hashLength; i++){
		distance += (query[i] == target[i] ? 0 : 1);
	}
	return distance;
}

完整的代码:

#include <iostream>
#include <bitset>
#include <string>
#include <iomanip>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>

using namespace std;
using namespace cv;

#define hashLength 64


// 计算8*8图像的平均灰度
float calcAverage(Mat_<uchar> image, const int &size) {
	float sum = 0;
	for (int i = 0; i < size; i++) {
		for (int j = 0; j < size; j++) {
			sum += image(i, j);
		}
	}
	return sum / (size*size);
}

/* 计算hash值
	image:8*8的灰度图像
	size: 图像大小  8*8
	ahahs:存放64位hash值
	averagePix: 灰度值的平均值
*/
void fingerPrint(Mat_<uchar> image, const int &size, bitset<hashLength> &ahash, const float &averagePix) {
	for (int i = 0; i < size; i++) {
		int pos = i * size;
		for (int j = 0; j < size; j++) {
			ahash[pos + j] = image(i, j) >= averagePix ? 1 : 0;
		}
	}
}

/*计算汉明距离*/
int hammingDistance(const bitset<hashLength> &query, const bitset<hashLength> &target) {
	int distance = 0;
	for (int i = 0; i < hashLength; i++) {
		distance += (query[i] == target[i] ? 0 : 1);
	}
	return distance;
}

string bitTohex(const bitset<hashLength> &target) {
	string str;
	for (int i = 0; i < hashLength; i = i + 4) {
		int sum = 0;
		string s;
		sum += target[i] + (target[i + 1] << 1) + (target[i + 2] << 2) + (target[i + 3] << 3);
		stringstream ss;
		ss << hex << sum;    // 以十六进制保存
		ss >> s;
		str += s;
	}
	return str;
}

int main() {
	Mat img = imread("C:\\Users\\zs\\Desktop\\1.jpg", 1);
	if (!img.data) {
		cout << "the image is not exist" << endl;
		return 0;
	}
	int size = 8;                               // 图片缩放后大小

	resize(img, img, Size(size, size));         // 缩放到8*8
	cvtColor(img, img, COLOR_BGR2GRAY);         // 灰度化
	float averagePix = calcAverage(img, size);  // 计算灰度化的均值
	//cout << averagePix << endl;
	bitset<hashLength> ahash;
	fingerPrint(img, size, ahash, averagePix);  // 得到均值hash
	//cout << ahash << endl;
	cout << bitTohex(ahash) << endl;

	//string img_dir = "E:\\algorithmZack\\ImageSearch\\image\\";
	for (int i = 1; i <= 8; i++) {
		Mat target = imread("C:\\Users\\zs\\Desktop\\2.jpg", 1);
		if (!target.data) {
			cout << "the target image is not exist" << endl;
			continue;
		}
		resize(target, target, Size(size, size));
		cvtColor(target, target, COLOR_BGR2GRAY);
		float averagePix2 = calcAverage(target, size);
		bitset<hashLength> ahash2;
		fingerPrint(target, size, ahash2, averagePix2);

		//cout << averagePix2 << endl;
		int distance = hammingDistance(ahash, ahash2);      // 计算汉明距离
		cout << "【" << i << "-" << distance << "】 ";
	}
	cout << endl;
	return 0;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值