感知哈希算法

感知哈希算法(Perceptual Hash Algorithm)是Neal Krawetz博士提出一种图片相似度检索算法,它的作用是为每张图片生成一个“指纹”(Fingerprint)字符串,然后比较不同图片的指纹,结果越接近,就说明图片越相似。

1实现原理

(1)缩小图片尺寸。将图片缩小到8x8的尺寸,总共64个像素,作用是去除各种图片尺寸和图片比例的差异,只保留结构、明暗等基本信息。

(2)转为灰度图片。将缩小后的图片,转为64级灰度图片。

(3)计算灰度平均值。计算图片中所有像素的灰度平均值。

(4)比较像素的灰度。将每个像素的灰度与平均值进行比较,如果大于或等于平均值记为1,小于平均值记为0。

(5)计算哈希值。将上一步的比较结果,组合在一起,就构成了一个64位的二进制整数即图片指纹。

(6)对比图片指纹,计算汉明距离。得到图片的指纹后,就可以对比不同的图片的指纹,计算出64位中有多少位是不一样的即汉明距离。如果不相同的数据位数不超过5,就说明两张图片很相似,如果大于10,说明它们是两张不同的图片。

2计算哈希指纹

均值Hash算法

string hashValue(Mat &src)
{
    string rst(64, '\0');
    Mat img;
    if(src.channels() == 3) {
       cvtColor(src, img, CV_BGR2GRAY);
    } else {
        img = src.clone();
    }
    // 第一步,缩小尺寸。将图片缩小到8x8的尺寸,总共64个像素,去除图片的细节。
    resize(img, img, Size(8, 8));
    // 第二步,简化色彩(Color Reduce)。将缩小后的图片,转为64级灰度。
    uchar *pData;
    for(int i = 0; i < img.rows; i++)
    {
        pData = img.ptr<uchar>(i);
        for(int j = 0; j < img.cols; j++)
        {
            pData[j] = pData[j] / 4;
        }
    }
    // 第三步,计算平均值。计算所有64个像素的灰度平均值。
    int average = mean(img).val[0];
    // 第四步,比较像素的灰度。将每个像素的灰度,与平均值进行比较。大于或等于平均值记为1,小于平均值记为0。
    Mat mask = (img >= (uchar)average);
    // 第五步,计算哈希值。
    int index = 0;
    for(int i=0;i<mask.rows;i++)
    {
        pData = mask.ptr<uchar>(i);
        for(int j=0;j<mask.cols;j++)
        {
            if(pData[j]==0) {
                rst[index++] = '0';
            } else {
                rst[index++] = '1';
            }
        }
    }
    return rst;
}

pHash算法,相对Hash算法鲁棒性稍强一些。

string pHashValue(Mat &src)
{
    Mat img ,dst;
    string rst(64,'\0');
    double dIdex[64];
    double mean = 0.0;
    int k = 0;
    if(src.channels()==3) {
        cvtColor(src,src,CV_BGR2GRAY);
        img = Mat_<double>(src);
    } else {
        img = Mat_<double>(src);
    }
    // 第一步,缩放尺寸。
    resize(img, img, Size(8,8));
    // 第二步,离散余弦变换,DCT系数求取。
    dct(img, dst);
    // 第三步,求取DCT系数均值(左上角8*8区块的DCT系数)。
    for (int i = 0; i < 8; ++i)
    {
        for (int j = 0; j < 8; ++j)
        {
            dIdex[k] = dst.at<double>(i, j);
            mean += dst.at<double>(i, j) / 64;
            ++k;
        }
    }
    // 第四步,计算哈希值。
    for (int i =0;i<64;++i)
    {
        if (dIdex[i]>=mean) {
            rst[i] = '1';
        } else {
            rst[i] = '0';
        }
    }
    return rst;
}

3计算汉明距离

int hanmingDistance(string &str1,string &str2)
{
    if((str1.size() != 64) || (str2.size() != 64)) return -1;
    int difference = 0;
    for(int i = 0; i < 64; i++)
    {
        if(str1[i] != str2[i]) difference++;
    }
    return difference;
}

4MySQL下的指纹存储与检索

(1)字段类型

`hash` bit(64) NOT NULL COMMENT '图片指纹'

(2)添加数据

insert into table_name (`hash`) values (b'1010010001010100101101011000010010110101101000010001010110100100')

(3)汉明距离查询

select hash, LPAD(BIN(hash), 64, '0') as phash, BIT_COUNT(`hash` ^ b'1001010000110100100101001011010010010110010100100001101010000001') as distance from table_name where BIT_COUNT(`hash` ^ b'1001010000110100100101001011010010010110010100100001101010000001') <= 10 order by distance asc

results matching ""

    No results matching ""