yolo_detector.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. /**
  2. * @file yolo_detector.cpp
  3. * @brief Yolo Object Detection Sample
  4. * @author OpenCV team
  5. */
  6. //![includes]
  7. #include <opencv2/dnn.hpp>
  8. #include <opencv2/imgproc.hpp>
  9. #include <opencv2/imgcodecs.hpp>
  10. #include <fstream>
  11. #include <sstream>
  12. #include "iostream"
  13. #include "common.hpp"
  14. #include <opencv2/highgui.hpp>
  15. //![includes]
  16. using namespace cv;
  17. using namespace cv::dnn;
  18. void getClasses(std::string classesFile);
  19. void drawPrediction(int classId, float conf, int left, int top, int right, int bottom, Mat& frame);
  20. void yoloPostProcessing(
  21. std::vector<Mat>& outs,
  22. std::vector<int>& keep_classIds,
  23. std::vector<float>& keep_confidences,
  24. std::vector<Rect2d>& keep_boxes,
  25. float conf_threshold,
  26. float iou_threshold,
  27. const std::string& model_name,
  28. const int nc
  29. );
  30. std::vector<std::string> classes;
  31. std::string keys =
  32. "{ help h | | Print help message. }"
  33. "{ device | 0 | camera device number. }"
  34. "{ model | onnx/models/yolox_s_inf_decoder.onnx | Default model. }"
  35. "{ yolo | yolox | yolo model version. }"
  36. "{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera. }"
  37. "{ classes | | Optional path to a text file with names of classes to label detected objects. }"
  38. "{ nc | 80 | Number of classes. Default is 80 (coming from COCO dataset). }"
  39. "{ thr | .5 | Confidence threshold. }"
  40. "{ nms | .4 | Non-maximum suppression threshold. }"
  41. "{ mean | 0.0 0.0 0.0 | Normalization constant. }"
  42. "{ scale | 1.0 1.0 1.0 | Preprocess input image by multiplying on a scale factor. }"
  43. "{ width | 640 | Preprocess input image by resizing to a specific width. }"
  44. "{ height | 640 | Preprocess input image by resizing to a specific height. }"
  45. "{ rgb | 1 | Indicate that model works with RGB input images instead BGR ones. }"
  46. "{ padvalue | 114.0 | padding value. }"
  47. "{ paddingmode | 2 | Choose one of computation backends: "
  48. "0: resize to required input size without extra processing, "
  49. "1: Image will be cropped after resize, "
  50. "2: Resize image to the desired size while preserving the aspect ratio of original image }"
  51. "{ backend | 0 | Choose one of computation backends: "
  52. "0: automatically (by default), "
  53. "1: Halide language (http://halide-lang.org/), "
  54. "2: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
  55. "3: OpenCV implementation, "
  56. "4: VKCOM, "
  57. "5: CUDA }"
  58. "{ target | 0 | Choose one of target computation devices: "
  59. "0: CPU target (by default), "
  60. "1: OpenCL, "
  61. "2: OpenCL fp16 (half-float precision), "
  62. "3: VPU, "
  63. "4: Vulkan, "
  64. "6: CUDA, "
  65. "7: CUDA fp16 (half-float preprocess) }"
  66. "{ async | 0 | Number of asynchronous forwards at the same time. "
  67. "Choose 0 for synchronous mode }";
  68. void getClasses(std::string classesFile)
  69. {
  70. std::ifstream ifs(classesFile.c_str());
  71. if (!ifs.is_open())
  72. CV_Error(Error::StsError, "File " + classesFile + " not found");
  73. std::string line;
  74. while (std::getline(ifs, line))
  75. classes.push_back(line);
  76. }
  77. void drawPrediction(int classId, float conf, int left, int top, int right, int bottom, Mat& frame)
  78. {
  79. rectangle(frame, Point(left, top), Point(right, bottom), Scalar(0, 255, 0));
  80. std::string label = format("%.2f", conf);
  81. if (!classes.empty())
  82. {
  83. CV_Assert(classId < (int)classes.size());
  84. label = classes[classId] + ": " + label;
  85. }
  86. int baseLine;
  87. Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
  88. top = max(top, labelSize.height);
  89. rectangle(frame, Point(left, top - labelSize.height),
  90. Point(left + labelSize.width, top + baseLine), Scalar::all(255), FILLED);
  91. putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.5, Scalar());
  92. }
  93. void yoloPostProcessing(
  94. std::vector<Mat>& outs,
  95. std::vector<int>& keep_classIds,
  96. std::vector<float>& keep_confidences,
  97. std::vector<Rect2d>& keep_boxes,
  98. float conf_threshold,
  99. float iou_threshold,
  100. const std::string& model_name,
  101. const int nc=80)
  102. {
  103. // Retrieve
  104. std::vector<int> classIds;
  105. std::vector<float> confidences;
  106. std::vector<Rect2d> boxes;
  107. if (model_name == "yolov8" || model_name == "yolov10" ||
  108. model_name == "yolov9")
  109. {
  110. cv::transposeND(outs[0], {0, 2, 1}, outs[0]);
  111. }
  112. if (model_name == "yolonas")
  113. {
  114. // outs contains 2 elements of shape [1, 8400, nc] and [1, 8400, 4]. Concat them to get [1, 8400, nc+4]
  115. Mat concat_out;
  116. // squeeze the first dimension
  117. outs[0] = outs[0].reshape(1, outs[0].size[1]);
  118. outs[1] = outs[1].reshape(1, outs[1].size[1]);
  119. cv::hconcat(outs[1], outs[0], concat_out);
  120. outs[0] = concat_out;
  121. // remove the second element
  122. outs.pop_back();
  123. // unsqueeze the first dimension
  124. outs[0] = outs[0].reshape(0, std::vector<int>{1, outs[0].size[0], outs[0].size[1]});
  125. }
  126. // assert if last dim is nc+5 or nc+4
  127. CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, nc+5 or nc+4]");
  128. CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == nc + 4), true, "Invalid output shape: ");
  129. for (auto preds : outs)
  130. {
  131. preds = preds.reshape(1, preds.size[1]); // [1, 8400, 85] -> [8400, 85]
  132. for (int i = 0; i < preds.rows; ++i)
  133. {
  134. // filter out non object
  135. float obj_conf = (model_name == "yolov8" || model_name == "yolonas" ||
  136. model_name == "yolov9" || model_name == "yolov10") ? 1.0f : preds.at<float>(i, 4) ;
  137. if (obj_conf < conf_threshold)
  138. continue;
  139. Mat scores = preds.row(i).colRange((model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10") ? 4 : 5, preds.cols);
  140. double conf;
  141. Point maxLoc;
  142. minMaxLoc(scores, 0, &conf, 0, &maxLoc);
  143. conf = (model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10") ? conf : conf * obj_conf;
  144. if (conf < conf_threshold)
  145. continue;
  146. // get bbox coords
  147. float* det = preds.ptr<float>(i);
  148. double cx = det[0];
  149. double cy = det[1];
  150. double w = det[2];
  151. double h = det[3];
  152. // [x1, y1, x2, y2]
  153. if (model_name == "yolonas" || model_name == "yolov10"){
  154. boxes.push_back(Rect2d(cx, cy, w, h));
  155. } else {
  156. boxes.push_back(Rect2d(cx - 0.5 * w, cy - 0.5 * h,
  157. cx + 0.5 * w, cy + 0.5 * h));
  158. }
  159. classIds.push_back(maxLoc.x);
  160. confidences.push_back(static_cast<float>(conf));
  161. }
  162. }
  163. // NMS
  164. std::vector<int> keep_idx;
  165. NMSBoxes(boxes, confidences, conf_threshold, iou_threshold, keep_idx);
  166. for (auto i : keep_idx)
  167. {
  168. keep_classIds.push_back(classIds[i]);
  169. keep_confidences.push_back(confidences[i]);
  170. keep_boxes.push_back(boxes[i]);
  171. }
  172. }
  173. /**
  174. * @function main
  175. * @brief Main function
  176. */
  177. int main(int argc, char** argv)
  178. {
  179. CommandLineParser parser(argc, argv, keys);
  180. parser.about("Use this script to run object detection deep learning networks using OpenCV.");
  181. if (parser.has("help"))
  182. {
  183. parser.printMessage();
  184. return 0;
  185. }
  186. CV_Assert(parser.has("model"));
  187. CV_Assert(parser.has("yolo"));
  188. // if model is default, use findFile to get the full path otherwise use the given path
  189. std::string weightPath = findFile(parser.get<String>("model"));
  190. std::string yolo_model = parser.get<String>("yolo");
  191. int nc = parser.get<int>("nc");
  192. float confThreshold = parser.get<float>("thr");
  193. float nmsThreshold = parser.get<float>("nms");
  194. //![preprocess_params]
  195. float paddingValue = parser.get<float>("padvalue");
  196. bool swapRB = parser.get<bool>("rgb");
  197. int inpWidth = parser.get<int>("width");
  198. int inpHeight = parser.get<int>("height");
  199. Scalar scale = parser.get<Scalar>("scale");
  200. Scalar mean = parser.get<Scalar>("mean");
  201. ImagePaddingMode paddingMode = static_cast<ImagePaddingMode>(parser.get<int>("paddingmode"));
  202. //![preprocess_params]
  203. // check if yolo model is valid
  204. if (yolo_model != "yolov5" && yolo_model != "yolov6"
  205. && yolo_model != "yolov7" && yolo_model != "yolov8"
  206. && yolo_model != "yolov10" && yolo_model !="yolov9"
  207. && yolo_model != "yolox" && yolo_model != "yolonas")
  208. CV_Error(Error::StsError, "Invalid yolo model: " + yolo_model);
  209. // get classes
  210. if (parser.has("classes"))
  211. {
  212. getClasses(findFile(parser.get<String>("classes")));
  213. }
  214. // load model
  215. //![read_net]
  216. Net net = readNet(weightPath);
  217. int backend = parser.get<int>("backend");
  218. net.setPreferableBackend(backend);
  219. net.setPreferableTarget(parser.get<int>("target"));
  220. //![read_net]
  221. VideoCapture cap;
  222. Mat img;
  223. bool isImage = false;
  224. bool isCamera = false;
  225. // Check if input is given
  226. if (parser.has("input"))
  227. {
  228. String input = parser.get<String>("input");
  229. // Check if the input is an image
  230. if (input.find(".jpg") != String::npos || input.find(".png") != String::npos)
  231. {
  232. img = imread(findFile(input));
  233. if (img.empty())
  234. {
  235. CV_Error(Error::StsError, "Cannot read image file: " + input);
  236. }
  237. isImage = true;
  238. }
  239. else
  240. {
  241. cap.open(input);
  242. if (!cap.isOpened())
  243. {
  244. CV_Error(Error::StsError, "Cannot open video " + input);
  245. }
  246. isCamera = true;
  247. }
  248. }
  249. else
  250. {
  251. int cameraIndex = parser.get<int>("device");
  252. cap.open(cameraIndex);
  253. if (!cap.isOpened())
  254. {
  255. CV_Error(Error::StsError, cv::format("Cannot open camera #%d", cameraIndex));
  256. }
  257. isCamera = true;
  258. }
  259. // image pre-processing
  260. //![preprocess_call]
  261. Size size(inpWidth, inpHeight);
  262. Image2BlobParams imgParams(
  263. scale,
  264. size,
  265. mean,
  266. swapRB,
  267. CV_32F,
  268. DNN_LAYOUT_NCHW,
  269. paddingMode,
  270. paddingValue);
  271. // rescale boxes back to original image
  272. Image2BlobParams paramNet;
  273. paramNet.scalefactor = scale;
  274. paramNet.size = size;
  275. paramNet.mean = mean;
  276. paramNet.swapRB = swapRB;
  277. paramNet.paddingmode = paddingMode;
  278. //![preprocess_call]
  279. //![forward_buffers]
  280. std::vector<Mat> outs;
  281. std::vector<int> keep_classIds;
  282. std::vector<float> keep_confidences;
  283. std::vector<Rect2d> keep_boxes;
  284. std::vector<Rect> boxes;
  285. //![forward_buffers]
  286. Mat inp;
  287. while (waitKey(1) < 0)
  288. {
  289. if (isCamera)
  290. cap >> img;
  291. if (img.empty())
  292. {
  293. std::cout << "Empty frame" << std::endl;
  294. waitKey();
  295. break;
  296. }
  297. //![preprocess_call_func]
  298. inp = blobFromImageWithParams(img, imgParams);
  299. //![preprocess_call_func]
  300. //![forward]
  301. net.setInput(inp);
  302. net.forward(outs, net.getUnconnectedOutLayersNames());
  303. //![forward]
  304. //![postprocess]
  305. yoloPostProcessing(
  306. outs, keep_classIds, keep_confidences, keep_boxes,
  307. confThreshold, nmsThreshold,
  308. yolo_model,
  309. nc);
  310. //![postprocess]
  311. // covert Rect2d to Rect
  312. //![draw_boxes]
  313. for (auto box : keep_boxes)
  314. {
  315. boxes.push_back(Rect(cvFloor(box.x), cvFloor(box.y), cvFloor(box.width - box.x), cvFloor(box.height - box.y)));
  316. }
  317. paramNet.blobRectsToImageRects(boxes, boxes, img.size());
  318. for (size_t idx = 0; idx < boxes.size(); ++idx)
  319. {
  320. Rect box = boxes[idx];
  321. drawPrediction(keep_classIds[idx], keep_confidences[idx], box.x, box.y,
  322. box.width + box.x, box.height + box.y, img);
  323. }
  324. const std::string kWinName = "Yolo Object Detector";
  325. namedWindow(kWinName, WINDOW_NORMAL);
  326. imshow(kWinName, img);
  327. //![draw_boxes]
  328. outs.clear();
  329. keep_classIds.clear();
  330. keep_confidences.clear();
  331. keep_boxes.clear();
  332. boxes.clear();
  333. if (isImage)
  334. {
  335. waitKey();
  336. break;
  337. }
  338. }
  339. }