安卓手机图片相似度对比--OpenCV感知哈希算法
近期公司布置了一个类似于腾讯手机管家的应用开发项目,在该应用中包含一个功能模块能够将用户的设备中的相似图片自动识别并提供给用户进行筛选与删除的选择。
完全没有头绪。花了大量时间在网上搜索各种技术后发现,在线资源大多都提到了OpenCV技术的使用方法。于是乎决定深入研究OpenCV的具体实现方式。
在多种方案中认为感知哈希算法契合项目的实际需求,并且其实这个算法的实现并不复杂;只要遵循以下规则即可完成。
感知型哈希算法(perceptual hash algorithm) ,其功能是为任意一张图片编码为一个‘水印码’(fingerprint)字符串,并将这些水印码进行比较。水印码之间的相似度越高,则表明两张图片越相近。
实现步骤:
- 缩小尺寸:将图像缩小到8*8的尺寸,总共64个像素。这一步的作用是去除图像的细节,只保留结构/明暗等基本信息,摒弃不同尺寸/比例带来的图像差异;
- 简化色彩:将缩小后的图像,转为64级灰度,即所有像素点总共只有64种颜色;
- 计算平均值:计算所有64个像素的灰度平均值;
- 比较像素的灰度:将每个像素的灰度,与平均值进行比较,大于或等于平均值记为1,小于平均值记为0;
- 计算哈希值:将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图像的指纹。组合的次序并不重要,只要保证所有图像都采用同样次序就行了;
- 得到指纹以后,就可以对比不同的图像,看看64位中有多少位是不一样的。在理论上,这等同于”汉明距离”(Hamming distance,在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数)。如果不相同的数据位数不超过5,就说明两张图像很相似;如果大于10,就说明这是两张不同的图像。
以上内容摘自:http://www.ruanyifeng.com/blog/2011/07/principle_of_similar_image_search.html
到此,我们知道如何对比图片的相似度
该系统实现了相似图片识别功能,在实际应用中,用户可能会对同一张图片进行多次扫描操作,无需为每一次扫描操作获取图像的灰度值数据,因此,在我们的系统设计中可以实现将每次获取到的64位灰度值进行数据存储,通常情况下,在同一场景下拍摄的照片其灰度值不会有太大变化,也就是说,在实际应用中只需对同一张图片进行一次扫描即可完成识别任务,通过将这些数据持久化存储在数据库中,可以在后续的操作中显著提升识别速度.
64位灰度值被存储在一个长整型变量中,在需要时再将其转换为二进制表示以便后续处理。采用长整型存储不仅可以有效节省内存资源,而且能够避免在数据库或共享存储中进行繁琐的操作
C++代码:
只需要传入图片的地址即可,然后即可返回一个Long类型的数据
include "SimalarPhotoUtils.h"
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <android/log.h>
#define LOG_TAG "JNI"
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
using namespace cv;
JNIEXPORT jlong
JNICALL Java_com_tpv_phonemanager_utils_SimalarPhotoUtils_getPhotoArray(
JNIEnv *env, jclass cls, jstring strSrcImageName) {
//this all code are copy form internet
char *imagPath = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(strSrcImageName, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
imagPath = (char *) malloc(alen + 1);
memcpy(imagPath, ba, alen);
imagPath[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
Mat matSrc, matDst1;
matSrc = cv::imread(imagPath, CV_LOAD_IMAGE_COLOR);
int iAvg1 = 0;
int arr1[64];
if (!matSrc.data) {
return 0j;
}
cv::resize(matSrc, matDst1, cv::Size(8, 8), 0, 0, cv::INTER_CUBIC);
cv::cvtColor(matDst1, matDst1, CV_BGR2GRAY);
for (int i = 0; i < 8; i++) {
uchar *data1 = matDst1.ptr<uchar>(i);
int tmp = i * 8;
for (int j = 0; j < 8; j++) {
int tmp1 = tmp + j;
arr1[tmp1] = data1[j] / 4 * 4;
iAvg1 += arr1[tmp1];
}
}
iAvg1 /= 64;
char *result;
int p = 1;
jlong value = 0;
for (int i = 0; i < 64; i++) {
p *= 2;
if (arr1[i] >= iAvg1) {
value += p;
}
}
return value;
}
每张图片的灰度值我们已经掌握如何获取其中其中一张图片与另一张图片之间的差异主要体现在其灰度值的不同之处具体来说就是我们需要计算两张图片在每个对应的像素点上的灰度值是否有差异这一过程涉及到对长整型数据进行处理由于长整型在转化为二进制时可能会导致数值不够完整从而无法准确反映两者的差异程度因此在这种情况下我们需要自行对缺失的部分进行补充确保最终得到的结果具有完整性
private int comparePhoto(PictureItem picture1, PictureItem picture2) {
int iDiffNum = 0;
StringBuffer arr1 = new StringBuffer();
StringBuffer arr2 = new StringBuffer();
genarateByte(picture1, arr1);
genarateByte(picture2, arr2);
for (int i = 0; i < 64; i++) {
if (arr1.charAt(i) != arr2.charAt(i))
++iDiffNum;
}
return iDiffNum;
}
private void genarateByte(PictureItem info, StringBuffer arr) {
String str = Long.toBinaryString(info.getPhotoArray());
if (str.length() < 64) {
int len = 64 - str.length();
for (int i = 0; i < len; i++) {
arr.append(0);
}
arr.append(str);
} else {
arr.append(str);
}
}
写作水平还有待提升,在初次尝试网络分享时也存在诸多需要改进的地方。建议大家多加指导和支持。
