OpenCV学习笔记22. 以图搜图,感知Hash的原理与实现(python/c++)

以图搜图,感知Hash的原理与实现(python/c++)

以下内容引自百度百科:

感知哈希算法(Perceptual hash algorithm)是哈希算法的一类,主要用来做相似图片的搜索工作。图片所包含的特征被用来生成一组指纹(不过它不是唯一的), 而这些指纹是可以进行比较的。

概括地讲,感知哈希算法一共分两步:
1、把图片转化为字符串 ,这个字符串就是图片的hash值,又称指纹
2、求两个字符串之间的相似度,字符串越相似说明两个图像越相似~

本文的目标是,实现一个最简单的感知哈希,下面看看详细的实现步骤

1、把图片转化为字符串

测试图像为:

这里写图片描述

(1) 把图像缩小为8*8,并转化为灰度图

# Step1. 把图像缩小为8 * 8,并转化为灰度图
src = cv2.imread(path, 0)
src = cv2.resize(src, (8, 8), cv2.INTER_LINEAR)

这里写图片描述

这64个像素的灰度值如下:

这里写图片描述

(2) 计算64个像素的平均灰度值

# Step2. 计算64个像素的灰度均值
avg = sum([sum(src[i]) for i in range(8)]) / 64

(3) 与平均值比较,生成01字符串

# Step3. 与平均值比较,生成01字符串
string = ""
    for i in range(8):
        string += ''.join(map(lambda i: '0' if i < avg else '1', src[i]))
print(''.join(string))

# lambda表达式可读性太差了, 等价于下面的形式
'''
for i in range(8):
    for j in range(8):
        if src[i][j] <= avg:
            src[i][j] = 0
        else:
            src[i][j] = 1
'''

64位01字符串是:
1111101111110011110000111100001110000011110000111111001111110111

(4) 计算hash值
对上面的64位字符串,每4个为一组,转化为16进制

# Step4. 计算hash值
result = ''
for i in range(0, 64, 4):
    result += ''.join('%x' % int(string[i: i + 4], 2))
return result

得到的结果为:fbf3c3c383c3f3f7

2、字符串相似度计算

字符串相似度计算使用汉明距离:

以下引自百度百科:

汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。

例如:

1011101 与 1001001 之间的汉明距离是 2。
2143896 与 2233796 之间的汉明距离是 3。
“toned” 与 “roses” 之间的汉明距离是 3。

这里写图片描述

def hamming(str1, str2):
    if len(str1) != len(str2): # 要求两个字符串的长度相等
        return
    count = 0
    for i in range(0, len(str1)):
        if str1[i] != str2[i]:
            count += 1
    return count

3、测试

这里写图片描述

测试使用了三张图片,测试leaf1与leaf2、leaf3之间的相似度。

这里写图片描述

leaf1 与leaf2的汉明距离是10,与leaf3的汉明距离是12,说明leaf1与leaf2更相似 ~

完整的代码如下:

# -*- coding:utf-8  -*-

"""
感知哈希的实现
author:lijialin(李家霖)
data:2017-08-04
"""
import cv2


def p_hash(path):

    # Step1. 把图像缩小为8 * 8,并转化为灰度图
    src = cv2.imread(path, 0)
    src = cv2.resize(src, (8, 8), cv2.INTER_LINEAR)

    # Step2. 计算64个像素的灰度均值
    avg = sum([sum(src[i]) for i in range(8)]) / 64

    # Step3. 与平均值比较,生成01字符串
    string = ''
    for i in range(8):
        string += ''.join(map(lambda i: '0' if i < avg else '1', src[i]))

    # Step4. 计算hash值
    result = ''
    for i in range(0, 64, 4):
        result += ''.join('%x' % int(string[i: i + 4], 2))
    return result


def hamming(str1, str2):
    if len(str1) != len(str2):
        return
    count = 0
    for i in range(0, len(str1)):
        if str1[i] != str2[i]:
            count += 1
    return count

# 读取三张图片,进行测试
h1 = p_hash('images/leaf1.jpg')
h2 = p_hash('images/leaf2.jpg')
h3 = p_hash('images/leaf3.jpg')

print(hamming(h1, h2)) # 10
print(hamming(h1, h3)) # 12

4、补充:余弦感知哈希算法

上面的算法由于使用了像素平均值,因此又叫均值哈希。均值哈希虽然简单,但对图像灰度的平均值特别敏感,也不具备旋转不变性。余弦感知哈希算法是对均值哈希上的提升,能够更好地处理旋转图形。“可以识别变形程度在25%以内的图片。”

余弦感知哈希算法步骤如下:

1、缩小尺寸:将图像缩小到32 * 32,并转化为灰度

2、计算DCT:对图像进行二维离散余弦变换

这里写图片描述

3、缩小DCT:只保留矩阵左上角8 * 8区域,对这个区域求哈希均值,并生成01字符串

4、计算hash值:与前一个方法相同

余弦hash的代码如下:

"""
余弦感知哈希算法
"""
def p_hash(path):

    # Step1. 把图像缩小为32 * 32,并转化为灰度图
    src = cv2.imread(path, 0)
    src = cv2.resize(src, (32, 32), cv2.INTER_LINEAR)

    # Step2. 对图像进行余弦变换
    h, w = src.shape[:2]
    arr = np.zeros((h, w), np.float32)
    arr[:h, :w] = src 

    src = cv2.dct(cv2.dct(arr)) # 离散余弦变换
    src.resize(8, 8)

    # Step3. 计算64个像素的灰度均值
    avg = sum([sum(src[i]) for i in range(8)]) / 64

    # Step4. 与平均值比较,生成01字符串
    string = ''
    for i in range(8):
        string += ''.join(map(lambda i: '0' if i < avg else '1', src[i]))

    # Step5. 计算hash值
    result = ''
    for i in range(0, 64, 4):
        result += ''.join('%x' % int(string[i: i + 4], 2))
    return result

参考

http://blog.csdn.net/zouxy09/article/details/17471401
http://yshblog.com/blog/43

补充:感知哈希的c++版代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


/************* 函数声明部分 start ************/

string p_hash(string path);    //计算图像的哈希字符串
int hamming(string str1, string str2);// 计算汉明距离
string str_to_hex(string s); // 字符串转16进制

/************* 函数声明部分 end ************/


int main() {
    string path0 = "images/image0.jpg";
    string path1 = "images/image1.jpg";
    string h0 = p_hash(path0);
    string h1 = p_hash(path1);
    cout << hamming(h0, h1) << endl;
}


// 输入图像的路径
// 感知哈希算法的实现
string p_hash(string path) {

    // Step1. 把图像缩小为 8 * 8
    Mat src = imread(path, 0);
    resize(src, src, Size(8, 8), 0, 0, CV_INTER_LINEAR);

    // Step2. 计算64个像素的灰度均值
    double avg = mean(src)[0];

    // Step3. 与平均值比较,生成01字符串
    string str = "";
    for (int i = 0; i < 8; i++) {
        unsigned char* data = src.ptr(i);
        for (int j = 0; j < 8; j++) {
            if (data[j] <= avg) {
                str += "0";
            }else {
                str += "1";
            }
        }
    }

    // Step4. 计算哈希值
    string h = "";
    for (int i = 0; i < 61; i+=4) {
        string s = str.substr(i,4);
        h += str_to_hex(s);
    }
    return h;
}


// 字符串转16进制
string str_to_hex(string s) {
    string result = "";
    int num = 0, p = 3;
    for (int i = 0; i < 4; i++) {
        if (s[i] == '1') {
            num += pow(2, p);
        }
        p--;
    }
    if (num == 10) { // 这里写得很渣很渣。。。
        result += "a";
    }else if (num == 11) {
        result += "b";
    }else if (num == 12) {
        result += "c";
    }else if (num == 13) {
        result += "d";
    }else if (num == 14) {
        result += "e";
    }
    else if (num == 15) {
        result += "f";
    }else {
        stringstream ss;
        ss << num;
        result += ss.str();
    }
    return result;
}


// 计算汉明距离
int hamming(string str1, string str2) {
    int count = 0;
    for (int i = 0; i < 16; i++) {
        if (str1[i] != str2[i]) {
            count++;
        }
    }
    return count;
}

今天就到这里吧, 拜拜~

这里写图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值