C++ 视觉开发 六.特征值匹配

 以图片识别匹配的案例来分析特征值检测与匹配方法。

目录

一.感知哈希算法(Perceptual Hash Algorithm)

二.特征值检测步骤

1.减小尺寸

2.简化色彩

3.计算像素点均值

4.构造感知哈希位信息

5.构造一维感知哈希值

三.实现程序

1.感知哈希值计算函数

2.计算距离函数

3.计算图像库内所有图像的哈希值

4.比较距离,输出结果

5.完整程序


一.感知哈希算法(Perceptual Hash Algorithm)

哈希值是数据的指纹,是数据的独一无二的特征值。任何微小的差异都会导致两个数据的哈希值完全不同。与传统哈希值的不同之处在于,感知哈希值可以对不同数据对应的哈希值进行比较,进而可以判断两个数据之间的相似性。也就是说,借助感知哈希能够实现对数据的比较。
一般来说,相似的图像即使在尺度、纵横比不同及颜色(对比度、亮度等)存在微小差异的情况下,仍然会具有相似的感知哈希值。这个属性为使用感知哈希值进行图像检索提供了理论基础。

流程图 

由图可知,基本流程是先提取所有图像的特征值(感知哈希值),然后比较检索图像和图像库中所有图像的特征值,和检索图像差值最小的图像库中的图像就是检索结果。针对图中的利用检索图像寻找相似图像,图像库第2行第1幅图像的特征值489与检索图像的特征值462的差为27,是所有的差值中最小的,因此该图像就是检索结果。

从上述分析可以看出,检索的关键点在于找到特征值,并计算距离。这涉及的图像处理领域中的三个关键问题:
(1)提取哪些特征:图像有很多特征,要找到有用的特征,这是关键一步。
(2)如何量化特征:简单来说就是用数字来表示特征,让特征变为可计算的。
(3)如何计算距离:有很多种不同的计算距离的方式,从中选择一种合适的即可,本章将使用汉明距离来衡量距离。

二.特征值检测步骤

1.减小尺寸

将图像减小至8像素x8像素大小,总计64像素。减小尺寸的作用在于去除图像内的高频和细节信息,仅保留图像中最重要的结构、亮度等信息。尺寸减小后的图像看起来是非常模糊的。该步骤不需要考虑纵横比等问题,无论原始图像如何,一律处理为8像素x8像素大小。这样的处理,能够让算法适用于各种尺度的图像。
需要注意的是,该方法虽然简单,但是在学习深度学习方法后会发现,该方法与深度学习方法提取图像特征的基本思路是一致的。当然,本章的特征提取相对比较粗糙,而深度学习方法提取的特征更具有抽象性。

#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;int main() {cv::Mat img = cv::imread("lena.bmp");if (img.empty()){cerr<<"error"<<endl;return -1;}cv::Size size(8, 8);cv::Mat rst;cv::resize(img, rst, size);std::cout << "img.shape = (" << img.rows << ", " << img.cols << ", " << img.channels() << ")" << std::endl;std::cout << "rst.shape = (" << rst.rows << ", " << rst.cols << ", " << rst.channels() << ")" << std::endl;cv::imshow("img", img);cv::imshow("rst", rst);cv::waitKey();cv::destroyAllWindows();return 0;
}

2.简化色彩

色彩空间转换到灰度空间

    cv::Mat gray;cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);

3.计算像素点均值

计算图像中64个像素点的均值M。

 double m = cv::mean(img)[0];

对于灰度图像,只需使用 [0] 即可。

4.构造感知哈希位信息

依次将每个像素点的像素值与均值M进行比较。像素值大于或等于均值M的像素点记为1;否则,记为0。

    cv::Mat r = img > m;// 打印特征值矩阵std::cout << "特征值:\n" << r << std::endl;

注意,在python中可以使用astype(int)将逻辑值转换为整数值,C++中的 cv::Mat 没有直接的 astype(int) 方法,打印布尔矩阵时会显示 0255。如果需要将布尔矩阵转换为整数矩阵(0 和 1),可以使用以下代码: 

cv::Mat r_int;
r.convertTo(r_int, CV_8U, 1.0 / 255.0);
std::cout << "特征值:\n" << r_int << std::endl;

5.构造一维感知哈希值

将上一步得到的64个1或0组合在一起,得到当前图像的一维感知哈希值表。

 cv::Mat r1 = r_int.reshape(1, 1);

需要说明的是,64个像素值的组合顺序并不重要,但需针对所有图像采用相同的顺序组合。通常情况采用从左到右从上到下的顺序拼接所有特征值。经过上述处理得到的感知哈希值可以看作图像的指纹。在图像被缩放或纵横比发生变化时,它的感知哈希值不会改变。通常情况下,调整图像的亮度或对比度,甚至改变图像的颜色都不会显著改变感知哈希值。重要的是,这种提取感知哈希值的方法的速度非常快。
综上所述,感知哈希值提取过程示意图如图所示:

按照上述方式分别提取检索图像图像库内图像的感知哈希值。
依次将检索图像的感知哈希值与图像库内图像的感知哈希值进行比较,距离最小的图像就是检索结果。
比较时,可以采用汉明距离来衡量不同图像之间的距离。具体为,将两幅图像的感知哈希值不同的位个数作为二者的距离。距离为0,表示二者可能是非常相似的图像(或同一幅图像的变体);距离较小,表示二者之间可能有一些不同,但它们十分相似;距离较大,表示二者的差距较大;距离非常大,表示二者可能是完全不同的两幅图像。 

三.实现程序

1.感知哈希值计算函数

为了方便,将上述特征值检测步骤封装成函数:注意返回类型为cv::Mat

cv::Mat getHash(cv::Mat& img) {// 缩放图像到 8x8cv::Size size(8, 8);cv::Mat rst;cv::resize(img, rst, size);cout << "Resized image:\n" << rst << endl;// 转换为灰度图像cv::Mat gray;cv::cvtColor(rst, gray, cv::COLOR_BGR2GRAY);cout << "Grayscale image:\n" << gray << endl;// 计算图像的平均值cv::Scalar mean_value = cv::mean(gray);double m = mean_value[0];cout << "Mean value: " << m << endl;// 进行比较,并转换为整数矩阵(0和1)cv::Mat r = (gray > m);cv::Mat r_int;r.convertTo(r_int, CV_8U, 1.0 / 255.0);cout << "Binary image:\n" << r_int << endl;// 转换为一维数组cv::Mat r1 = r_int.reshape(1, 1); // 展平为1行return r1;
}

2.计算距离函数

比较感知哈希值时,可以采用汉明距离来衡量二者的距离,具体为将两个感知哈希值位值不同的位个数作为二者的距离。假设感知哈希值test为“1011”:
感知哈希值x1值为“1010”,test1与x1只有第4位上的值不同,因此二者的距离为1。

可以借助OpenCV中的按位异或运算函数cv::bitwise xor()来计算两个感知哈希值之间的距离在进行按位异或运算时,若运算数相同则返回0,若运算数不同则返回1。
按位异或运算是将两个数值按二进制位逐位进行异或运算。

将上述运算结果逐位相加求和,得到的值就是两个感知哈希值的汉明距离。

例如:cv::bitwise xor(“1011”,“1010”,r)的返回值为“0001”,将“0001”逐位相加求和“0+0+0+1=1”。因此,“1011”和“1010”的距离为1。

int hanming(cv::Mat& h1, cv::Mat& h2) {cv::Mat r;cv::bitwise_xor(h1, h2, r);int h = cv::sum(r)[0];return h;
}

3.计算图像库内所有图像的哈希值

为了高效读取文件,利用cv::glob函数设计findImages函数来实现读取指定文件夹下所有图像文件。

void findImages(vector<string>& images, const vector<string>& exts, const string& folder) {for (const auto& ext : exts) {vector<cv::String> files;glob(folder + "/*." + ext, files, false);images.insert(images.end(), files.begin(), files.end());}
}

在主函数中调用并计算其哈希值:

    // 计算检索图像的哈希值cv::Mat h = getHash(img);cout << "检索图像的感知哈希值为:\n" << h << endl;// 获取图像文件列表vector<string> images;vector<string> exts = { "jpg", "jpeg", "JPG", "JPEG", "gif", "GIF", "png", "PNG", "bmp", "BMP" };findImages(images, exts, "image");vector<pair<string, cv::Mat>> seq;// 读取图像并计算哈希值for (const auto& f : images) {cv::Mat I = cv::imread(f, cv::IMREAD_COLOR);if (I.empty()) {cerr << "无法读取图像文件: " << f << endl;continue;}seq.push_back(make_pair(f, getHash(I)));}

4.比较距离,输出结果

    // 计算检索图像与图像库内所有图像距离,将最小距离作为检索结果vector<pair<int, string>> distance;for (const auto& x : seq) {distance.push_back(make_pair(hamming(h, x.second), x.first)); // 每次添加(距离值,图像名称)}// 排序,把距离最小的放在最前面sort(distance.begin(), distance.end());// 打印最相似的三个图像cout << "最相似的图像:\n";for (int i = 0; i < 3 && i < distance.size(); ++i) {cout << distance[i].second << " with distance: " << distance[i].first << endl;}// 显示最相似的三个图像cv::Mat r1 = cv::imread(distance[0].second);cv::Mat r2 = cv::imread(distance[1].second);cv::Mat r3 = cv::imread(distance[2].second);// 使用 OpenCV 显示结果图像cv::imshow("Original", img);cv::imshow("Most similar 1", r1);cv::imshow("Most similar 2", r2);cv::imshow("Most similar 3", r3);cv::waitKey(0);cv::destroyAllWindows();

5.完整程序

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>using namespace std;
using namespace cv;// 提取感知哈希值函数
cv::Mat getHash(cv::Mat& img) {// 缩放图像到 8x8cv::Size size(8, 8);cv::Mat rst;cv::resize(img, rst, size);// 转换为灰度图像cv::Mat gray;cv::cvtColor(rst, gray, cv::COLOR_BGR2GRAY);// 计算图像的平均值cv::Scalar mean_value = cv::mean(gray);double m = mean_value[0];// 进行比较,并转换为整数矩阵(0和1)cv::Mat r = (gray > m);cv::Mat r_int;r.convertTo(r_int, CV_8U, 1.0 / 255.0);// 转换为一维数组cv::Mat r1 = r_int.reshape(1, 1); // 展平为1行return r1;
}// 计算汉明距离函数
int hamming(const cv::Mat& h1, const cv::Mat& h2) {cv::Mat r;cv::bitwise_xor(h1, h2, r);int h = cv::sum(r)[0];return h;
}// 查找指定文件夹中的图像文件
void findImages(vector<string>& images, const vector<string>& exts, const string& folder) {for (const auto& ext : exts) {vector<cv::String> files;glob(folder + "/*." + ext, files, false);images.insert(images.end(), files.begin(), files.end());}
}int main() {// 读取检索图像cv::Mat img = cv::imread("apple.jpg");if (img.empty()) {std::cerr << "无法读取图像文件: apple.jpg" << std::endl;return -1;}// 计算检索图像的哈希值cv::Mat h = getHash(img);cout << "检索图像的感知哈希值为:\n" << h << endl;// 获取图像文件列表vector<string> images;vector<string> exts = { "jpg", "jpeg", "JPG", "JPEG", "gif", "GIF", "png", "PNG", "bmp", "BMP" };findImages(images, exts, "image");vector<pair<string, cv::Mat>> seq;// 读取图像并计算哈希值for (const auto& f : images) {cv::Mat I = cv::imread(f, cv::IMREAD_COLOR);if (I.empty()) {cerr << "无法读取图像文件: " << f << endl;continue;}seq.push_back(make_pair(f, getHash(I)));}// 计算检索图像与图像库内所有图像距离,将最小距离作为检索结果vector<pair<int, string>> distance;for (const auto& x : seq) {distance.push_back(make_pair(hamming(h, x.second), x.first)); // 每次添加(距离值,图像名称)}// 排序,把距离最小的放在最前面sort(distance.begin(), distance.end());// 打印最相似的三个图像cout << "最相似的图像:\n";for (int i = 0; i < 3 && i < distance.size(); ++i) {cout << distance[i].second << " with distance: " << distance[i].first << endl;}// 显示最相似的三个图像cv::Mat r1 = cv::imread(distance[0].second);cv::Mat r2 = cv::imread(distance[1].second);cv::Mat r3 = cv::imread(distance[2].second);// 使用 OpenCV 显示结果图像cv::imshow("Original", img);cv::imshow("Most similar 1", r1);cv::imshow("Most similar 2", r2);cv::imshow("Most similar 3", r3);cv::waitKey(0);cv::destroyAllWindows();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/370106.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CTF入门知识点

CTF知识点 md5函数 <?php$a 123;echo md5($a,true); ?> 括号中true显示输出二进制 替换成false显示输出十六进制绕过 ffifdyop 这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c&#xff0c;这个字符串前几位刚好是 or 6 而 Mysql 刚好又会把 …

分支与循环

目录 1. if语句 1&#xff09;if 2) else 3&#xff09;分支中包含多条语句 4&#xff09;if嵌套 2.关系操作符 3.条件操作符 4.逻辑操作符&#xff1a;&& || ! 1) 逻辑取反运算符 !​编辑 2 与运算符​编辑 3) 或运算符​编辑 4) 闰年的判断 5) 短路 …

一行代码用git新建分支

1.在本地创建分支 dev git branch dev2.切换分支 git checkout devwebstorm操作如下&#xff1a; 3.推送新分支到远程 git push --set-upstream origin 分支名webstorm操作如下&#xff1a;提交代码的时候会自动推送到远程 4.到git上面可以看看刚刚推送的内容 dev多推送…

MacOS miniconda安装方法

打开macos “终端” 应用 执行命令 mkdir -p ~/miniconda3curl https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-MacOSX-arm64.sh -o ~/miniconda3/miniconda.shbash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3rm -rf ~/miniconda3/mini…

Java项目:基于SSM框架实现的智慧城市实验室管理系统分前后台【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的智慧城市实验室管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单…

golang与以太坊交互

文章目录 golang与以太坊交互什么是go-ethereum与节点交互前的准备使用golang与以太坊区块链交互查询账户的余额使用golang生成以太坊账户使用golang生成以太坊钱包使用golang在账户之间转移eth安装使用solc和abigen生成bin和abi文件生成go文件使用golang在测试网上部署智能合约…

RK3588 Android12实现UVC输出功能详解

首先需要在相关部分添加uvc的功能&#xff0c;这里参考一下&#xff1a;rockchip rk3588添加uvc及uvc,adb的复合设备_uvc.gs6-CSDN博客 setprop sys.usb.config none;setprop sys.usb.config uvc 或者setprop sys.usb.config none;setprop sys.usb.config uvc,adb 使rk3588 进…

一款强大且免费开源的多连接数据库管理工具

大家好&#xff0c;今天给大家分享一款免费开源的跨平台数据库管理工具DbGate。 DbGate是一款免费开源的跨平台数据库管理工具&#xff0c;支持多种数据库&#xff0c;包括MySQL、PostgreSQL、SQL Server、MongoDB、SQLite等。它可以在Windows、Linux、Mac操作系统上运行&#…

【HTML入门】第二课 - head标签下的常见表情们

目录 1 本节概要 2 head下的常见标签 2.1 网页编码设置 2.2 网页的标题 2.3 样式标签 3 head标签的内容不会显示到网页上 4 查看网页源代码 1 本节概要 上一节&#xff0c;我们说了HTML网页最基本的框架标签&#xff0c;说到标签分为head头部和body身体部分。这一小节呢…

架构师学习理解和总结

1.架构设计理念 2.架构方法论 2.1需求分析 2.1.1常见需求层次 2.1.2 常见需求结果 2.1.3 需求与架构关系 2.2 领域分析 2.3 关键需求 2.4 概念架构设计 2.5 细化架构设计 2.6 架构设计验证 3.架构设计工具 3.1 DDD领域建模 3.2 41视图分析法 3.3 UML设计工具 4.架构师知…

全能PDF工具集 -- PDF Shaper Professional v14.3 特别版

软件简介 PDF Shaper是一款功能强大的PDF工具集&#xff0c;它提供了一系列用于处理PDF文档的工具。这款软件使用户能够轻松地转换、分割、合并、提取页面以及旋转和加密PDF文件。PDF Shaper的界面简洁直观&#xff0c;使得即使是新手用户也能快速上手。它支持广泛的功能&…

Linux系统(CentOS)安装Mysql5.7.x

安装准备&#xff1a; Linux系统(CentOS)添加防火墙、iptables的安装和配置 请访问地址&#xff1a;https://blog.csdn.net/esqabc/article/details/140209894 1&#xff0c;下载mysql安装文件&#xff08;mysql-5.7.44为例&#xff09; 选择Linux通用版本64位&#xff08;L…

hdu物联网硬件实验1 小灯闪烁

物联网硬件基础实验报告 学院 班级 学号 姓名 日期 成绩 实验题目 配置环境小灯 实验目的 配置环境以及小灯闪烁 硬件原理 无 关键代码及注释 /* Blink The basic Energia example. Turns on an LED on for one second, then off for one sec…

网络安全领域国标分类汇总大全V1.0版:共计425份标准文档,全部可免费下载

《网络安全法》、《数据安全法》、《个人信息保护法》落地实施需要大量国家标准的支撑&#xff0c;博主耗时三周时间&#xff0c;吐血整理了从1999年至今相关的所有涉及安全的国家标准&#xff0c;梳理出《网络安全领域国标分类汇总大全V1.0版》&#xff0c;共计 425 项现行标准…

Cartographer前后端梳理

0. 简介 最近在研究整个SLAM框架的改进处&#xff0c;想着能不能从Cartographer中找到一些亮点可以用于参考。所以这一篇博客希望能够梳理好Cartographer前后端优化&#xff0c;并从中得到一些启发。carto整体是graph-based框架&#xff0c;前端是scan-map匹配&#xff0c;后端…

【笔记】记一次在linux上通过在线安装mysql报错 CentOS 7 的官方镜像已经不再可用的解决方法+mysql配置

报错&#xff08;恨恨恨恨恨恨恨&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff09;&#xff1a; [rootlocalhost ~]# sudo yum install mysql-server 已加载插件&#xff1a;fastestmirror, langpacks Determining fastest mirrors Could not retrie…

JMeter--定时执行的方法

原文网址&#xff1a;JMeter--定时执行的方法_IT利刃出鞘的博客-CSDN博客 简介 本文介绍JMeter如何使用定时器定时执行测试任务。 Java技术星球&#xff1a;way2j.com 方法 第一步&#xff1a;新建定时器 右键测试任务> Add > Timer> Constant Timer 如下图所示…

NTP协议格式解析

1. NTP时间戳格式 SNTP使用在RFC 1305 及其以前的版本所描述标准NTP时间戳的格式。与因特网标准标准一致&#xff0c; NTP 数据被指定为整数或定点小数&#xff0c;位以big-endian风格从左边0位或者高位计数。除非不这样指定&#xff0c;全部数量都将设成unsigned的类型&#…

加盖骑缝章软件、可以给PDF软件加盖自己的骑缝章

加盖骑缝章的软件多种多样&#xff0c;尤其是针对PDF文件&#xff0c;有多种软件可以实现给PDF文件加盖自己的骑缝章。以下是一些常用的软件及其特点&#xff1a; 1. Adobe Acrobat Pro DC 特点&#xff1a; 多功能PDF编辑&#xff1a;Adobe Acrobat Pro DC是一款功能强大的…

【Kubernetes】Pod 资源调度之亲和性调度

Pod 资源调度之亲和性调度 1.Node 亲和性调度1.1 Node 硬亲和性1.2 Node 软亲和性 2.Pod 亲和性调度2.1 Pod 硬亲和2.2 Pod 软亲和2.3 Pod 反亲和 Kubernetes 的 默认调度器 以 预选、优选、选定机制 完成将每个新的 Pod 资源绑定至为其选出的目标节点上&#xff0c;不过&#…