YoloFeatureManager.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #include "../pch/pch.h"
  2. #include "YoloFeatureManager.h"
  3. #include <fstream>
  4. #include <algorithm>
  5. #include <iostream>
  6. #include <functional>
  7. #include <numeric>
  8. #include <sstream>
  9. #include "../tool/CVideoCapture.h"
  10. #include "YoloClassName.h"
  11. YoloFeatureManager::YoloFeatureManager()
  12. {
  13. inputWidth = 448;
  14. inputHeight = 448 ;
  15. CONF_THRESHOLD = 0.5f; // 可以根据需要调整置信度阈值
  16. NMS_THRESHOLD = 0.4f; // 可以根据需要调整NMS阈值
  17. FRUIT_VEGETABLE_COUNT = sizeof(FRUIT_VEGETABLE_NAMES) / sizeof(FRUIT_VEGETABLE_NAMES[0]);
  18. }
  19. YoloFeatureManager::~YoloFeatureManager()
  20. {
  21. }
  22. void YoloFeatureManager::loadModel()
  23. {
  24. try
  25. {
  26. std::wstring wsProgramDir = CSystem::GetProgramDir();
  27. std::filesystem::path mainDir = wsProgramDir;
  28. std::string sMainDir = mainDir.string();
  29. // YOLO2026模型路径
  30. std::string modelPath = sMainDir + "/ai/yolo26s-cls-zhipuzi-448.onnx";
  31. net = cv::dnn::readNetFromONNX(modelPath);
  32. }
  33. catch (const std::exception& e)
  34. {
  35. std::string aa = std::string(e.what());
  36. CLewaimaiLog::OutputDebugMessage("加载模型失败: " + std::string(e.what()));
  37. return;
  38. }
  39. }
  40. void YoloFeatureManager::loadModelForOpenVINO()
  41. {
  42. try
  43. {
  44. std::wstring wsProgramDir = CSystem::GetProgramDir();
  45. std::filesystem::path mainDir = wsProgramDir;
  46. std::string sMainDir = mainDir.string();
  47. // YOLO2026模型路径
  48. std::string modelPath = sMainDir + "/ai/best.xml";
  49. std::string configPath = sMainDir + "/ai/best.bin";
  50. net = cv::dnn::readNetFromModelOptimizer(modelPath, configPath);
  51. // 设置目标设备 (可选: CPU, GPU, MYRIAD等)
  52. net.setPreferableBackend(cv::dnn::DNN_BACKEND_INFERENCE_ENGINE);
  53. net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); // 或DNN_TARGET_MYRIAD等
  54. }
  55. catch (const std::exception& e)
  56. {
  57. std::string aa = std::string(e.what());
  58. CLewaimaiLog::OutputDebugMessage(("加载模型失败: " + std::string(e.what())).c_str());
  59. return;
  60. }
  61. }
  62. // 寻找置信度最高的类别
  63. int YoloFeatureManager::getTopClass(const cv::Mat& output)
  64. {
  65. // 将输出展平为一维数组
  66. cv::Mat flatOutput = output.reshape(1, 1);
  67. double maxVal;
  68. cv::Point maxLoc;
  69. // 找到最大值的位置(即最高置信度类别索引)
  70. cv::minMaxLoc(flatOutput, nullptr, &maxVal, nullptr, &maxLoc);
  71. return maxLoc.x;
  72. }
  73. // 获取类别名称
  74. std::string YoloFeatureManager::getClassName(std::size_t classId) const
  75. {
  76. if (classId >= 0 && classId < FRUIT_VEGETABLE_COUNT)
  77. {
  78. std::string cnName = FRUIT_VEGETABLE_NAMES[classId];
  79. return cnName;
  80. }
  81. return "Unknown";
  82. }
  83. std::vector<float> YoloFeatureManager::extractFeatures(const std::string & imagePath)
  84. {
  85. cv::Mat image = cv::imread(imagePath);
  86. return extractFeatures(image);
  87. }
  88. std::vector<float> YoloFeatureManager::extractFeatures(cv::Mat& image)
  89. {
  90. std::lock_guard<std::mutex> lock(m_mutex);
  91. try
  92. {
  93. auto time_1 = std::chrono::high_resolution_clock::now();
  94. if (image.empty())
  95. {
  96. throw std::runtime_error("Could not load image");
  97. }
  98. // 转换为blob(归一化+通道转换)
  99. cv::Mat blob;
  100. cv::dnn::blobFromImage(image, blob, 1.0 / 255, cv::Size(inputWidth, inputHeight), cv::Scalar(0, 0, 0), true, true);
  101. net.setInput(blob);
  102. auto time_2 = std::chrono::high_resolution_clock::now();
  103. //获取模型的所有层名称(调试用)
  104. std::vector<cv::String> layerNames = net.getLayerNames();
  105. // 获取Flatten层输出(yolo26s-cls的Flatten层名称为 "onnx_node!/model.10/Flatten",这是GAP后分类头前的一层)'
  106. // GAP层是onnx_node!/model.10/pool/GlobalAveragePool
  107. cv::Mat featureMat = net.forward("onnx_node!/model.10/Flatten");
  108. // 检查输出是否有效
  109. if (featureMat.empty())
  110. {
  111. throw std::runtime_error("模型前向传播未产生有效输出");
  112. }
  113. if (featureMat.type() != CV_32F)
  114. {
  115. throw std::runtime_error("Mat类型错误");
  116. }
  117. float norm_before = cv::norm(featureMat, cv::NORM_L2);
  118. CLewaimaiLog::OutputDebugMessageFormat("归一化前 norm:%.6f\n", norm_before);
  119. cv::normalize(featureMat, featureMat, 1.0, 0.0, cv::NORM_L2); //L2归一化
  120. float norm_after = cv::norm(featureMat, cv::NORM_L2);
  121. CLewaimaiLog::OutputDebugMessageFormat("归一化后 norm:%.6f\n", norm_after);
  122. // 将Mat格式的特征转换为vector<float>(方便后续计算/存储)
  123. std::vector<float> feature_vector;
  124. feature_vector.assign((float*)featureMat.data, (float*)featureMat.data + featureMat.total());
  125. //进行时间统计
  126. auto time_3 = std::chrono::high_resolution_clock::now();
  127. auto duration_1 = std::chrono::duration_cast<std::chrono::milliseconds>(time_2 - time_1);
  128. std::wstring msg = L"图片处理耗时: " + std::to_wstring(duration_1.count()) + L" 毫秒";
  129. CLewaimaiLog::OutputDebugMessage(msg.c_str());
  130. auto duration_2 = std::chrono::duration_cast<std::chrono::milliseconds>(time_3 - time_2);
  131. std::wstring msg2 = L"模型推理耗时: " + std::to_wstring(duration_2.count()) + L" 毫秒";
  132. CLewaimaiLog::OutputDebugMessage(msg2.c_str());
  133. auto totalDuration = std::chrono::duration_cast<std::chrono::milliseconds>(time_3 - time_1);
  134. std::wstring msg4 = L"总耗时: " + std::to_wstring(totalDuration.count()) + L" 毫秒";
  135. CLewaimaiLog::OutputDebugMessage(msg4.c_str());
  136. return feature_vector;
  137. }
  138. catch (const std::exception& e)
  139. {
  140. std::string aa = std::string(e.what());
  141. CLewaimaiLog::OutputDebugMessage("提取特征失败: " + std::string(e.what()));
  142. return {};
  143. }
  144. }
  145. void YoloFeatureManager::DebugTopResults(const cv::Mat& output, int topK)
  146. {
  147. cv::Mat scores = output.reshape(1, 1);
  148. const int count = scores.cols;
  149. topK = std::min(topK, count);
  150. std::vector<int> indices(count);
  151. std::iota(indices.begin(), indices.end(), 0);
  152. std::partial_sort(indices.begin(), indices.begin() + topK, indices.end(),
  153. [&scores](int left, int right)
  154. {
  155. return scores.at<float>(0, left) > scores.at<float>(0, right);
  156. });
  157. std::string message = "YOLO分类 Top " + std::to_string(topK) + " 结果:";
  158. for (int i = 0; i < topK; ++i)
  159. {
  160. const int classId = indices[i];
  161. const float confidence = scores.at<float>(0, classId);
  162. message += "\nTop " + std::to_string(i + 1) +
  163. ": id=" + std::to_string(classId) +
  164. ": name=" + this->getClassName(classId) +
  165. ", confidence=" + std::to_string(confidence);
  166. }
  167. m_topResultMessage = message;
  168. }
  169. std::string YoloFeatureManager::Class(cv::Mat & image)
  170. {
  171. std::lock_guard<std::mutex> lock(m_mutex);
  172. try
  173. {
  174. std::string className = "";
  175. // ====================== 图像预处理 ======================
  176. // 转换为blob格式:归一化(0-1)、通道转换(BGR->RGB)、调整尺寸
  177. cv::Mat blob;
  178. cv::dnn::blobFromImage(image, blob, 1.0 / 255.0, cv::Size(inputWidth, inputHeight), cv::Scalar(0, 0, 0), true, true);
  179. net.setInput(blob);
  180. auto time_1 = std::chrono::high_resolution_clock::now();
  181. // ====================== 模型推理 ======================
  182. cv::Mat output = net.forward(); // 输出形状:1x1000(对应ImageNet 1000类)
  183. this->DebugTopResults(output);
  184. auto time_2 = std::chrono::high_resolution_clock::now();
  185. auto totalDuration = std::chrono::duration_cast<std::chrono::milliseconds>(time_2 - time_1);
  186. std::wstring msg = L"Class 总耗时: " + std::to_wstring(totalDuration.count()) + L" 毫秒\r\n";
  187. msg += CLewaimaiString::ANSIToUnicode(m_topResultMessage);
  188. CLewaimaiLog::OutputDebugMessage(msg.c_str());
  189. CLewaimaiLog::OutputMessageBox(msg.c_str());
  190. // ====================== 解析结果 ======================
  191. int topClassIdx = this->getTopClass(output);
  192. float topConfidence = output.at<float>(topClassIdx);
  193. if (topConfidence > 0.8)
  194. {
  195. className = this->getClassName(topClassIdx);
  196. }
  197. else
  198. {
  199. className = "Unknown";
  200. }
  201. return className;
  202. }
  203. catch (const std::exception& e)
  204. {
  205. CLewaimaiLog::OutputDebugMessage(("YOLO分类失败: " + std::string(e.what())).c_str());
  206. return {};
  207. }
  208. }
  209. void YoloFeatureManager::drawChineseText(cv::Mat & img, const wchar_t * text, cv::Point pos, cv::Scalar color, int fontSize)
  210. {
  211. // 1. 检查输入有效性
  212. if (img.empty() || text == nullptr || wcslen(text) == 0)
  213. {
  214. return;
  215. }
  216. if (img.type() != CV_8UC3)
  217. {
  218. // 仅支持 3 通道彩色图像
  219. cvtColor(img, img, cv::COLOR_GRAY2BGR);
  220. }
  221. // 2. 创建内存 DC 并关联临时位图(关键:基于图像的 DC 创建,而非屏幕 DC)
  222. HDC hScreenDC = GetDC(NULL);
  223. HDC hMemDC = CreateCompatibleDC(hScreenDC);
  224. // 创建与原图像尺寸、格式匹配的位图
  225. HBITMAP hMemBmp = CreateCompatibleBitmap(hScreenDC, img.cols, img.rows);
  226. // 保存原始位图句柄,用于后续恢复
  227. HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hMemBmp);
  228. // 3. 将 OpenCV 图像数据复制到内存位图(保留原图像内容,而非黑色)
  229. BITMAPINFO bmi = { 0 };
  230. bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  231. bmi.bmiHeader.biWidth = img.cols;
  232. bmi.bmiHeader.biHeight = -img.rows; // 翻转 Y 轴(OpenCV 与 GDI 坐标方向相反)
  233. bmi.bmiHeader.biPlanes = 1;
  234. bmi.bmiHeader.biBitCount = 24;
  235. bmi.bmiHeader.biCompression = BI_RGB;
  236. // 将 OpenCV 图像写入内存位图
  237. SetDIBits(hScreenDC, hMemBmp, 0, img.rows, img.data, &bmi, DIB_RGB_COLORS);
  238. // 4. 设置中文字体(修复字体创建参数,增加容错)
  239. HFONT hFont = CreateFont(
  240. fontSize, 0, 0, 0, FW_NORMAL, 0, 0, 0,
  241. GB2312_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
  242. DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"黑体"
  243. );
  244. HFONT hOldFont = (HFONT)SelectObject(hMemDC, hFont);
  245. // 5. 设置文字绘制属性(背景透明、颜色正确)
  246. SetBkMode(hMemDC, TRANSPARENT);
  247. // OpenCV 是 BGR,GDI 是 RGB,需转换
  248. SetTextColor(hMemDC, RGB((int)color[2], (int)color[1], (int)color[0]));
  249. // 6. 绘制中文字符(确保坐标在图像范围内)
  250. int textLen = wcslen(text);
  251. if (pos.x >= 0 && pos.y >= 0 && pos.x < img.cols && pos.y < img.rows)
  252. {
  253. TextOutW(hMemDC, pos.x, pos.y, text, textLen);
  254. }
  255. // 7. 将绘制后的位图数据复制回 OpenCV 图像
  256. GetDIBits(hScreenDC, hMemBmp, 0, img.rows, img.data, &bmi, DIB_RGB_COLORS);
  257. // 8. 释放资源(关键:恢复原始句柄后再删除,避免内存泄漏)
  258. SelectObject(hMemDC, hOldFont); // 恢复原始字体
  259. DeleteObject(hFont); // 删除自定义字体
  260. SelectObject(hMemDC, hOldBmp); // 恢复原始位图
  261. DeleteObject(hMemBmp); // 删除内存位图
  262. DeleteDC(hMemDC); // 删除内存 DC
  263. ReleaseDC(NULL, hScreenDC); // 释放屏幕 DC
  264. }