[OpenCV实战]45 基于OpenCV实现图像哈希算法
目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash)。图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅图像是否相似。两幅图像越相似,其哈希值的汉明距离越小,通过这种方式就能够比较两幅图像是否相似。在实际应用中,图像哈希算法可以用于图片检索,重复图片剔除,以图搜图以及图片相似度比较。
为什么图像哈希算法能够评估两幅图像的相似性,这就需要从哈希值说起,哈希值计算算法的本质就是对原始数据进行有损压缩,有损压缩后的固定字长能够作为唯一标识来标识原始数据,这个唯一标识就是哈希值。通常改变原始数据任意一个部分,哈希值都将不同。关于哈希值的具体介绍见:
但是计算图像哈希值的方法并不唯一,因此有不同的图像哈希计算方法。OpenCV contrib库中的img_hash模块提供计算两种图像的哈希值并比较两张图像相似性的算法。img_hash模块主要移植自PHash库,其官方代码仓库介绍见:
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中的几种哈希算法的使用,关于图像哈希进一步介绍见:
本文需要OpenCV contrib库,OpenCV contrib库的编译安装见:
本文所有代码见:
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模块各种哈希算法的特点和文献如下所示:
- AverageHash
基于像素均值计算哈希值,一种快速的图像哈希算法,但仅适用于简单情况。 - PHash
AverageHash的改进版,比AverageHash慢,但可以适应更多的情况。 - MarrHildrethHash
基于Marr-Hildreth边缘算子计算哈希值,速度最慢,但更具区分性。 - RadialVarianceHash
基于Radon变换计算哈希值 - BlockMeanHash
基于块均值计算哈希值,与MarrHildrethHash在同一篇文章介绍。 - 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实现图像哈希算法的更多相关文章
- [OpenCV实战]48 基于OpenCV实现图像质量评价
本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...
- [OpenCV实战]28 基于OpenCV的GUI库cvui
目录 1 cvui的使用 1.1 如何在您的应用程序中添加cvui 1.2 基本的"hello world"应用程序 2 更高级的应用 3 代码 4 参考 有很多很棒的GUI库,例 ...
- [OpenCV实战]47 基于OpenCV实现视觉显著性检测
人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...
- [OpenCV实战]38 基于OpenCV的相机标定
文章目录 1 什么是相机标定? 2 图像形成几何学 2.1 设定 2.1.1 世界坐标系 2.1.2 相机坐标系 2.1.3 图像坐标系 2.2 图像形成方法总结 3 基于OpenCV的相机标定原理 ...
- [OpenCV实战]15 基于深度学习的目标跟踪算法GOTURN
目录 1 什么是对象跟踪和GOTURN 2 在OpenCV中使用GOTURN 3 GOTURN优缺点 4 参考 在这篇文章中,我们将学习一种基于深度学习的目标跟踪算法GOTURN.GOTURN在Caf ...
- [OpenCV实战]51 基于OpenCV实现图像极坐标变换与逆变换
在图像处理领域中,经常通过极坐标与笛卡尔直角坐标的互转来实现图像中圆形转为方形,或者通过极坐标反变换实现方形转圆形.例如钟表的表盘,人眼虹膜,医学血管断层都需要用到极坐标变换来实现圆转方. 文章目录 ...
- [OpenCV实战]26 基于OpenCV实现选择性搜索算法
目录 1 背景 1.1 目标检测与目标识别 1.2 滑动窗口算法 1.3 候选区域选择算法 2 选择性搜索算法 2.1 什么是选择性搜索? 2.2 选择性搜索相似性度量 2.3 结果 3 代码 4 参 ...
- [OpenCV实战]11 基于OpenCV的二维码扫描器
目录 1 二维码(QRCode)扫描 2 结果 3 参考 在这篇文章中,我们将看到如何使用OpenCV扫描二维码.您将需要OpenCV3.4.4或4.0.0及更高版本来运行代码. 1 二维码(QRCo ...
- [OpenCV实战]19 使用OpenCV实现基于特征的图像对齐
目录 1 背景 1.1 什么是图像对齐或图像对准? 1.2 图像对齐的应用 1.3 图像对齐基础理论 1.4 如何找到对应点 2 OpenCV的图像对齐 2.1 基于特征的图像对齐的步骤 2.2 代码 ...
随机推荐
- GitLab + Jenkins + Harbor 工具链快速落地指南
目录 一.今天想干啥? 二.今天干点啥? 三.今天怎么干? 3.1.常规打法 3.2.不走寻常路 四.开干吧! 4.1.工具链部署 4.2.网络配置 4.3.验证工具链部署结果 4.3.1.GitLa ...
- Docker | 容器数据卷详解
什么是容器数据卷 从docker的理念说起,docker将应用和环境打包成一个镜像,运行镜像(生成容器)就可以访问服务了. 如果数据都存在容器中,那么删除容器,数据就会丢失!需求:数据可以持久化 My ...
- hibernate validation 手动参数校验 不经过spring
/** * 校验工具类 * @author wdmcygah * */ public class ValidationUtils { private static Validator validato ...
- 获取cpu的核数
//获取cpu的核数 System.out.println(Runtime.getRuntime().availableProcessors());
- 齐博x1fun实例 鉴于很多人问列表的筛选怎么放到首页、内容页等等地方 贴出方法
application\common\fun\Field.php 你可以复制一份 也可以直接改 直接改记得加锁 不然升级就覆盖了 我们把 public function list_filter($ ...
- 浅谈--ETCD的基本概念及用法
1. 简介 ETCD 是一个高可用的分布式键值数据库,可用于服务发现.ETCD 采用 raft 一致性算法,基于 Go 语言实现. raft是一个强一致的集群日志同步算法. ETCD使用gRPC,网络 ...
- 前端学习笔记--HTML5
网页的优点(客户端为网页)(B/S)模式 开发成本低) 不需要安装 无需更新 跨平台(最重要)可以有效的减小开发成本 传统的为C/S模式,开发成本高 前端工程师负责写网页的源代码,而浏览器负责把网页渲 ...
- Java代码审计sql注入
java_sec_code 该项目也可以叫做Java Vulnerability Code(Java漏洞代码). 每个漏洞类型代码默认存在安全漏洞(除非本身不存在漏洞),相关修复代码在注释里.具体可查 ...
- Windows版CheatSheet——一键显示当前程序快捷键列表
Windows系统上的各种软件有太多的快捷键,想要记住是几乎不可能的,推荐一个一键显示当前软件快捷键的软件,在使用其他程序的时候,只要按下Ctrl+`就可以理解弹出该软件的所有快捷键列表,还支持收藏功 ...
- go mod常用命令 已经 常见问题
最近接触到go mod,网上查了查资料,这里记录一下. 1 介绍 1.1.go mod是什么 go mod 是Golang 1.11 版本引入的官方包(package)依赖管理工具,用于解决之前没有地 ...