From d775c14a67f5ee2efd447bc38feb06bb10b6faf8 Mon Sep 17 00:00:00 2001 From: Soham Date: Sun, 1 Dec 2024 21:12:32 +0530 Subject: [PATCH 1/4] feat: add cpp demo for wechatqrcode --- models/qrcode_wechatqrcode/CMakeLists.txt | 11 ++ models/qrcode_wechatqrcode/README.md | 19 +++ models/qrcode_wechatqrcode/demo.cpp | 175 ++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 models/qrcode_wechatqrcode/CMakeLists.txt create mode 100644 models/qrcode_wechatqrcode/demo.cpp diff --git a/models/qrcode_wechatqrcode/CMakeLists.txt b/models/qrcode_wechatqrcode/CMakeLists.txt new file mode 100644 index 00000000..823d7e17 --- /dev/null +++ b/models/qrcode_wechatqrcode/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.24.0) +project(opencv_zoo_qrcode_wechatqrcode) + +set(OPENCV_VERSION "4.10.0") +set(OPENCV_INSTALLATION_PATH "" CACHE PATH "Where to look for OpenCV installation") + +# Find OpenCV +find_package(OpenCV ${OPENCV_VERSION} REQUIRED HINTS ${OPENCV_INSTALLATION_PATH}) + +add_executable(demo demo.cpp) +target_link_libraries(demo ${OpenCV_LIBS}) diff --git a/models/qrcode_wechatqrcode/README.md b/models/qrcode_wechatqrcode/README.md index f310b48d..c4cc7a8a 100644 --- a/models/qrcode_wechatqrcode/README.md +++ b/models/qrcode_wechatqrcode/README.md @@ -9,6 +9,8 @@ Notes: ## Demo +### Python + Run the following command to try the demo: ```shell @@ -21,6 +23,23 @@ python demo.py --input /path/to/image -v python demo.py --help ``` +### C++ + +Install latest OpenCV and CMake >= 3.24.0 to get started with: + +```shell +# A typical and default installation path of OpenCV is /usr/local +cmake -B build -D OPENCV_INSTALLATION_PATH=/path/to/opencv/installation . +cmake --build build + +# detect on camera input +./build/demo +# detect on an image +./build/demo -i=/path/to/image -v +# get help messages +./build/demo -h +``` + ### Example outputs ![webcam demo](./example_outputs/wechat_qrcode_demo.gif) diff --git a/models/qrcode_wechatqrcode/demo.cpp b/models/qrcode_wechatqrcode/demo.cpp new file mode 100644 index 00000000..3b2c6a03 --- /dev/null +++ b/models/qrcode_wechatqrcode/demo.cpp @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include + +// function to visualize QR code detection results +cv::Mat visualize(cv::Mat image, const std::vector& results, + const std::vector& points, + cv::Scalar points_color = cv::Scalar(0, 255, 0), + cv::Scalar text_color = cv::Scalar(0, 255, 0), + double fps = -1) { + cv::Mat output = image.clone(); + + if (fps >= 0) { + cv::putText(output, "FPS: " + std::to_string(fps), cv::Point(0, 15), + cv::FONT_HERSHEY_SIMPLEX, 0.5, text_color); + } + + double fontScale = 0.5; + int fontSize = 1; + + for (size_t i = 0; i < results.size(); ++i) { + const auto& p = points[i]; + + // iterate through the mat to access points + for (int r = 0; r < p.rows; ++r) { + cv::Point point(p.at(r, 0), p.at(r, 1)); + cv::circle(output, point, 10, points_color, -1); + } + + // calculate QR code center + int qrcode_center_x = (p.at(0, 0) + p.at(2, 0)) / 2; + int qrcode_center_y = (p.at(0, 1) + p.at(2, 1)) / 2; + + // get text size + int baseline = 0; + cv::Size text_size = + cv::getTextSize(results[i], cv::FONT_HERSHEY_DUPLEX, fontScale, + fontSize, &baseline); + + // position text at the center of QR code + cv::Point text_pos(qrcode_center_x - text_size.width / 2, + qrcode_center_y + text_size.height / 2); + + // draw text + cv::putText(output, results[i], text_pos, cv::FONT_HERSHEY_DUPLEX, + fontScale, text_color, fontSize); + } + + return output; +} + +int main(int argc, char** argv) { + // argument parsing + cv::CommandLineParser parser( + argc, argv, + "{help h | | Show this help message.}" + "{input i | | Set path to the input image. Omit for using default camera.}" + "{detect_prototxt_path | detect_2021nov.prototxt | Set path to detect.prototxt.}" + "{detect_model_path | detect_2021nov.caffemodel | Set path to detect.caffemodel.}" + "{sr_prototxt_path | sr_2021nov.prototxt | Set path to sr.prototxt.}" + "{sr_model_path | sr_2021nov.caffemodel | Set path to sr.caffemodel.}" + "{backend_target bt | 0 | Choose one of the backend-target pairs to run this demo.}" + "{save s | false | Specify to save file with results (i.e. bounding box, confidence level). Invalid in case of camera input.}" + "{vis v | false | Specify to open a new window to show results. Invalid in case of camera input.}"); + + if (parser.has("help")) { + parser.printMessage(); + return 0; + } + + // backend-target pairs + const std::vector> backend_target_pairs = { + {cv::dnn::DNN_BACKEND_OPENCV, cv::dnn::DNN_TARGET_CPU}, + {cv::dnn::DNN_BACKEND_CUDA, cv::dnn::DNN_TARGET_CUDA}, + {cv::dnn::DNN_BACKEND_CUDA, cv::dnn::DNN_TARGET_CUDA_FP16}, + {cv::dnn::DNN_BACKEND_TIMVX, cv::dnn::DNN_TARGET_NPU}, + {cv::dnn::DNN_BACKEND_CANN, cv::dnn::DNN_TARGET_NPU}}; + + // get backend-target from arguments + int backend_target_index = parser.get("backend_target"); + if (backend_target_index < 0 || + backend_target_index >= backend_target_pairs.size()) { + std::cerr << "Invalid backend-target index" << std::endl; + return -1; + } + + // get paths + std::string detect_prototxt = parser.get("detect_prototxt_path"); + std::string detect_model = parser.get("detect_model_path"); + std::string sr_prototxt = parser.get("sr_prototxt_path"); + std::string sr_model = parser.get("sr_model_path"); + + // initialize wechatqrcode detector + cv::Ptr detector = + cv::makePtr( + detect_prototxt, detect_model, sr_prototxt, sr_model); + + // check if input is specified + std::string input_path = parser.get("input"); + bool save_result = parser.get("save"); + bool visualize_result = parser.get("vis"); + + if (!input_path.empty()) { + // image processing + cv::Mat image = cv::imread(input_path); + if (image.empty()) { + std::cerr << "Could not read the image" << std::endl; + return -1; + } + + std::vector results; + std::vector points; + results = detector->detectAndDecode(image, points); + + for (const auto& result : results) { + std::cout << result << std::endl; + } + + // visualize results + cv::Mat result_image = visualize(image, results, points); + + // save results if requested + if (save_result) { + cv::imwrite("result.jpg", result_image); + std::cout << "Results saved to result.jpg" << std::endl; + } + + // show visualization if requested + if (visualize_result) { + cv::imshow(input_path, result_image); + cv::waitKey(0); + } + } else { + // camera processing + cv::VideoCapture cap(0); + if (!cap.isOpened()) { + std::cerr << "Error opening camera" << std::endl; + return -1; + } + + cv::Mat frame; + cv::TickMeter tm; + + while (true) { + cap >> frame; + if (frame.empty()) { + std::cout << "No frames grabbed" << std::endl; + break; + } + + std::vector results; + std::vector points; + + tm.start(); + results = detector->detectAndDecode(frame, points); + tm.stop(); + + double fps = tm.getFPS(); + + // visualize results + cv::Mat result_frame = + visualize(frame, results, points, cv::Scalar(0, 255, 0), + cv::Scalar(0, 255, 0), fps); + cv::imshow("WeChatQRCode Demo", result_frame); + + tm.reset(); + + if (cv::waitKey(1) >= 0) break; + } + } + + return 0; +} From 0d0ec703496a3d7c42f52a29d6d7ce75ac22b5a7 Mon Sep 17 00:00:00 2001 From: Soham Date: Mon, 2 Dec 2024 14:39:26 +0530 Subject: [PATCH 2/4] refactor: change to class based --- models/qrcode_wechatqrcode/demo.cpp | 241 +++++++++++++++------------- 1 file changed, 126 insertions(+), 115 deletions(-) diff --git a/models/qrcode_wechatqrcode/demo.cpp b/models/qrcode_wechatqrcode/demo.cpp index 3b2c6a03..ad1659e6 100644 --- a/models/qrcode_wechatqrcode/demo.cpp +++ b/models/qrcode_wechatqrcode/demo.cpp @@ -4,55 +4,88 @@ #include #include -// function to visualize QR code detection results -cv::Mat visualize(cv::Mat image, const std::vector& results, - const std::vector& points, - cv::Scalar points_color = cv::Scalar(0, 255, 0), - cv::Scalar text_color = cv::Scalar(0, 255, 0), - double fps = -1) { - cv::Mat output = image.clone(); - - if (fps >= 0) { - cv::putText(output, "FPS: " + std::to_string(fps), cv::Point(0, 15), - cv::FONT_HERSHEY_SIMPLEX, 0.5, text_color); - } +class WeChatQRCode { + public: + WeChatQRCode(const std::string& detect_prototxt, + const std::string& detect_model, + const std::string& sr_prototxt, const std::string& sr_model, + int backend_target_index) + : backend_target_index_(backend_target_index) { + + const std::vector> backend_target_pairs = { + {cv::dnn::DNN_BACKEND_OPENCV, cv::dnn::DNN_TARGET_CPU}, + {cv::dnn::DNN_BACKEND_CUDA, cv::dnn::DNN_TARGET_CUDA}, + {cv::dnn::DNN_BACKEND_CUDA, cv::dnn::DNN_TARGET_CUDA_FP16}, + {cv::dnn::DNN_BACKEND_TIMVX, cv::dnn::DNN_TARGET_NPU}, + {cv::dnn::DNN_BACKEND_CANN, cv::dnn::DNN_TARGET_NPU}}; + + if (backend_target_index_ < 0 || + backend_target_index_ >= backend_target_pairs.size()) { + throw std::invalid_argument("Invalid backend-target index"); + } - double fontScale = 0.5; - int fontSize = 1; + // initialize detector + detector_ = cv::makePtr( + detect_prototxt, detect_model, sr_prototxt, sr_model); + } - for (size_t i = 0; i < results.size(); ++i) { - const auto& p = points[i]; + std::pair, std::vector> detect( + const cv::Mat& image) { + std::vector results; + std::vector points; + results = detector_->detectAndDecode(image, points); + return {results, points}; + } - // iterate through the mat to access points - for (int r = 0; r < p.rows; ++r) { - cv::Point point(p.at(r, 0), p.at(r, 1)); - cv::circle(output, point, 10, points_color, -1); + cv::Mat visualize(const cv::Mat& image, + const std::vector& results, + const std::vector& points, + cv::Scalar points_color = cv::Scalar(0, 255, 0), + cv::Scalar text_color = cv::Scalar(0, 255, 0), + double fps = -1) const { + cv::Mat output = image.clone(); + + if (fps >= 0) { + cv::putText(output, "FPS: " + std::to_string(fps), cv::Point(0, 15), + cv::FONT_HERSHEY_SIMPLEX, 0.5, text_color); } - // calculate QR code center - int qrcode_center_x = (p.at(0, 0) + p.at(2, 0)) / 2; - int qrcode_center_y = (p.at(0, 1) + p.at(2, 1)) / 2; + double fontScale = 0.5; + int fontSize = 1; + + for (size_t i = 0; i < results.size(); ++i) { + const auto& p = points[i]; + + for (int r = 0; r < p.rows; ++r) { + cv::Point point(p.at(r, 0), p.at(r, 1)); + cv::circle(output, point, 10, points_color, -1); + } - // get text size - int baseline = 0; - cv::Size text_size = - cv::getTextSize(results[i], cv::FONT_HERSHEY_DUPLEX, fontScale, - fontSize, &baseline); + int qrcode_center_x = (p.at(0, 0) + p.at(2, 0)) / 2; + int qrcode_center_y = (p.at(0, 1) + p.at(2, 1)) / 2; - // position text at the center of QR code - cv::Point text_pos(qrcode_center_x - text_size.width / 2, - qrcode_center_y + text_size.height / 2); + int baseline = 0; + cv::Size text_size = + cv::getTextSize(results[i], cv::FONT_HERSHEY_DUPLEX, fontScale, + fontSize, &baseline); - // draw text - cv::putText(output, results[i], text_pos, cv::FONT_HERSHEY_DUPLEX, - fontScale, text_color, fontSize); + cv::Point text_pos(qrcode_center_x - text_size.width / 2, + qrcode_center_y + text_size.height / 2); + + cv::putText(output, results[i], text_pos, cv::FONT_HERSHEY_DUPLEX, + fontScale, text_color, fontSize); + } + + return output; } - return output; -} + private: + int backend_target_index_; + cv::Ptr detector_; +}; int main(int argc, char** argv) { - // argument parsing + cv::CommandLineParser parser( argc, argv, "{help h | | Show this help message.}" @@ -62,113 +95,91 @@ int main(int argc, char** argv) { "{sr_prototxt_path | sr_2021nov.prototxt | Set path to sr.prototxt.}" "{sr_model_path | sr_2021nov.caffemodel | Set path to sr.caffemodel.}" "{backend_target bt | 0 | Choose one of the backend-target pairs to run this demo.}" - "{save s | false | Specify to save file with results (i.e. bounding box, confidence level). Invalid in case of camera input.}" - "{vis v | false | Specify to open a new window to show results. Invalid in case of camera input.}"); + "{save s | false | Specify to save file with results.}" + "{vis v | false | Specify to open a new window to show results.}"); if (parser.has("help")) { parser.printMessage(); return 0; } - // backend-target pairs - const std::vector> backend_target_pairs = { - {cv::dnn::DNN_BACKEND_OPENCV, cv::dnn::DNN_TARGET_CPU}, - {cv::dnn::DNN_BACKEND_CUDA, cv::dnn::DNN_TARGET_CUDA}, - {cv::dnn::DNN_BACKEND_CUDA, cv::dnn::DNN_TARGET_CUDA_FP16}, - {cv::dnn::DNN_BACKEND_TIMVX, cv::dnn::DNN_TARGET_NPU}, - {cv::dnn::DNN_BACKEND_CANN, cv::dnn::DNN_TARGET_NPU}}; - - // get backend-target from arguments - int backend_target_index = parser.get("backend_target"); - if (backend_target_index < 0 || - backend_target_index >= backend_target_pairs.size()) { - std::cerr << "Invalid backend-target index" << std::endl; - return -1; - } - // get paths std::string detect_prototxt = parser.get("detect_prototxt_path"); std::string detect_model = parser.get("detect_model_path"); std::string sr_prototxt = parser.get("sr_prototxt_path"); std::string sr_model = parser.get("sr_model_path"); + int backend_target_index = parser.get("backend_target"); - // initialize wechatqrcode detector - cv::Ptr detector = - cv::makePtr( - detect_prototxt, detect_model, sr_prototxt, sr_model); - - // check if input is specified + // input check std::string input_path = parser.get("input"); bool save_result = parser.get("save"); bool visualize_result = parser.get("vis"); - if (!input_path.empty()) { - // image processing - cv::Mat image = cv::imread(input_path); - if (image.empty()) { - std::cerr << "Could not read the image" << std::endl; - return -1; - } - - std::vector results; - std::vector points; - results = detector->detectAndDecode(image, points); + try { + WeChatQRCode qrDetector(detect_prototxt, detect_model, sr_prototxt, + sr_model, backend_target_index); - for (const auto& result : results) { - std::cout << result << std::endl; - } - - // visualize results - cv::Mat result_image = visualize(image, results, points); + if (!input_path.empty()) { + // process image + cv::Mat image = cv::imread(input_path); + if (image.empty()) { + std::cerr << "Could not read the image" << std::endl; + return -1; + } - // save results if requested - if (save_result) { - cv::imwrite("result.jpg", result_image); - std::cout << "Results saved to result.jpg" << std::endl; - } + auto [results, points] = qrDetector.detect(image); + for (const auto& result : results) { + std::cout << result << std::endl; + } - // show visualization if requested - if (visualize_result) { - cv::imshow(input_path, result_image); - cv::waitKey(0); - } - } else { - // camera processing - cv::VideoCapture cap(0); - if (!cap.isOpened()) { - std::cerr << "Error opening camera" << std::endl; - return -1; - } + cv::Mat result_image = qrDetector.visualize(image, results, points); - cv::Mat frame; - cv::TickMeter tm; + if (save_result) { + cv::imwrite("result.jpg", result_image); + std::cout << "Results saved to result.jpg" << std::endl; + } - while (true) { - cap >> frame; - if (frame.empty()) { - std::cout << "No frames grabbed" << std::endl; - break; + if (visualize_result) { + cv::imshow(input_path, result_image); + cv::waitKey(0); + } + } else { + // process camera + cv::VideoCapture cap(0); + if (!cap.isOpened()) { + std::cerr << "Error opening camera" << std::endl; + return -1; } - std::vector results; - std::vector points; + cv::Mat frame; + cv::TickMeter tm; - tm.start(); - results = detector->detectAndDecode(frame, points); - tm.stop(); + while (true) { + cap >> frame; + if (frame.empty()) { + std::cout << "No frames grabbed" << std::endl; + break; + } - double fps = tm.getFPS(); + auto [results, points] = qrDetector.detect(frame); + tm.start(); + double fps = tm.getFPS(); + tm.stop(); - // visualize results - cv::Mat result_frame = - visualize(frame, results, points, cv::Scalar(0, 255, 0), - cv::Scalar(0, 255, 0), fps); - cv::imshow("WeChatQRCode Demo", result_frame); + cv::Mat result_frame = qrDetector.visualize( + frame, results, points, cv::Scalar(0, 255, 0), + cv::Scalar(0, 255, 0), fps); + cv::imshow("WeChatQRCode Demo", result_frame); - tm.reset(); + tm.reset(); - if (cv::waitKey(1) >= 0) break; + if (cv::waitKey(1) >= 0) break; + } } + + } catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << std::endl; + return -1; } return 0; From 41faeecb58a2c7b5df6e974f670eb1f20f4cc9b3 Mon Sep 17 00:00:00 2001 From: Soham Date: Mon, 2 Dec 2024 22:36:25 +0530 Subject: [PATCH 3/4] fix: make cpp11 compatible --- models/qrcode_wechatqrcode/demo.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/models/qrcode_wechatqrcode/demo.cpp b/models/qrcode_wechatqrcode/demo.cpp index ad1659e6..5f915a83 100644 --- a/models/qrcode_wechatqrcode/demo.cpp +++ b/models/qrcode_wechatqrcode/demo.cpp @@ -127,7 +127,10 @@ int main(int argc, char** argv) { return -1; } - auto [results, points] = qrDetector.detect(image); + std::pair, std::vector> detectionResult = qrDetector.detect(image); + auto& results = detectionResult.first; + auto& points = detectionResult.second; + for (const auto& result : results) { std::cout << result << std::endl; } @@ -161,7 +164,10 @@ int main(int argc, char** argv) { break; } - auto [results, points] = qrDetector.detect(frame); + std::pair, std::vector> detectionResult = qrDetector.detect(frame); + auto& results = detectionResult.first; + auto& points = detectionResult.second; + tm.start(); double fps = tm.getFPS(); tm.stop(); From 0377dfe45da053dd7de055877f47e95b46271a87 Mon Sep 17 00:00:00 2001 From: Soham Date: Fri, 13 Dec 2024 17:49:14 +0530 Subject: [PATCH 4/4] fix: add opencv_contrib in readme --- models/qrcode_wechatqrcode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/qrcode_wechatqrcode/README.md b/models/qrcode_wechatqrcode/README.md index c4cc7a8a..786490b3 100644 --- a/models/qrcode_wechatqrcode/README.md +++ b/models/qrcode_wechatqrcode/README.md @@ -25,7 +25,7 @@ python demo.py --help ### C++ -Install latest OpenCV and CMake >= 3.24.0 to get started with: +Install latest OpenCV (with opencv_contrib) and CMake >= 3.24.0 to get started with: ```shell # A typical and default installation path of OpenCV is /usr/local