目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash)。图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅图像是否相似。两幅图像越相似,其哈希值的汉明距离越小,通过这种方式就能够比较两幅图像是否相似。在实际应用中,图像哈希算法可以用于图片检索,重复图片剔除,以图搜图以及图片相似度比较。

为什么图像哈希算法能够评估两幅图像的相似性,这就需要从哈希值说起,哈希值计算算法的本质就是对原始数据进行有损压缩,有损压缩后的固定字长能够作为唯一标识来标识原始数据,这个唯一标识就是哈希值。通常改变原始数据任意一个部分,哈希值都将不同。关于哈希值的具体介绍见:

通俗地理解哈希函数

但是计算图像哈希值的方法并不唯一,因此有不同的图像哈希计算方法。OpenCV contrib库中的img_hash模块提供计算两种图像的哈希值并比较两张图像相似性的算法。img_hash模块主要移植自PHash库,其官方代码仓库介绍见:

Image Hashing algorithms

img_hash模块提供了多种图像哈希算法,具体介绍如下:

  • Average hash (also called Different hash)
  • PHash (also called Perceptual hash)
  • Marr Hildreth Hash
  • Radial Variance Hash
  • Block Mean Hash (modes 0 and 1)
  • Color Moment Hash (this is the one and only hash algorithm resist to rotation attack(-90~90 degree))

PHash是工程实践最常用的图像哈希算法。本文主要介绍img_hash中的几种哈希算法的使用,关于图像哈希进一步介绍见:

图片哈希概述(image hash)

本文需要OpenCV contrib库,OpenCV contrib库的编译安装见:

OpenCV_contrib库在windows下编译使用指南

本文所有代码见:

OpenCV-Practical-Exercise

1 方法说明与代码实现

1.1 方法说明

图像哈希算法计算过程如下图所示,以pHash为例,先将将图片缩小到8x8的尺寸,总共64个像素,然后通过pHash函数计算hash值,得到一个唯一标识码hash码,hash码包括8个uint8数值,组合在一起,就构成了一个64位的整数。

pHash可以比较不同大小图像的hash值。通过计算两幅图像hash码的汉明距离,得到结果如下表所示:

img1 img2 img3
img1 0
img2 1.0 0
img3 29.0 30.0 0

汉明距离越小,表示两幅图像越接近,可以看到img1和img2最相近,img2为img1的灰色版本。

OpenCV img_hash模块各种哈希算法的特点和文献如下所示:

  1. AverageHash

    基于像素均值计算哈希值,一种快速的图像哈希算法,但仅适用于简单情况。
  2. PHash

    AverageHash的改进版,比AverageHash慢,但可以适应更多的情况。
  3. MarrHildrethHash

    基于Marr-Hildreth边缘算子计算哈希值,速度最慢,但更具区分性。
  4. RadialVarianceHash

    基于Radon变换计算哈希值
  5. BlockMeanHash

    基于块均值计算哈希值,与MarrHildrethHash在同一篇文章介绍。
  6. ColorMomentHash

    基于颜色矩计算哈希值,与RadialVarianceHash在同一篇文章介绍。

本文提供img_hash模块的C++和Python代码示例。实际调用方法如下:

C++

// 创建AverageHash类
Ptr<AverageHash> func= AverageHash::create;
// 计算图a的哈希值
func->compute(a, hashA);
// 计算图b的哈希值
func->compute(b, hashB);
// 比较两张图像哈希值的距离
func->compare(hashA, hashB);

Python

# 创建类
hashFun = cv2.img_hash.AverageHash_create()
# 计算图a的哈希值
hashA = hashFun.compute(a)
# 计算图b的哈希值
hashB = hashFun.compute(b)
# 比较两张图像哈希值的距离
hashFun.compare(hashA, hashB)

1.2 代码实现

C++和Python实现都分别提供,结果如1.1所示,但是C++代码用了类模板。通过ImgHashBase基础类,能够实现代码重复使用。

代码测试的图像已经在1.1部分展示,img1为基准图像,img2为img1的灰色版本,img3是另外一张完全不同的彩色图。

C++

#include <opencv2/opencv.hpp>
#include <opencv2/img_hash.hpp> #include <iostream> using namespace cv;
using namespace cv::img_hash;
using namespace std; template <typename T>
inline void test_one(const std::string &title, const Mat &a, const Mat &b)
{
cout << "=== " << title << " ===" << endl;
TickMeter tick;
Mat hashA, hashB;
// 模板方便重复利用
Ptr<ImgHashBase> func;
func = T::create(); tick.reset();
tick.start();
// 计算图a的哈希值
func->compute(a, hashA);
tick.stop();
cout << "compute1: " << tick.getTimeMilli() << " ms" << endl; tick.reset();
tick.start();
// 计算图b的哈希值
func->compute(b, hashB);
tick.stop();
cout << "compute2: " << tick.getTimeMilli() << " ms" << endl; // 比较两张图像哈希值的距离
cout << "compare: " << func->compare(hashA, hashB) << endl << endl;
} int main()
{
// 打开两张图像进行相似度比较
Mat input = imread("./image/img1.jpg");
Mat target = imread("./image/img2.jpg"); // 通过不同方法比较图像相似性
test_one<AverageHash>("AverageHash", input, target);
test_one<PHash>("PHash", input, target);
test_one<MarrHildrethHash>("MarrHildrethHash", input, target);
test_one<RadialVarianceHash>("RadialVarianceHash", input, target);
test_one<BlockMeanHash>("BlockMeanHash", input, target);
test_one<ColorMomentHash>("ColorMomentHash", input, target); system("pause");
return 0;
}

Python

# -*- coding: utf-8 -*-
"""
Created on Thu Aug 27 19:03:21 2020 @author: luohenyueji
""" import cv2 def test_one(title, a, b):
# 创建类
if "AverageHash" == title:
hashFun = cv2.img_hash.AverageHash_create()
elif "PHash" == title:
hashFun = cv2.img_hash.PHash_create()
elif "MarrHildrethHash" == title:
hashFun = cv2.img_hash.MarrHildrethHash_create()
elif "RadialVarianceHash" == title:
hashFun = cv2.img_hash.RadialVarianceHash_create()
elif "BlockMeanHash" == title:
hashFun = cv2.img_hash.BlockMeanHash_create()
elif "ColorMomentHash" == title:
hashFun = cv2.img_hash.ColorMomentHash_create() tick = cv2.TickMeter()
print("=== " + title + " ===") tick.reset()
tick.start()
# # 计算图a的哈希值
hashA = hashFun.compute(a)
tick.stop()
print("compute1: " + str(tick.getTimeMilli()) + " ms") tick.reset()
tick.start()
# 计算图b的哈希值
hashB = hashFun.compute(b)
tick.stop()
print("compute2: " + str(tick.getTimeMilli()) + " ms")
# 比较两张图像哈希值的距离
print("compare: " + str(hashFun.compare(hashA, hashB))) def main():
inputImg = cv2.imread("./image/img1.jpg")
targetImg = cv2.imread("./image/img2.jpg") if inputImg is None or targetImg is None:
print("check input image")
return test_one("AverageHash", inputImg, targetImg)
test_one("PHash", inputImg, targetImg)
test_one("MarrHildrethHash", inputImg, targetImg)
test_one("RadialVarianceHash", inputImg, targetImg)
test_one("BlockMeanHash", inputImg, targetImg)
test_one("ColorMomentHash", inputImg, targetImg) if __name__ == '__main__':
main()

1.3 方法比较与选择

以img1为基准,img2与img1对比结果和img3与img1对比结果如下表所示。可以看到RadialVarianceHash和ColorMomentHash结果与事实不符,这主要因为RadialVarianceHash和ColorMomentHash通过像素点颜色值信息来计算哈希值,img2是灰色图与img1相差过大。

此外可以看到各种图像哈希算法的计算速度,在实际中PHash是个很不错的选择,快速且效果好。

img1/img2 img1/img3 result speed/ms
AverageHash 3 31 TRUE 0.0565
PHash 1 29 TRUE 0.072
MarrHildrethHash 28 283 TRUE 9.8433
RadialVarianceHash 0.989896 0.543267 FALSE 1.0259
BlockMeanHash 10 113 TRUE 0.694
ColorMomentHash 45.4928 16.7632 FALSE 3.39

然而,PHash常用,并不代表其他算法没用。比如如果将img1水平翻转得到img4,如下图所示。那么将会得到完全不一样的结果,如下表所示。

img1/img3 img1/img4 result
AverageHash 31 36 FALSE
PHash 29 36 FALSE
MarrHildrethHash 283 301 FALSE
RadialVarianceHash 0.543267 0.285715 TRUE
BlockMeanHash 113 139 FALSE
ColorMomentHash 16.7632 0.270448 TRUE

导致以上情况的主要原因是,RadialVarianceHash和ColorMomentHash基于全局信息来计算hash值,其他算法基于局部信息来计算hash值。

总之在实际应用中,通过图像哈希值计算图像相似性比较粗糙,但是图像哈希值也是比较常用的图像相似性比较算法。现在图像相似性比较算法效果都很一般,即使用了深度学习如Siamese Network,效果也没有太大提高。因此在计算相似性前,都会进行图像对准和颜色转换,这一点是非常必要的。不过图像哈希算法在实际中计算固定场景效果还是很不错的。

2 参考

2.1 参考代码

2.2 相关文档

2.3 相关文献

[OpenCV实战]45 基于OpenCV实现图像哈希算法的更多相关文章

  1. [OpenCV实战]48 基于OpenCV实现图像质量评价

    本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...

  2. [OpenCV实战]28 基于OpenCV的GUI库cvui

    目录 1 cvui的使用 1.1 如何在您的应用程序中添加cvui 1.2 基本的"hello world"应用程序 2 更高级的应用 3 代码 4 参考 有很多很棒的GUI库,例 ...

  3. [OpenCV实战]47 基于OpenCV实现视觉显著性检测

    人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...

  4. [OpenCV实战]38 基于OpenCV的相机标定

    文章目录 1 什么是相机标定? 2 图像形成几何学 2.1 设定 2.1.1 世界坐标系 2.1.2 相机坐标系 2.1.3 图像坐标系 2.2 图像形成方法总结 3 基于OpenCV的相机标定原理 ...

  5. [OpenCV实战]15 基于深度学习的目标跟踪算法GOTURN

    目录 1 什么是对象跟踪和GOTURN 2 在OpenCV中使用GOTURN 3 GOTURN优缺点 4 参考 在这篇文章中,我们将学习一种基于深度学习的目标跟踪算法GOTURN.GOTURN在Caf ...

  6. [OpenCV实战]51 基于OpenCV实现图像极坐标变换与逆变换

    在图像处理领域中,经常通过极坐标与笛卡尔直角坐标的互转来实现图像中圆形转为方形,或者通过极坐标反变换实现方形转圆形.例如钟表的表盘,人眼虹膜,医学血管断层都需要用到极坐标变换来实现圆转方. 文章目录 ...

  7. [OpenCV实战]26 基于OpenCV实现选择性搜索算法

    目录 1 背景 1.1 目标检测与目标识别 1.2 滑动窗口算法 1.3 候选区域选择算法 2 选择性搜索算法 2.1 什么是选择性搜索? 2.2 选择性搜索相似性度量 2.3 结果 3 代码 4 参 ...

  8. [OpenCV实战]11 基于OpenCV的二维码扫描器

    目录 1 二维码(QRCode)扫描 2 结果 3 参考 在这篇文章中,我们将看到如何使用OpenCV扫描二维码.您将需要OpenCV3.4.4或4.0.0及更高版本来运行代码. 1 二维码(QRCo ...

  9. [OpenCV实战]19 使用OpenCV实现基于特征的图像对齐

    目录 1 背景 1.1 什么是图像对齐或图像对准? 1.2 图像对齐的应用 1.3 图像对齐基础理论 1.4 如何找到对应点 2 OpenCV的图像对齐 2.1 基于特征的图像对齐的步骤 2.2 代码 ...

随机推荐

  1. Kubeadm部署高可用K8S集群

    一 基础环境 1.1 资源 节点名称 ip地址 VIP 192.168.12.150 master01 192.168.12.48 master02 192.168.12.242 master03 1 ...

  2. 齐博x1注意事项:再强调严禁用记事本改任何文件

    提醒大家,X1任何文件,不要用记事本修改.比如这个用户就改出问题了 导致后台不能升级. 当然这是问题之一, 还有其它意料之外的问题.还没发现. 这个用户做一个测试风格. 配置文件可能是用记事本修改的. ...

  3. 齐博x1非正常修改后台入口admin.php导致的问题

    如果你不是从后台基础设置修改后台入口admin.php文件名的话,也即强行通过FTP修改admin.php文件的名的话,就会导致网站会运行异常 比如会出现不能上传文件之类的.如下图所示

  4. Springboot JSON 转换:Jackson篇

    本案例基于 Springboot 2.5.7 单元测试场景下进行 <!-- SpringMVC默认使用Jacson,只需要引用web启动器即可,无序单独引用Jackson --> < ...

  5. LcdToos如何实现PX01自动调Flicker及VCOM烧录

    准备工作: LcdTools+PX01点亮需调Flicker的屏:F118 Flicker探头,用于自动Flicker校准测量,F118连接PX01上电后,探头屏会提示零点校准,此时需盖住探头窗口再按 ...

  6. C++算法之旅、02 从木棒切割问题领悟二分法精髓

    172.木棒切割问题 https://sunnywhy.com/problem/172 题目描述 给出n根木棒的长度,现在希望通过切割它们来得到至少k段长度相等的木棒(长度必须是整数),问这些长度相等 ...

  7. css文字单行/多行超出显示省略号...

    css文字单行/多行超出显示省略号... 项目里写css样式我们经常会遇到将文字超出显示省略号的情况,记录一下以后能用到. 单行超出 .oneline { width:300upx; /*宽度一定要设 ...

  8. HTML5+CSS3常见布局方式

    1.等高布局 1.1 代码 等高布局是指子元素在父元素中高度相等的布局方式 <div class="father"> <div class="f1&qu ...

  9. Java注解与原理分析

    目录 一.注解基础 二.注解原理 三.常用注解 1.JDK注解 2.Lombok注解 四.自定义注解 1.同步控制 2.类型引擎 五.参考源码 使用的太多,被忽略的理所当然: 一.注解基础 注解即标注 ...

  10. DevOps | 企业内源(内部开源)适合什么样的公司

    框架类是否适合企业内源? 框架类都由公司早期来的一些大佬们负责(相当于技术委员会),更新频率非常低.给框架类提MR的人,多数本身就在技术委员会. 如果公司的人员众多,类似BAT级别,几万人使用的框架, ...