// // You can download a baseline ReID model and sample input from: // https://github.com/ReID-Team/ReID_extra_testdata // // Authors of samples and Youtu ReID baseline: // Xing Sun // Feng Zheng // Xinyang Jiang // Fufu Yu // Enwei Zhang // // Copyright (C) 2020-2021, Tencent. // Copyright (C) 2020-2021, SUSTech. // #include #include #include #include #include using namespace cv; using namespace cv::dnn; std::string param_keys = "{help h | | show help message}" "{model m | | network model}" "{query_list q | | list of query images}" "{gallery_list g | | 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 | | path for visualization(it should be existed)}"; std::string backend_keys = cv::format( "{ backend | 0 | Choose one of computation backends: " "%d: automatically (by default), " "%d: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), " "%d: OpenCV implementation, " "%d: VKCOM, " "%d: CUDA }", cv::dnn::DNN_BACKEND_DEFAULT, cv::dnn::DNN_BACKEND_INFERENCE_ENGINE, cv::dnn::DNN_BACKEND_OPENCV, cv::dnn::DNN_BACKEND_VKCOM, cv::dnn::DNN_BACKEND_CUDA); std::string target_keys = cv::format( "{ target | 0 | Choose one of target computation devices: " "%d: CPU target (by default), " "%d: OpenCL, " "%d: OpenCL fp16 (half-float precision), " "%d: VPU, " "%d: Vulkan, " "%d: CUDA, " "%d: CUDA fp16 (half-float preprocess) }", cv::dnn::DNN_TARGET_CPU, cv::dnn::DNN_TARGET_OPENCL, cv::dnn::DNN_TARGET_OPENCL_FP16, cv::dnn::DNN_TARGET_MYRIAD, cv::dnn::DNN_TARGET_VULKAN, cv::dnn::DNN_TARGET_CUDA, cv::dnn::DNN_TARGET_CUDA_FP16); std::string keys = param_keys + backend_keys + target_keys; 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); for (int y = 0; y < ret.rows; y ++) { for (int x = 0; x < ret.cols; x++) { for (int c = 0; c < 3; c++) { ret.at(y,x)[c] = (float)((img.at(y,x)[c] / 255.0 - mean[2 - c]) / std[2 - c]); } } } return ret; } static std::vector normalization(const std::vector& feature) { std::vector ret; float sum = 0.0; for(int i = 0; i < (int)feature.size(); i++) { sum += feature[i] * feature[i]; } sum = sqrt(sum); for(int i = 0; i < (int)feature.size(); i++) { ret.push_back(feature[i] / sum); } return ret; } static void extractFeatures(const std::vector& imglist, Net* net, const int& batch_size, const int& resize_h, const int& resize_w, std::vector>& features) { for(int st = 0; st < (int)imglist.size(); st += batch_size) { std::vector 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); net->setInput(blob); Mat out = net->forward(); for(int i = 0; i < (int)out.size().height; i++) { std::vector temp_feature; for(int j = 0; j < (int)out.size().width; j++) { temp_feature.push_back(out.at(i,j)); } features.push_back(normalization(temp_feature)); } } return ; } static void getNames(const std::string& ImageList, std::vector& 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& feature1, const std::vector& feature2) { float result = 0.0; for(int i = 0; i < (int)feature1.size(); i++) { result += feature1[i] * feature2[i]; } return result; } static void getTopK(const std::vector>& queryFeatures, const std::vector>& galleryFeatures, const int& topk, std::vector>& result) { for(int i = 0; i < (int)queryFeatures.size(); i++) { std::vector similarityList; std::vector 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 topk_result; for(int j = 0; j < min(topk, (int)index.size()); j++) { topk_result.push_back(index[j]); } result.push_back(topk_result); } return ; } static void addBorder(const Mat& img, const Scalar& color, Mat& result) { const int bordersize = 5; copyMakeBorder(img, result, bordersize, bordersize, bordersize, bordersize, cv::BORDER_CONSTANT, color); return ; } static void drawRankList(const std::string& queryName, const std::vector& galleryImageNames, const std::vector& 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; 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); std::vector Images; Images.push_back(q_img); for(int i = 0; i < (int)topk_index.size(); i++) { 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 ; } static void visualization(const std::vector>& topk, const std::vector& queryImageNames, const std::vector& 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); std::string output_path = output_dir + "/" + queryImageNames[i].substr(queryImageNames[i].rfind("/")+1); imwrite(output_path, img); } return ; } }; }; int main(int argc, char** argv) { // Parse command line arguments. CommandLineParser parser(argc, argv, keys); if (argc == 1 || 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("model"); const std::string queryImageList = parser.get("query_list"); const std::string galleryImageList = parser.get("gallery_list"); const int backend = parser.get("backend"); const int target = parser.get("target"); const int batch_size = parser.get("batch_size"); const int resize_h = parser.get("resize_h"); const int resize_w = parser.get("resize_w"); const int topk = parser.get("topk"); const std::string output_dir= parser.get("output_dir"); std::vector queryImageNames; reid::getNames(queryImageList, queryImageNames); std::vector galleryImageNames; reid::getNames(galleryImageList, galleryImageNames); dnn::Net net = dnn::readNet(modelPath); net.setPreferableBackend(backend); net.setPreferableTarget(target); std::vector> queryFeatures; reid::extractFeatures(queryImageNames, &net, batch_size, resize_h, resize_w, queryFeatures); std::vector> galleryFeatures; reid::extractFeatures(galleryImageNames, &net, batch_size, resize_h, resize_w, galleryFeatures); std::vector> topkResult; reid::getTopK(queryFeatures, galleryFeatures, topk, topkResult); reid::visualization(topkResult, queryImageNames, galleryImageNames, output_dir, resize_h, resize_w); return 0; }