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.mzph.cn/pingmian/41420.shtml

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

相关文章

vscode 生成项目目录结构 directory-tree 实用教程

1. 安装插件 directory-tree 有中文介绍&#xff0c;极其友好&#xff01; 2. 用 vscode 打开目标项目 3. 快捷键 Ctrl Shift p&#xff0c;输入 Directory Tree 后回车 会在 README.md 文件的底部生成项目目录&#xff08;若项目中没有 README.md 文件&#xff0c;则会自动创…

用NanoID换掉 UUID,好处是?【送源码】

当我们在分布式环境中存储一些数据的时候&#xff0c;不得不面对的一个选择&#xff0c;就是ID生成器。 使用一个唯一的字符串&#xff0c;来标识一条完整的记录。 这时候&#xff0c;不能使用md5或者sha1来对整个记录做摘要&#xff0c;因为我们后续还要改动这个记录。也不能…

【C++】日期类

鼠鼠实现了一个日期类&#xff0c;用来练习印证前几篇博客介绍的内容&#xff01;&#xff01; 目录 1.日期类的定义 2.得到某年某月的天数 3.检查日期是否合法 4.&#xff08;全缺省&#xff09;构造函数 5.拷贝构造函数 6.析构函数 7.赋值运算符重载 8.>运算符重…

高效PD,稳定传输,LDR6023CQ芯片,打造顶级直播体验

在当今市场&#xff0c;一款备受瞩目的直播神器——无线领夹麦克风&#xff08;MIC&#xff09;&#xff0c;正风靡于网红直播、在线教育、专业采访、高清视频录制及视频会议等多个领域。麦克风&#xff0c;这一昔日课堂上的常见设备&#xff0c;已随着科技的飞速发展而焕然一新…

数字信号处理及MATLAB仿真(2)——离散系统

上回书说到如何来编写一些简单的离散时间序列&#xff0c;今天咱们就来谈谈一些关于常系数差分方程的操作吧。 说到这里咱们对于常系数差分方程可能最关心的就是怎么去求解了。 其中最关键的部分就是filter函数&#xff0c;可以用来计算系统在输入信号为x的输出信号y。大家学过…

ASUS/华硕枪神5 G533Q G733Q系列 原厂win10系统 工厂文件 带F12 ASUS Recovery恢复

华硕工厂文件恢复系统 &#xff0c;安装结束后带隐藏分区&#xff0c;一键恢复&#xff0c;以及机器所有驱动软件。 系统版本&#xff1a;Windows10 原厂系统下载网址&#xff1a;http://www.bioxt.cn 需准备一个20G以上u盘进行恢复 请注意&#xff1a;仅支持以上型号专用…

JAVA--JSON转换工具类

JSON转换工具类 import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackso…

NoSQL 非关系型数据库 Redis 的使用:

redis是基于内存型的NoSQL 非关系型数据库&#xff0c;本内容只针对有基础的小伙伴&#xff0c; 因为楼主不会做更多的解释&#xff0c;而是记录更多的技术接口使用&#xff0c;毕竟楼主不是做教学的&#xff0c;没有教学经验。 关于redis的介绍请自行搜索查阅。 使用redis数据…

h5 video 标签播放经过 java 使用 ws.schild( jave、ffmpeg ) 压缩后的 mp4 视频只有声音无画面的问题排查记录

1. 引入 ws.schild MAVEN 依赖&#xff1a; <dependency><groupId>ws.schild</groupId><artifactId>jave-all-deps</artifactId><version>3.5.0</version></dependency><dependency><groupId>ws.schild</grou…

idm下载慢怎么回事 idm批量导入下载使用方法

IDM (Internet Download Manager)是一款兼容性大&#xff0c;支持多种语言的下载管理软件&#xff0c;它可以自动检测并下载网页上的内容&#xff0c;这正是这一优点&#xff0c;使得它受到了广大用户的喜爱。在日常使用互联网的过程中&#xff0c;快速下载文件对我们来说非常重…

Victor CMS v1.0 SQL 注入漏洞(CVE-2022-26201)

前言 CVE-2022-26201 是 Victor CMS v1.0 中发现的一个 SQL 注入漏洞。该漏洞允许攻击者通过特制的 SQL 查询注入到应用程序中&#xff0c;从而访问或操作数据库中的数据。以下是详细信息&#xff1a; 漏洞描述&#xff1a; 类型&#xff1a;SQL 注入 (SQL Injection)影响版本…

LLM - 神经网络的训练过程

1. 对于回归问题&#xff0c;用损失函数来计算预测值和真实值的差异&#xff0c;一种常用的公式是如下图所示(Mean Square Error)&#xff0c;如果损失函数的值越小说明神经网络学习越准确&#xff0c;所以神经网络训练目标是减小损失函数的值&#xff0c; 2. 对于分类问题&…

并发、多线程和HTTP连接之间有什么关系?

一、并发的概念 并发是系统同时处理多个任务或事件的能力。在计算中&#xff0c;这意味着系统能够在同一时间段内处理多个任务&#xff0c;而不是严格按照顺序一个接一个地执行它们。并发提高了系统的效率和资源利用率&#xff0c;从而更好地满足用户的需求。在现代应用程序中&…

SwiftUI中List的liststyle样式及使用详解添加、移动、删除、自定义滑动

SwiftUI中的List可是个好东西&#xff0c;它用于显示可滚动列表的视图容器&#xff0c;类似于UITableView。在List中可以显示静态或动态的数据&#xff0c;并支持垂直滚动。List是一个数据驱动的视图&#xff0c;当数据发生变化时&#xff0c;列表会自动更新。针对List&#xf…

神领物流项目第一天

文章目录 聚焦快递领域首先第一个是验证码模块流程登录接口权限管家 聚焦快递领域 首先第一个是验证码模块流程 首先生成验证码的流程 可以使用工具类去生成验证码 LineCaptcha lineCaptcha CaptchaUtil.createLineCaptcha(160, 60, 4, 26);// 获取值然后存入redis中 strin…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(二十二)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 32 节&#xff09; P32《31.通知-基础通知》 基础文本类型通知&#xff1a;briefText 没有用&#xff0c;写了也白写。 长文本类型…

【东奥会计-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

elasticsearch-users和elasticsearch-reset-password介绍

elasticsearch 内置 elastic, kibana, logstash_system,beats_system 共4个用户&#xff0c;用途如下&#xff1a; elastic 账号&#xff1a;内置的超级用户&#xff0c;拥有 superuser 角色。 kibana 账号&#xff1a;用来连接 elasticsearch 并与之通信。Kibana 服务器以该用…

【HICE】基于用户认证的虚拟服务搭建

1.创建特定的内容 --账号与密码&#xff08;需要认证访问&#xff09;【里面】 2.编辑配置1.conf的内容&#xff0c;更新httpd 3.编辑hehe网页&#xff08;外部公开&#xff09; cd /www/ echo hehe > hehe/index.html 4.更改本地hosts和window下的解析 5.浏览器下验证内…

OpenCV基础(1)

目录 安装OpenCV 读取图像 显示图像 cv2.waitKey()函数 cv2.destroyAllWindows()函数 保存图像 读取视频 开启摄像头 图像处理 像素处理 二值图像及灰度图像 彩色图像及通道处理 调整图像大小 感兴趣区域 掩模 掩模基础及构造 掩模作为函数参数 色彩处理 色…