Hiện tại OpenCV 4.8.0 là phiên bản mới, có thể chạy được với các model Pytorch, Tensorflow,… Bài viết này hướng dẫn build lib OpenCV C++ bằng Visual Studio 2017 trên Windows 10 x64
Bài viết này KHÔNG build những thứ như sau:
- Không build CUDA, chỉ chạy với CPU
- Không build Python, chỉ build C++. Đã viết bài hướng dẫn build OpenCV-Python riêng
- Không build x86, chỉ build x64 vì tốc độ nhanh hơn
Trong quá trình build chắc chắn có nhiều vấn đề, khuyến khích các bạn sử dụng Git hoặc SVN để revert nếu thao tác sai.
Bước 1: chuẩn bị source code & tool cần thiết
- OpenCV 4.8.0: https://github.com/opencv/opencv/releases
- OpenCV Contrib 4.8.0: https://github.com/opencv/opencv_contrib/releases/tag/4.8.0
- Visual Studio 2017 Community: nhớ cài đặt C++
- cmake-3.22.1-windows-x86_64.msi
Các tool & version đã test thành công, các bạn chọn không đúng version như hướng dẫn có thể gây ra lỗi.
Bước 2: make C++ solution
Mình sẽ giải nén vào folder D:\LIB\opencv, như vậy 2 folder source code tương ứng là:
D:\LIB\opencv\opencv480
D:\LIB\opencv\opencv_contrib-4.8.0
Chạy Cmake (GUI) lên và chọn folder source code như hình dưới,
Sau đó ấn nút Configure, một popup hiện ra, các bạn chọn phiên bản Visual Studio và architect (Win32/x64). Lý do mình chọn Visual Studio 2017 là do các thư viện khác ở cty cũng dùng VS 2017, x64 vì chạy nhanh hơn Win32.
Các config cần thực hiện
OPENCV_EXTRA_MODULES_PATH: Chọn đến folder D:\LIB\opencv\opencv_contrib-4.8.0\modules
BUILD_JAVA: mình không sử dụng nên uncheck
BUILD_opencv_java_bindings_generator: mình không sử dụng nên uncheck
BUILD_opencv_python3: mình không sử dụng nên uncheck
BUILD_opencv_python_bindings_generator: mình không sử dụng nên uncheck
BUILD_opencv_python_tests: mình không sử dụng nên uncheck
WITH_JASPER: mình không sử dụng nên uncheck
BUILD_OPENEXR: mình không sử dụng nên uncheck
WITH_OPENEXR: mình không sử dụng nên uncheck
BUILD_TIFF: mình không sử dụng nên uncheck
BUILD_opencv_js: mình không sử dụng nên uncheck
BUILD_opencv_js_bindings_generator: mình không sử dụng nên uncheck
BUILD_PERF_TESTS: không cần test performance nên bỏ
BUILD_TESTS: không cần test nên bỏ
ENABLE_PRECOMPILED_HEADERS: bên mình cũng không precompile header nên bỏ
BUILD_ITT: mình không sử dụng nên uncheck
BUILD_ITT: mình không sử dụng nên uncheck
ccitt: mình không sử dụng nên uncheck
BUILD_IPP_IW: mình không sử dụng nên uncheck
WITH_IPP: mình không sử dụng nên uncheck
CPU_DISPATCH: xóa hết vì mình muốn build chạy được và ổn định trên tất cả CPU, kể cả CPU đời cũ
Sau đó ấn nút Generate, Cmake sẽ chạy và báo Generating done là xong
Vào folder D:\LIB\opencv\opencv480 sẽ thấy OpenCV.sln, mở bằng Visual Studio 2017 và build là được
Test nhanh bằng YOLOv5
- Clone YOLOv5 tại link: https://github.com/ultralytics/yolov5
- Download file yolov5m.pt và bỏ vào folder Yolov5
- Tại folder Yolov5 chạy command sau để convert file pt thành file onnx
- Khi convert thành công bạn sẽ được file yolo5m.onnx như hình dưới
- Build project OpenCV DNN c++ với đoạn code bên dưới [bg_collapse view=”button-orange” color=”#4a4949″ expand_text=”expand” collapse_text=”collapse” ]
- Download file coco.names bỏ vào folder output
- Copy file onnx vào folder output của project OpenCV DNN
- Run code ta được kết quả
export.py --weights yolov5m.pt --include onnx --opset=16
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
#include <fstream> #include <sstream> #include <iostream> #include <opencv2/dnn.hpp> #include <opencv2/imgproc.hpp> #include <opencv2/highgui.hpp> // Namespaces. using namespace cv::dnn; // Constants. const float INPUT_WIDTH = 640.0; const float INPUT_HEIGHT = 640.0; const float SCORE_THRESHOLD = 0.5; const float NMS_THRESHOLD = 0.45; const float CONFIDENCE_THRESHOLD = 0.45; // Text parameters. const float FONT_SCALE = 0.7; const int FONT_FACE = cv::FONT_HERSHEY_SIMPLEX; const int THICKNESS = 1; // Colors. cv::Scalar BLACK = cv::Scalar(0, 0, 0); cv::Scalar BLUE = cv::Scalar(255, 178, 50); cv::Scalar YELLOW = cv::Scalar(0, 255, 255); cv::Scalar RED = cv::Scalar(0, 0, 255); // Draw the predicted bounding box. void draw_label(cv::Mat& input_image, std::string label, int left, int top) { // Display the label at the top of the bounding box. int baseLine; cv::Size label_size = cv::getTextSize(label, FONT_FACE, FONT_SCALE, THICKNESS, &baseLine); top = std::max(top, label_size.height); // Top left corner. cv::Point tlc = cv::Point(left, top); // Bottom right corner. cv::Point brc = cv::Point(left + label_size.width, top + label_size.height + baseLine); // Draw black rectangle. rectangle(input_image, tlc, brc, BLACK, cv::FILLED); // Put the label on the black rectangle. cv::putText(input_image, label, cv::Point(left, top + label_size.height), FONT_FACE, FONT_SCALE, YELLOW, THICKNESS); } cv::Mat post_process(cv::Mat &input_image, std::vector<cv::Mat> &outputs, const std::vector<std::string> &class_name) { // Initialize vectors to hold respective outputs while unwrapping detections. std::vector<int> class_ids; std::vector<float> confidences; std::vector<cv::Rect> boxes; // Resizing factor. float x_factor = input_image.cols / INPUT_WIDTH; float y_factor = input_image.rows / INPUT_HEIGHT; float *data = (float *)outputs[0].data; const int dimensions = 85; const int rows = 25200; // Iterate through 25200 detections. for (int i = 0; i < rows; ++i) { float confidence = data[4]; // Discard bad detections and continue. if (confidence >= CONFIDENCE_THRESHOLD) { float * classes_scores = data + 5; // Create a 1x85 Mat and store class scores of 80 classes. cv::Mat scores(1, class_name.size(), CV_32FC1, classes_scores); // Perform minMaxLoc and acquire index of best class score. cv::Point class_id; double max_class_score; minMaxLoc(scores, 0, &max_class_score, 0, &class_id); // Continue if the class score is above the threshold. if (max_class_score > SCORE_THRESHOLD) { // Store class ID and confidence in the pre-defined respective vectors. confidences.push_back(confidence); class_ids.push_back(class_id.x); // Center. float cx = data[0]; float cy = data[1]; // Box dimension. float w = data[2]; float h = data[3]; // Bounding box coordinates. int left = int((cx - 0.5 * w) * x_factor); int top = int((cy - 0.5 * h) * y_factor); int width = int(w * x_factor); int height = int(h * y_factor); // Store good detections in the boxes vector. boxes.push_back(cv::Rect(left, top, width, height)); } } // Jump to the next column. data += 85; } // Perform Non Maximum Suppression and draw predictions. std::vector<int> indices; NMSBoxes(boxes, confidences, SCORE_THRESHOLD, NMS_THRESHOLD, indices); for (int i = 0; i < indices.size(); i++) { int idx = indices[i]; cv::Rect box = boxes[idx]; int left = box.x; int top = box.y; int width = box.width; int height = box.height; // Draw bounding box. rectangle(input_image, cv::Point(left, top), cv::Point(left + width, top + height), BLUE, 1 * THICKNESS); // Get the label for the class name and its confidence. std::string label = cv::format("%.2f", confidences[idx]); label = class_name[class_ids[idx]] + ":" + label; // Draw class labels. draw_label(input_image, label, left, top); } return input_image; } int main() { // Load class list. std::vector<std::string> class_list; std::ifstream ifs("coco.names"); std::string line; while (getline(ifs, line)) { class_list.push_back(line); } // Load image. cv::Mat frame = cv::imread("sample.jpg"); // Load model. Net net = readNetFromONNX("yolov5m.onnx"); cv::Mat blob; blobFromImage(frame, blob, 1. / 255., cv::Size(INPUT_WIDTH, INPUT_HEIGHT), cv::Scalar(), true, false); net.setInput(blob); // Forward propagate. std::vector<cv::Mat> outputs; net.forward(outputs, net.getUnconnectedOutLayersNames()); cv::Mat img = post_process(frame.clone(), outputs, class_list); // Put efficiency information. // The function getPerfProfile returns the overall time for inference(t) and the timings for each of the layers(in layersTimes) std::vector<double> layersTimes; double freq = cv::getTickFrequency() / 1000; double t = net.getPerfProfile(layersTimes) / freq; std::string label = cv::format("Inference time : %.2f ms", t); cv::putText(img, label, cv::Point(20, 40), FONT_FACE, FONT_SCALE, RED); cv::imshow("Output", img); cv::waitKey(0); return 0; } |
Tương tự, các bạn cũng có thể convert các model train từ Yolov7 như yolov7.py, yolov7-tiny.pt.
Tổng kết
Việc build project OpenCV C++ có những lợi ích như sau:
- Đóng gói thành chương trình dễ dàng hơn, người dùng cuối chỉ quen thuộc với .exe, không quen chạy từ file .py
- Dễ dàng tích hợp vào chương trình viết bằng C#
- Giấu source code, giấu data, không bị lộ như Python
- Tốc độ nhanh hơn Python
Chúc các bạn thành công