【opencv】dnn示例-person_reid.cpp 人员识别(ReID,Re-Identification)系统

devtools/2024/11/15 4:37:20/

ReID(Re-Identification,即对摄像机视野外的人进行再识别)

f9ead194adfdb30e720ade283ef6cef5.png

d594c9e286ac15fa87e3f84fb95396d0.jpeg

0030_c1_f0056923.jpg

638af75a1984c8bf50ed10b1c6474530.jpeg

0042_c5_f0068994.jpg

186d15dca87d1f2cc30c77dd3ebd897c.jpeg

0056_c8_f0017063.jpg

以上为输出结果:result文件夹下

7140678ac74852f928ccc7ef4e27e897.png

bb29871aeb8a18c2938ac16c17b4f834.png

galleryLIst.txt

f1b1f73036d89a226fd45edb39d3c186.png

676c1be6abac50d39fde5bc7ca9c4cce.png

queryList.txt

模型下载:

8d0a2547f7d87d33484c41c645623bff.png

https://github.com/ReID-Team/ReID_extra_testdata/tree/main
https://drive.google.com/drive/folders/1wFGcuolSzX3_PqNKb4BAV3DNac7tYpc2

6424a37576095e249f7763a2446c2c11.png

这段代码是一个使用OpenCV库实现的人员识别(ReID,Re-Identification)系统。程序的主要功能是用于处理图像数据,根据查询图片(queries)识别并匹配图库中(gallery)的图片

首先,导入所需的头文件,包括标准输入输出(iostream)、文件流(fstream),以及OpenCV库中的图像处理(imgproc)、图像显示(highgui)和深度神经网络dnn)相关的模块。

定义命令行参数,并使用OpenCV的CommandLineParser类来解析输入参数。参数包括模型文件路径、查询图像列表路径、图库图像列表路径、批次大小、输入图像调整大小的高和宽、可视化时显示的最多图库图像数(topk)、可视化结果保存路径、计算后端和目标计算设备的设置。

接下来定义一个cv::reid命名空间,包含一系列函数:

  • preprocess:预处理函数,对读入的图像进行标准化处理,按照给定的均值和标准差进行缩放。

  • normalization:特征归一化函数,将特征向量转换为单位向量。

  • extractFeatures:特征提取函数,从图像列表中批量读取图像,进行预处理后输入神经网络提取特征。

  • getNames:获取图像名单函数,从存储图像路径的文件中读取图像名称。

  • similarity:计算相似度函数,通过点积操作得出两个特征向量的相似度。

  • getTopK:选取TopK函数,对每一个查询特征,计算与所有图库特征的相似度,并排序选取最相似的K个结果。

  • addBorder:添加边框函数,给图像添加边框。

  • drawRankList:绘制排名列表函数,将查询图像和它的TopK相似图库图像绘制在一起进行可视化。

  • visualization:可视化函数,将所有查询结果进行可视化并保存到指定目录。

最后,main函数是程序的入口点,首先解析命令行参数,然后读取查询和图库图像的文件路径。使用readNet函数读取训练好的网络模型,并设置计算后端和设备。提取查询和图库图像的特征,并计算TopK结果。最后,对结果进行可视化和存储。

简单总结,该代码定义了一个基于OpenCV的人员重识别系统,可以读取图像,提取特征,并通过计算相似度来匹配查询图像与图库中的图像,最后可视化输出匹配结果。使用了OpenCV库中的深度学习模块和图像处理模块,以及标准的归一化和相似度计算方法来实现该功能。

// 以下是用于行人重识别(ReID)的基础模型和示例输入的下载地址:
// https://github.com/ReID-Team/ReID_extra_testdata// 行人重识别(ReID)基础模型和示例的作者信息:
// Xing Sun <winfredsun@tencent.com>
// Feng Zheng <zhengf@sustech.edu.cn>
// Xinyang Jiang <sevjiang@tencent.com>
// Fufu Yu <fufuyu@tencent.com>
// Enwei Zhang <miyozhang@tencent.com>// 版权信息
// Copyright (C) 2020-2021, Tencent.
// Copyright (C) 2020-2021, SUSTech.#include <iostream> // 引入IO流库,用于数据输入输出
#include <fstream>  // 引入文件流库,用于文件操作#include <opencv2/imgproc.hpp> // 引入OpenCV图像处理头文件
#include <opencv2/highgui.hpp> // 引入OpenCV GUI头文件
#include <opencv2/dnn.hpp>     // 引入OpenCV深度学习模块头文件using namespace cv;          // 使用cv命名空间
using namespace cv::dnn;     // 使用cv::dnn命名空间// 定义命令行参数解析所需的关键词参数
const char* keys =
"{help    h  |                 | show help message}"  // 帮助信息
"{model   m  |    youtu_reid_baseline_lite.onnx       | network model}"  // 网络模型
"{query_list q |    ReID_extra_testdata/queryList.txt     | list of query images}"  // 查询图像列表
"{gallery_list g |   ReID_extra_testdata/galleryList.txt    | list of gallery images}"  // 画廊图像列表
"{batch_size | 32              | batch size of each inference}"  // 每次推理的批处理大小
"{resize_h   | 256             | resize input to specific height.}"  // 输入图像调整到特定的高度
"{resize_w   | 128             | resize input to specific width.}"  // 输入图像调整到特定的宽度
"{topk k     | 5               | number of gallery images showed in visualization}"  // 可视化展示中画像的数量
"{output_dir |result       | path for visualization(it should be existed)}"  // 可视化结果的保存路径(必须已存在)
"{backend b  | 5               | choose one of computation backends: "
"0: automatically (by default), "  // 计算后端选项:默认自动选择
"1: Halide language (http://halide-lang.org/), "  // Halide语言后端
"2: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "  // 英特尔深度学习推理引擎后端
"3: OpenCV implementation, "  // OpenCV实现后端
"4: VKCOM, "  // VKCOM后端
"5: CUDA }"  // CUDA后端
"{target  t  | 6                | choose one of target computation devices: "
"0: CPU target (by default), "  // 计算设备选项:默认使用CPU
"1: OpenCL, "  // 使用OpenCL
"2: OpenCL fp16 (half-float precision), "  // 使用OpenCL半精度浮点数
"4: Vulkan, "  // 使用Vulkan
"6: CUDA, "  // 使用CUDA
"7: CUDA fp16 (half-float preprocess) }";  // 使用CUDA半精度预处理// OpenCV和重识别命名空间内的处理函数
namespace cv{
namespace reid{// 预处理图像的函数,将图像数据进行归一化处理
static Mat preprocess(const Mat& img)
{const double mean[3] = {0.485, 0.456, 0.406}; // 定义三个通道的均值const double std[3] = {0.229, 0.224, 0.225};  // 定义三个通道的标准差Mat ret = Mat(img.rows, img.cols, CV_32FC3);  // 创建一个新的Mat对象来存放处理后的图像for (int y = 0; y < ret.rows; y ++)          // 遍历图像的行{for (int x = 0; x < ret.cols; x++)       // 遍历图像的列{for (int c = 0; c < 3; c++)          // 遍历图像的通道{// 进行通道值的归一化处理,并将结果存储在新的Mat对象中ret.at<Vec3f>(y,x)[c] = (float)((img.at<Vec3b>(y,x)[c] / 255.0 - mean[2 - c]) / std[2 - c]);}}}return ret; // 返回处理后的图像
}// 特征向量归一化的函数
static std::vector<float> normalization(const std::vector<float>& feature)
{std::vector<float> ret; // 创建一个用于存储归一化后特征的向量float sum = 0.0;        // 初始化求和变量for(int i = 0; i < (int)feature.size(); i++) // 遍历特征向量的元素{sum += feature[i] * feature[i]; // 计算L2范数的平方累积}sum = sqrt(sum); // 计算L2范数for(int i = 0; i < (int)feature.size(); i++) // 再次遍历特征向量元素,进行归一化{ret.push_back(feature[i] / sum); // 将归一化后的值添加到结果向量中}return ret; // 返回归一化后的特征向量
}// 提取特征的函数
static void extractFeatures(const std::vector<std::string>& imglist, Net* net, const int& batch_size, const int& resize_h, const int& resize_w, std::vector<std::vector<float>>& features)
{for(int st = 0; st < (int)imglist.size(); st += batch_size) // 批量处理图像,每次处理batch_size数量的图像{std::vector<Mat> batch; // 存储处理后的图像批次的容器for(int delta = 0; delta < batch_size && st + delta < (int)imglist.size(); delta++) // 遍历当前批次里的所有图像{Mat img = imread(imglist[st + delta]); // 读取一张图像batch.push_back(preprocess(img)); // 对图像进行预处理,并加入到图像批次中}Mat blob = dnn::blobFromImages(batch, 1.0, Size(resize_w, resize_h), Scalar(0.0,0.0,0.0), true, false, CV_32F); // 创建一个4维blob作为网络的输入net->setInput(blob); // 设置网络的输入数据为blobMat out = net->forward(); // 网络前向传播,输出每一张图像的特征for(int i = 0; i < (int)out.size().height; i++) // 遍历每一张图像{std::vector<float> temp_feature; // 存储单张图像的特征for(int j = 0; j < (int)out.size().width; j++) // 遍历提取的特征向量{temp_feature.push_back(out.at<float>(i,j)); // 读取特征并存储}features.push_back(normalization(temp_feature)); // 对提取出的特征进行归一化处理,并存储到features中}}return ; // 结束函数,无需返回值
}static void getNames(const std::string& ImageList, std::vector<std::string>& result)
{std::ifstream img_in(ImageList); // 打开图像列表文件std::string img_name; // 存储单个图像的名称while(img_in >> img_name) // 循环读取图像名称{result.push_back(img_name); // 将图像名称添加到结果向量中}return ; // 结束函数
}static float similarity(const std::vector<float>& feature1, const std::vector<float>& feature2)
{float result = 0.0; // 初始化相似度结果为0.0for(int i = 0; i < (int)feature1.size(); i++) // 遍历特征向量的每一个维度{result += feature1[i] * feature2[i]; // 计算两个特征向量的点积,作为相似度度量}return result; // 返回相似度结果
}// getTopK函数
// 计算查询特征和画廊特征之间的相似性,并获取每个查询特征的前K个最相似画廊特征索引
static void getTopK(const std::vector<std::vector<float>>& queryFeatures, const std::vector<std::vector<float>>& galleryFeatures, const int& topk, std::vector<std::vector<int>>& result)
{for(int i = 0; i < (int)queryFeatures.size(); i++)  // 遍历所有查询特征{std::vector<float> similarityList;  // 存储查询特征与所有画廊特征相似度的列表std::vector<int> index;  // 存储画廊特征索引的列表for(int j = 0; j < (int)galleryFeatures.size(); j++)  // 遍历所有画廊特征{similarityList.push_back(similarity(queryFeatures[i], galleryFeatures[j]));  // 计算并存储相似度index.push_back(j);  // 存储当前索引}sort(index.begin(), index.end(), [&](int x,int y){return similarityList[x] > similarityList[y];});  // 根据相似度对索引进行降序排序std::vector<int> topk_result;  // 存储前K个索引的列表for(int j = 0; j < min(topk, (int)index.size()); j++)  // 选择前K个最相似的特征索引{topk_result.push_back(index[j]);  // 存储排序后的索引}result.push_back(topk_result);  // 将结果存入最终结果列表}return ;
}
// addBorder函数:给图像增加一个固定尺寸的边框
static void addBorder(const Mat& img, const Scalar& color, Mat& result)
{const int bordersize = 5;  // 定义边框大小为5copyMakeBorder(img, result, bordersize, bordersize, bordersize, bordersize, cv::BORDER_CONSTANT, color);  // 对图像img进行边框扩展,每边增加bordersize个像素宽度,边框类型为固定颜色,颜色由参数color指定return ;  // 函数无返回值
}// drawRankList函数:绘制查询结果的排名列表
static void drawRankList(const std::string& queryName, const std::vector<std::string>& galleryImageNames, const std::vector<int>& topk_index, const int& resize_h, const int& resize_w, Mat& result)
{const Size outputSize = Size(resize_w, resize_h);  // 定义输出图像大小Mat q_img = imread(queryName), temp_img;  // 读取查询图像queryName,temp_img为临时变量resize(q_img, temp_img, outputSize);  // 将查询图像调整到定义的大小addBorder(temp_img, Scalar(0,0,0), q_img);  // 给查询图像增加黑色边框putText(q_img, "Query", Point(10, 30), FONT_HERSHEY_COMPLEX, 1.0, Scalar(0,255,0), 2);  // 在查询图像上放置文字"Query"std::vector<Mat> Images;  // 定义Mat类型的向量,用于存放所有图片Images.push_back(q_img);  // 将查询图像加入向量for(int i = 0; i < (int)topk_index.size(); i++)  // 遍历topk_index中的所有索引{Mat g_img = imread(galleryImageNames[topk_index[i]]);  // 读取画廊图像resize(g_img, temp_img, outputSize);  // 将读取的画廊图像调整到定义的大小addBorder(temp_img, Scalar(255,255,255), g_img);  // 给画廊图像增加白色边框putText(g_img, "G" + std::to_string(i), Point(10, 30), FONT_HERSHEY_COMPLEX, 1.0, Scalar(0,255,0), 2);  // 在画廊图像上放置文字(显示排名)Images.push_back(g_img);  // 将画廊图像加入向量}hconcat(Images, result);  // 将所有图像水平拼接成一张图return ;  // 函数无返回值
}// visualization函数:可视化展示查询的排名结果
static void visualization(const std::vector<std::vector<int>>& topk, const std::vector<std::string>& queryImageNames, const std::vector<std::string>& galleryImageNames, const std::string& output_dir, const int& resize_h, const int& resize_w)
{for(int i = 0; i < (int)queryImageNames.size(); i++)  // 遍历所有查询图像名称{Mat img;  // 定义图像变量,用于存放绘制后的排名列表drawRankList(queryImageNames[i], galleryImageNames, topk[i], resize_h, resize_w, img);  // 调用drawRankList函数绘制单个查询图像的排名列表std::string output_path = output_dir + "/" + queryImageNames[i].substr(queryImageNames[i].rfind("/")+1);  // 定义输出路径,生成每张查询图像的排名图像保存位置imwrite(output_path, img);  // 将绘制的排名列表图像写入文件}return ;  // 函数无返回值
}
};  // 结束命名空间// main函数
// 程序入口,解析命令行参数,加载网络模型,提取特征,获取相似性排名,并进行结果可视化
int main(int argc, char** argv)
{CommandLineParser parser(argc, argv, keys);  // 创建命令行参数解析器if (argc == 0 || parser.has("help"))  // 如果没有参数或请求帮助信息{parser.printMessage();  // 打印帮助信息return 0;  // 退出程序}parser = CommandLineParser(argc, argv, keys);  // 重新创建命令行参数解析器(貌似是多余的)parser.about("Use this script to run ReID networks using OpenCV.");  // 关于信息// 从命令行参数中获取网络模型路径、查询图像列表、画廊图像列表等信息const std::string modelPath = parser.get<String>("model");const std::string queryImageList = parser.get<String>("query_list");const std::string galleryImageList = parser.get<String>("gallery_list");const int backend = parser.get<int>("backend");const int target = parser.get<int>("target");const int batch_size = parser.get<int>("batch_size");const int resize_h = parser.get<int>("resize_h");const int resize_w = parser.get<int>("resize_w");const int topk = parser.get<int>("topk");const std::string output_dir= parser.get<String>("output_dir");// 读取图像列表std::vector<std::string> queryImageNames;reid::getNames(queryImageList, queryImageNames);std::vector<std::string> galleryImageNames;reid::getNames(galleryImageList, galleryImageNames);// 加载网络模型,并设置计算后端和目标设备dnn::Net net = dnn::readNet(modelPath);net.setPreferableBackend(backend);net.setPreferableTarget(target);// 提取查询图像特征std::vector<std::vector<float>> queryFeatures;reid::extractFeatures(queryImageNames, &net, batch_size, resize_h, resize_w, queryFeatures);// 提取画廊图像特征std::vector<std::vector<float>> galleryFeatures;reid::extractFeatures(galleryImageNames, &net, batch_size, resize_h, resize_w, galleryFeatures);// 获取查询图像特征和画廊图像特征之间的前K个相似性排名std::vector<std::vector<int>> topkResult;reid::getTopK(queryFeatures, galleryFeatures, topk, topkResult);// 可视化结果reid::visualization(topkResult, queryImageNames, galleryImageNames, output_dir, resize_h, resize_w);return 0;  // 程序执行完成
}
Mat blob = dnn::blobFromImages(batch, 1.0, Size(resize_w, resize_h), Scalar(0.0,0.0,0.0), true, false, CV_32F);

350850eade5f0433f473113b3a46af47.png

sort(index.begin(), index.end(), [&](int x, int y) {return similarityList[x] > similarityList[y]; });

其利用了C++的STL(Standard Template Library,标准模板库)中的sort函数来对索引进行排序。具体来说,这行代码的作用是根据similarityList中元素的值来对索引进行从大到小的排序

cecdd7fb7031701a003da3a5fdd2a129.png

copyMakeBorder(img, result, bordersize, bordersize, bordersize, bordersize, cv::BORDER_CONSTANT, color);

7dc7a4b9ef062e63f608061aeb3429fe.png

ReID网络的输出:Mat out = net->forward();

b87ec5a49ead7a021be1cde4b4e62a08.png

记录:Debug报错。Release ok

995afcaee2a9c24bdacf4612c23b0d4f.png

C:\Users\cxy\AppData\Local\Programs\Python\Python310\python.exe -m onnxsim youtu_reid_baseline_large.onnx youtu_reid_baseline_large_sim.onnx
C:\Users\cxy\AppData\Local\Programs\Python\Python310\python.exe -m onnxsim youtu_reid_baseline_lite.onnx youtu_reid_baseline_lite_sim.onnx

The End

作者陈晓永:智能装备专业高级职称,软件工程师,机械设计中级职称,机器人与自动化产线仿真动画制作 


http://www.ppmy.cn/devtools/8102.html

相关文章

VUE识别图片文字OCR(tesseract.js)

效果:1&#xff1a; 效果图2&#xff1a; 一、安装tesseract.js npm i tesseract.js 二、静态页面实现 <template><div><div style"marginTop:100px"><input change"handleChage" type"file" id"image-input"…

Spring Boot与JdbcTemplate:构建MySQL数据库应用的简易指南

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

SpringBoot 操作 Redis

导入对应版本的依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>修改配置文件中的信息 spring:redis:host: 127.0.0.1port: 8888注意: 我这里 xsh…

经典机器学习算法——决策树

优质博文&#xff1a;IT-BLOG-CN 树模型是机器学习中最常用的一类模型&#xff0c;包括随机森林、AdaBoost、GBDT&#xff08;XGBoost和Lightgbm&#xff09;等&#xff0c;基本原理都是通过集成弱学习器的即式来进一步提升准确度。这里的弱学习器包括线性模型和决策树模型&…

Vue 3 Hooks:优雅管理组件状态的完整指南

一、介绍 Hooks是Vue 3中的特性&#xff0c;允许在函数组件中使用状态和其他React的逻辑。本教程将演示如何使用TypeScript和Hooks管理Vue 3组件的状态和生命周期。 二、创建Hooks 首先&#xff0c;创建一个hooks.ts文件&#xff0c;包含自定义hooks。 import { ref, onMou…

Python程序设计 字典

教学案例十 字典 1. 判断出生地 sfz.txt文件中存储了地区编码和地区名称 身份证的前6位为地区编码&#xff0c;可以在sfz.txt文件中查询到地区编号对应的地区名称 编写程序&#xff0c;输入身份证号&#xff0c;查询并显示对应的地区名称 若该地区编码不在文件中&#xff0c;…

C#面:IEnumerable的缺点有哪些

C# 中的 IEnumerable 接口是用于表示可枚举集合的基本接口。它提供了一个方法来迭代集合中的元素。虽然 IEnumerable 接口在很多情况下非常有用&#xff0c;但也存在一些缺点。以下是一些常见的缺点&#xff1a; 只能进行前向迭代&#xff1a;IEnumerable 接口只提供了一个方法…

Spring(下)

接上篇&#xff0c;从第八个问题讲起 八.Spring工厂创建复杂对象 1.什么是复杂对象 简单对象就是可以直接new出来的&#xff0c;也就是直接调用构造方法创建 所以复杂对象就是不能直接通过调用构造方法创建。就比如JDBC中的Connection 2.三种方法 &#xff08;1&#xff…