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

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); // 设置网络的输入数据为blob
        Mat 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.0
    for(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;  // 定义边框大小为5
    copyMakeBorder(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

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

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值