感知哈希算法--python实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/insthink/article/details/51366571

    最近在看运动目标跟踪方面的资料,偶然间看到zouxy09大神的一篇《基于感知哈希算法的视觉跟踪》,觉得挺有意思的。于是去查了相关的感知哈希的资料,发现在图片搜索领域里面应用非常广泛,这种技术我觉得有点像模板匹配,其核心在于:通过指纹的相似度来搜索最相似的图片。

    基于zouxy09大神的c++程序改写了一个python版本,并且增加了差值哈希的实现。

——————————————————————————————————————————

一、算法原理及过程

1.ahash算法

    在向下采样的过程中,只保留图像的低频信息。

    工作过程:

    ①缩小尺寸,简化色彩:8*8灰度

    ②计算灰度均值:64个灰度值的平均

    ③生成哈希码:将64个灰度值与上一步生成的平均值进行比较。比均值大,则置1;比均值小,则置0。从而得到一个包含64个元素的“指纹”。

    ④计算汉明距离:将两个“指纹”进行比较,计算它们的汉明距离。(==0):非常相似;(<5): 相似; (>10): 不同


2.phash算法

    均值哈希虽然简单,但是受均值影响大。如果对图像进行伽马校正或者进行直方图均值化都会影响均值,从而影响哈希值的计算。所以就有人提出更健壮的方法,通过离散余弦(DCT)进行低频提取。

    离散余弦变换(DCT)是种图像压缩算法,它将图像从像素域变换到频率域。然后一般图像都存在很多冗余和相关性的,所以转换到频率域之后,只有很少的一部分频率分量的系数才不为0,大部分系数都为0(或者说接近于0)。下图的右图是对lena图进行离散余弦变换(DCT)得到的系数矩阵图。从左上角依次到右下角,频率越来越高,由图可以看到,左上角的值比较大,到右下角的值就很小很小了。换句话说,图像的能量几乎都集中在左上角这个地方的低频系数上面了。

    工作过程:

    ①缩小尺寸,简化色彩:32*32灰度

    ②计算DCT:对图像进行离散余弦变换,得到32*32的DCT系数矩阵

    ③截取DCT:因为只有左上角的部分呈现图像的最低频部分,所以我们截取左上角的8*8矩阵

    ④计算均值:64个DCT系数的均值

    ⑤生成哈希码:将64个DCT系数与上一步生成的平均值进行比较。之后过程同均值哈希


3.dhash算法

    这是一种通过渐变实现的哈希算法,相比phash,速度上有较大的优势。

    工作过程:

    ①缩小尺寸、简化色彩:9*8灰度

    ②计算差异:每行9个像素做差得到8个差异值,总共64个差异值

    ③生成哈希码:差异值与0比较。大于0,置1;小于0,置0。之后过程同均值哈希

——————————————————————————————————————————

二、代码实现:

# coding:UTF-8

import cv2
import numpy as np
import time
from glob import iglob


class HashTracker:
    def __init__(self, path):
        # 初始化图像
        self.img = cv2.imread(path)
        self.gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY)

    def cal_hash_code(self, cur_gray):
        s_img = cv2.resize(cur_gray, dsize=(8, 8))
        img_mean = cv2.mean(s_img)
        return s_img > img_mean[0]

    def cal_phash_code(self, cur_gray):
        # 缩小至32*32
        m_img = cv2.resize(cur_gray, dsize=(32, 32))
        # 浮点型用于计算
        m_img = np.float32(m_img)
        # 离散余弦变换,得到dct系数矩阵
        img_dct = cv2.dct(m_img)
        img_mean = cv2.mean(img_dct[0:8, 0:8])
        # 返回一个8*8bool矩阵
        return img_dct[0:8, 0:8] > img_mean[0]

    def cal_dhash_code(self, cur_gray):
        # dsize=(width, height)
        m_img = cv2.resize(cur_gray, dsize=(9, 8))
        m_img = np.int8(m_img)
        # 得到8*8差值矩阵
        m_img_diff = m_img[:, :-1] - m_img[:, 1:]
        return m_img_diff > 0

    def cal_hamming_distance(self, model_hash_code, search_hash_code):
        # 返回不相同的个数
        diff = np.uint8(model_hash_code - search_hash_code)
        return cv2.countNonZero(diff)

    def hash_track(self, roi, rect, flag=0):
        # 获得矩形框信息
        width = abs(rect[0] - rect[2])
        height = abs(rect[1] - rect[3])
        # 获得当前图像的长宽信息
        img_w, img_h = self.img.shape[:2]
        # 根据flag,选择方法,计算前一帧的hash值
        if flag == 0:
            model_hash_code = self.cal_hash_code(roi)
        elif flag == 1:
            model_hash_code = self.cal_phash_code(roi)
        elif flag == 2:
            model_hash_code = self.cal_dhash_code(roi)
        # 初始化汉明距离
        min_dis = 64
        # 滑动窗口匹配,步长为2
        for i in xrange(0, img_h, 2):
            for j in xrange(0, img_w, 2):
                if flag == 0:
                    search_hash_code = self.cal_hash_code(self.gray[j:j + height, i:i + width])
                elif flag == 1:
                    search_hash_code = self.cal_phash_code(self.gray[j:j + height, i:i + width])
                elif flag == 2:
                    search_hash_code = self.cal_dhash_code(self.gray[j:j + height, i:i + width])
                # 计算汉明距离
                distance = self.cal_hamming_distance(model_hash_code, search_hash_code)
                # 获得最小汉明距离,同时得到此时的匹配框
                if distance < min_dis:
                    rect = i, j, i + width, j + height
                    min_dis = distance
        # 根据匹配框,获得下一帧的匹配模板
        roi = self.gray[rect[1]:rect[3], rect[0]:rect[2]]
        # 显示当前帧矩形框位置
        cv2.rectangle(self.img, (rect[0], rect[1]), (rect[2], rect[3]), (255, 0, 0), 2)
        return roi


# 鼠标响应函数
box = [0]*4


def mouse_handler(event, x, y, flag, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        # 起始点记录
        box[0], box[1] = x, y
    elif event == cv2.EVENT_MOUSEMOVE and flag == cv2.EVENT_FLAG_LBUTTON:
        box[2], box[3] = x, y
    elif event == cv2.EVENT_LBUTTONUP:
        # 获得右下角点
        box[2], box[3] = x, y


def main():
    # 读取第一张图片,用来画框
    img = cv2.imread('./img/0001.jpg')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv2.namedWindow('hashTracker', 1)
    cv2.setMouseCallback('hashTracker', mouse_handler)
    while True:
        cv2.imshow('hashTracker', img)
        if cv2.waitKey(1) == 27:
            break
    # 获取初始化模型
    model = gray[box[1]:box[3], box[0]:box[2]]
    # 获取图片序列
    paths = iglob(r'./img/*.jpg')
    # 计数用
    frame_count = 0
    # 循环读入图片
    for path in paths:
        frame_count += 1
        # 实例创建
        h = HashTracker(path)
        # 感知哈希跟踪
        start_time = time.clock()
        model = h.hash_track(model, box)
        fin_time = time.clock()

        print "%d: delta time:%.2f" % (frame_count, fin_time - start_time)
        cv2.imshow('hashTracker', h.img)
        if cv2.waitKey(20) == 27:
            break


if __name__ == '__main__':
    main()
——————————————————————————————————————————
三、实验结果及分析

下面是基于均值哈希跟踪的实验结果


                              第25帧                                                             第50帧


                            第100帧                                                            第200帧

结果:均值哈希和差值哈希速度相对比较快,phash效果好,但是速度好慢;其实总体上,如果要用作跟踪算法的话,我觉得速度都比较慢OTZ。但是思路还是很好。

——————————————————————————————————————————

参考文献:

基于感知哈希算法的视觉目标跟踪

三种基于感知哈希算法的相似图像检索技术

相似图片搜索原理


    


  


猜你喜欢

转载自blog.csdn.net/insthink/article/details/51366571