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) {
    // 缩放图像到 8x8
    cv::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) {
    // 缩放图像到 8x8
    cv::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.mfbz.cn/a/777497.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SCI二区TOP|麋鹿群优化算法: 一种新颖的受自然启发的元启发式算法

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;SO Oladejo受到麋鹿群的繁殖过程启发&#xff0c;提出了麋鹿群优化算法&#xff08;Elk herd optimizer, EHO&#xff09;。 2.算法原理 2.1算法思想 EHO灵感来自麋鹿…

vue2 data内对象引用另一个data对象无法使用this的解决办法

背景&#xff1a;data内有一复杂对象&#xff0c;并且内部一属性经常修改&#xff0c;每次修改的话属性.属性会很长&#xff0c;所以希望引用另一简单对象&#xff0c;但data内this用不了。(集合数组是地址引用&#xff0c;基本数据类型这么操作没意义) 如&#xff1a; 解决办法…

字节码编程javassist之定义方法和返回值

写在前面 源码 。 本文看下如何使用javassist来定义方法和返回值。 1&#xff1a;源码 package com.dahuyou.javassist.generateFieldAndMethod;import javassist.*;import java.lang.reflect.Method;public class JustDoIt222 {public static void main(String[] args) thr…

数值分析笔记(五)线性方程组解法

三角分解法 A的杜利特分解公式如下&#xff1a; u 1 j a 1 j ( j 1 , 2 , ⋯ , n ) , l i 1 a i 1 / u 11 ( i 2 , 3 , ⋯ , n ) , u k j a k j − ∑ m 1 k − 1 l b m u m j ⇒ a k j ( j k , k 1 , ⋯ , n ) , l i k ( a i k − ∑ m 1 k − 1 l i n u m k ) /…

苹果电脑能玩赛博朋克2077吗 如何在mac上运行赛博朋克2077 crossover能玩什么游戏

各位喜欢赛博朋克风的一定不能错过《赛博朋克2077》。那么《赛博朋克2077》是一款什么样的游戏&#xff1f;《赛博朋克2077》在苹果电脑上可以运行吗&#xff1f;一起来看看介绍吧。 一、《赛博朋克2077》是一款什么样的游戏&#xff1f; 《赛博朋克2077》是一款由CD Projekt …

[激光原理与应用-98]:南京科耐激光-激光焊接-焊中检测-智能制程监测系统IPM介绍 - 2 - 什么是激光器焊接? 常见的激光焊接技术详解

目录 一、什么是激光焊接 1.1 概述 1.2 激光焊接的优点 二、激光焊接的应用 2.1 哪些场合必须使用激光焊接 1. 汽车制造业 2. 航空航天领域 3. 电子行业&#xff1a;消费类电子3C 4. 医疗器械制造 5. 新能源锂电池行业 6. 其他领域 三、激光焊接的分类 3.1 按焊接…

【SpringBoot】SpringBoot自动装配原理

在上一篇文章中&#xff0c;讲述了 SpringBoot核心启动流程源码解析 其中&#xff0c;主要是构造方法和run方法的处理&#xff0c;本篇接着准备上下文环境后续&#xff0c;讲述是如何将springboot是如何完成自动装配&#xff0c;主线其实就是 什么时候完成 对主类的加载&#…

Spring Boot基础篇

快速上手 SpringBoot是由Pivotal团队提高的全新框架&#xff0c;其设计目的是用来简化Spring应用的初始化搭建以及开发过程 入门案例 在Idea创建 创建时要选择Spring Initializr。 Server URL为要连接的网站&#xff0c;默认为官网start.spring.io&#xff08;访问速度慢&…

Node.js 生成vue组件

在项目根目录下创建 create.js /*** 脚本生成vue组件* 主要是利用node自带的fs模块操作文件的写入* ===========================================* 准备步骤:* 1.输入作者名* 2.输入文件名* 3.输入菜单名* 4.输入文件地址* ============================================* 操…

CTS单测某个模块和测试项

1 &#xff0c;测试单个模块命令 run cts -m <模块名> 比如&#xff1a;run cts -m CtsUsbTests模块名可以从测试报告中看&#xff0c;如下&#xff1a; 2&#xff0c; 测试单个测试项 run cts -m <模块名> -t <test_name> 比如&#xff1a;run cts -m ru…

【新能源时代!看大模型(LLMs)如何助力汽车自动驾驶!】

文末有福利&#xff01; 引言 本文主要介绍大模型(LLMs)如何助力汽车自动驾驶&#xff0c;简单来说&#xff0c;作者首先带大家了解大模型的工作模式&#xff0c;然后介绍了自动驾驶大模型的3大应用场景&#xff0c;最后指出自动驾驶大模型将会是未来的发展趋势&#xff0c;只…

浏览器怎么抓包?Wireshark详细教程奉上!

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

Android 添加LBS辅助定位

1.软件需求&#xff1a; 某Android设备没有sim卡但其支持定位&#xff0c;客户需求为在已有的Android中添加LBS网络定位&#xff0c;用以辅助gps定位。 2.思路分析 首先看到这个需求笔者是比较懵逼的&#xff0c;秉持着客户是上帝的原则&#xff0c;笔者首先先了解了一下什么…

【深海王国】小学生都能玩的语音模块?ASRPRO打造你的第一个智能语音助手(7)

Hi~ (o^^o)♪, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~ 辛勤工作的你今天也辛苦啦(/≧ω) 今天大都督继续为大家带来系列——小学生都能玩的语音模块&#xff0c;帮你一周内快速学会语音模块的使用方式&#xff0c;打造一个可用于智能家居、物联网领域的语音助…

通过SDK使用百度智能云的图像生成模型SDXL

登录进入百度智能云控制台&#xff0c;在模型广场按照图像生成类别进行筛选&#xff0c;可以找到Stable-Diffusion-XL模型。点击Stable-Diffusion-XL模型的API文档后在弹出的新页面下拉可以找到SDK调用的说明。 import qianfandef sdxl(file: str, prompt: str, steps: int 2…

商品分页,商品模糊查询

一、商品分页 引入分页 定义分页主件的参数 在请求url上拼接参数 定义改变当前页码后触发的事件&#xff0c;把当前页码的值给到分页表单&#xff0c;重新查询 二、商品查询&#xff08;以商品的名称查询name为例&#xff09; 引入elementplus的from表单组件 定义一个FormData…

Spring源码十二:事件发布源码跟踪

上一篇我们在Spring源码十一&#xff1a;事件驱动中&#xff0c;介绍了spring refresh方法的initMessageSource方法与initApplicationEventMulticaster方法&#xff0c;举了一个简单的例子进行简单的使用的Spring为我们提供的事件驱动发布的示例。这一篇我们将继续跟踪源码&…

创建React项目

使用 create-react-app快速搭建开发环境 create-react-app 是一个快速创建React开发环境的工具&#xff0c;底层由Webpack构建&#xff0c;封装了配置细节&#xff0c;开箱即用。 安装npx npx是一个由Node.js官方提供的用于快速执行npm包中的可执行文件的工具&#xff0c;np…

如何给gitlab其他访问者创建账号并增加权限

嗨&#xff0c;今天创建了项目之后&#xff0c;我想把项目链接发送给其他人&#xff0c;让他下载这个项目&#xff0c;结果发现对方打开显示登录的界面&#xff0c;没错&#xff0c;他要想使用这个git下载项目&#xff0c;首先他的有一个git账号 接下来我找有权限的相关人员给他…

C语言之常用内存函数以及模拟实现

目录 前言 一、memcpy的使用和模拟实现 二、memmove的使用和模拟实现 三、memset的使用和模拟实现 四、memcmp的使用和模拟实现 总结 前言 本文主要讲述C语言中常用的内存函数&#xff1a;memcpy、memmove、memset、memcmp。内容不多&#xff0c;除了了解如何使用&#x…