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

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

Python

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

1.2 代码实现

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

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

C++

  1. #include <opencv2/opencv.hpp>
  2. #include <opencv2/img_hash.hpp>
  3. #include <iostream>
  4. using namespace cv;
  5. using namespace cv::img_hash;
  6. using namespace std;
  7. template <typename T>
  8. inline void test_one(const std::string &title, const Mat &a, const Mat &b)
  9. {
  10. cout << "=== " << title << " ===" << endl;
  11. TickMeter tick;
  12. Mat hashA, hashB;
  13. // 模板方便重复利用
  14. Ptr<ImgHashBase> func;
  15. func = T::create();
  16. tick.reset();
  17. tick.start();
  18. // 计算图a的哈希值
  19. func->compute(a, hashA);
  20. tick.stop();
  21. cout << "compute1: " << tick.getTimeMilli() << " ms" << endl;
  22. tick.reset();
  23. tick.start();
  24. // 计算图b的哈希值
  25. func->compute(b, hashB);
  26. tick.stop();
  27. cout << "compute2: " << tick.getTimeMilli() << " ms" << endl;
  28. // 比较两张图像哈希值的距离
  29. cout << "compare: " << func->compare(hashA, hashB) << endl << endl;
  30. }
  31. int main()
  32. {
  33. // 打开两张图像进行相似度比较
  34. Mat input = imread("./image/img1.jpg");
  35. Mat target = imread("./image/img2.jpg");
  36. // 通过不同方法比较图像相似性
  37. test_one<AverageHash>("AverageHash", input, target);
  38. test_one<PHash>("PHash", input, target);
  39. test_one<MarrHildrethHash>("MarrHildrethHash", input, target);
  40. test_one<RadialVarianceHash>("RadialVarianceHash", input, target);
  41. test_one<BlockMeanHash>("BlockMeanHash", input, target);
  42. test_one<ColorMomentHash>("ColorMomentHash", input, target);
  43. system("pause");
  44. return 0;
  45. }

Python

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Thu Aug 27 19:03:21 2020
  4. @author: luohenyueji
  5. """
  6. import cv2
  7. def test_one(title, a, b):
  8. # 创建类
  9. if "AverageHash" == title:
  10. hashFun = cv2.img_hash.AverageHash_create()
  11. elif "PHash" == title:
  12. hashFun = cv2.img_hash.PHash_create()
  13. elif "MarrHildrethHash" == title:
  14. hashFun = cv2.img_hash.MarrHildrethHash_create()
  15. elif "RadialVarianceHash" == title:
  16. hashFun = cv2.img_hash.RadialVarianceHash_create()
  17. elif "BlockMeanHash" == title:
  18. hashFun = cv2.img_hash.BlockMeanHash_create()
  19. elif "ColorMomentHash" == title:
  20. hashFun = cv2.img_hash.ColorMomentHash_create()
  21. tick = cv2.TickMeter()
  22. print("=== " + title + " ===")
  23. tick.reset()
  24. tick.start()
  25. # # 计算图a的哈希值
  26. hashA = hashFun.compute(a)
  27. tick.stop()
  28. print("compute1: " + str(tick.getTimeMilli()) + " ms")
  29. tick.reset()
  30. tick.start()
  31. # 计算图b的哈希值
  32. hashB = hashFun.compute(b)
  33. tick.stop()
  34. print("compute2: " + str(tick.getTimeMilli()) + " ms")
  35. # 比较两张图像哈希值的距离
  36. print("compare: " + str(hashFun.compare(hashA, hashB)))
  37. def main():
  38. inputImg = cv2.imread("./image/img1.jpg")
  39. targetImg = cv2.imread("./image/img2.jpg")
  40. if inputImg is None or targetImg is None:
  41. print("check input image")
  42. return
  43. test_one("AverageHash", inputImg, targetImg)
  44. test_one("PHash", inputImg, targetImg)
  45. test_one("MarrHildrethHash", inputImg, targetImg)
  46. test_one("RadialVarianceHash", inputImg, targetImg)
  47. test_one("BlockMeanHash", inputImg, targetImg)
  48. test_one("ColorMomentHash", inputImg, targetImg)
  49. if __name__ == '__main__':
  50. 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. 魔改xxl-job,彻底告别手动配置任务!

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是Hydra. xxl-job是一款非常优秀的任务调度中间件,轻量级.使用简单.支持分布式等优点,让它广泛应用在我们的项目中,解 ...

  2. Vue学习之--------深入理解Vuex之模块化编码(2022/9/4)

    在以下文章的基础上 1.深入理解Vuex.原理详解.实战应用:https://blog.csdn.net/weixin_43304253/article/details/126651368 2.深入理 ...

  3. 使用doctest代码测试和Sphinx自动生成文档

    python代码测试并自动生成文档 Tips:两大工具:doctest--单元测试.Sphinx--自动生成文档 1.doctest doctest是python自带的一个模块.doctest有两种使 ...

  4. 7.Gitee导入其他远程托管中心仓库

    的码云是开源中国推出的基于Git的代码托管服务中心 网址是https://gitee.com/,使用方式跟github一致,并且是一个中文网站 码云的使用配置方式与github一致,码云支持导入git ...

  5. vue3+element-plus+登录逻辑token+环境搭建

    vue3+element-plus+登录逻辑token环境搭建 安装脚手架工具 1 npm i @vue/cli@4.5.13 -g 验证是否安装成功 1 vue -V # 输出 @vue/cli 4 ...

  6. docker常用配置以及命令

    1. Docker基本概念 1.1 什么是 docker hub DockHub是一个仓库 https://hub.docker.com/ 仓库是集中存放镜像文件的场所 仓库分为公开仓库(Public ...

  7. VUE2 学习(推荐直接学习VUE3)

    概念区分: 前端框架:Vue.AngularJS.React 界面模板:Bootstrap.easyUI.adminlte 学习地址: b站:https://space.bilibili.com/39 ...

  8. WPF之MVVM实践中的Command与CommandParameter

    先记录一下,方便以后复习. https://www.cnblogs.com/babietongtianta/p/3474101.html

  9. mindxdl--common--web_cert_utils.go

    // Copyright (c) 2021. Huawei Technologies Co., Ltd. All rights reserved.// Package common this file ...

  10. leetcode学习记录2.13

    [13] 罗马数字转整数 import java.util.HashMap; import java.util.Map; /* * * [13] 罗马数字转整数 * * https://leetcod ...