目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(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. Hbase之API基本操作

    API之框架 private static Admin admin = null; private static Connection connection = null; private stati ...

  2. (数据科学学习手札144)使用管道操作符高效书写Python代码

    本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 大家好我是费老师,一些比较熟悉pandas的读者 ...

  3. MySQL开发

    常用数据类型 整数:tinyint.int.bigint小数:decimal.字符串:char.varchar.text 增 insert into 表名(列名,列名)values(值,值): 删 d ...

  4. PX01关于手机屏SPI触摸调试学习笔记

    上位机工具:http://www.xk-image.com/download/blog/0002_TP调试/LcdTools20210605.rar 调试案例:http://www.xk-image. ...

  5. 使用rsync向服务器迁移大文件

    场景 本人将12G本地单文件(12G大小h5文件数据集)向Linux服务器进行大文件上传时传输失败.最初使用 scp 命令或 rsync 直接对大文件进行传输,会出现网络断开或服务器端管道破裂情况,而 ...

  6. JAVA语言学习-面向对象(1)

    类与对象 类 类是JAVA语言中重要的复合型数据类型().类的实现包括两个部分:成员变量和成员方法("方法"可以看作是C语言中的函数) 类的声明 要使用类,首先得对其声明.声明一个 ...

  7. shardingsphere-jdbc 水平分表学习记录

    放在自己博客里搬过来一份~ 前司使用的是自己魔改的TDDL,在家时间比较多就尝试学一些业内比较常用的中间件. 这里记录一下学习中遇到的一些问题. 环境 设置的比较简单(太懒了就测试了几个表), 两个分 ...

  8. python 的time、datetime模块

    python 时间模块 import datetime ​ res = datetime.datetime.now() print(res) # 2022-08-07 16:47:07.120459 ...

  9. HTML躬行记(4)——Web音视频基础

    公司目前的业务会接触比较多的音视频,所以有必要了解一些基本概念. 文章涉及的一些源码已上传至 Github,可随意下载. 一.基础概念 本节音视频的基础概念摘自书籍<FFmpeg入门详解 音视频 ...

  10. 【笔记】入门DP(Ⅱ)

    0X00 P1433 吃奶酪 状压 \(DP\),把经过的点压缩成01串.若第 \(i\) 位为 \(0\) 表示未到达,为 \(1\) 则表示已到达. 用 \(f[i][j]\) 表示以 \(i\) ...