最近在看运动目标跟踪方面的资料,偶然间看到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。但是思路还是很好。
——————————————————————————————————————————
参考文献: