#include <iostream> // 引入标准输入输出流库
#include "opencv2/objdetect.hpp" // 引入OpenCV物体检测库
#include "opencv2/imgproc.hpp" // 引入OpenCV图像处理库
#include "opencv2/highgui.hpp" // 引入OpenCV高层GUI库using namespace cv; // 使用OpenCV命名空间
using namespace std; // 使用标准库命名空间// 预定义一些颜色常量供后续使用
static const Scalar greenColor(0, 255, 0); // 绿色
static const Scalar redColor(0, 0, 255); // 红色
static const Scalar yellowColor(0, 255, 255); // 黄色
// 生成随机颜色的函数
static Scalar randColor()
{RNG &rng = theRNG(); // 获取随机数发生器的引用return Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)); // 生成随机颜色
}//==============================================================================// TheApp结构体,其中封装了应用程序的主要逻辑
struct TheApp
{Ptr<barcode::BarcodeDetector> bardet; // 指向条码检测器的智能指针//! [output]vector<Point> corners; // 存储检测到的条码的角点vector<string> decode_info; // 存储条码的解码信息vector<string> decode_type; // 存储条码的类型//! [output]bool detectOnly; // 标记是否只进行检测而不解码// 清理函数,用于清除存储结果的vectorvoid cleanup()
{corners.clear();decode_info.clear();decode_type.clear();}// 返回当前模式对应的字符串inline string modeString() const
{return detectOnly ? "<detect>" : "<detectAndDecode>";}// 绘制检测和解码结果的函数void drawResults(Mat &frame) const
{//! [visualize]for (size_t i = 0; i < corners.size(); i += 4) // 遍历所有角点{const size_t idx = i / 4; // 计算当前idx// 判断当前条码是否被成功解码const bool isDecodable = idx < decode_info.size()&& idx < decode_type.size()&& !decode_type[idx].empty();const Scalar lineColor = isDecodable ? greenColor : redColor; // 根据是否解码成功选择颜色// 绘制条码轮廓的矩形vector<Point> contour(corners.begin() + i, corners.begin() + i + 4);const vector< vector<Point> > contours {contour};drawContours(frame, contours, 0, lineColor, 1); // 画轮廓// 绘制轮廓的四个角点for (size_t j = 0; j < 4; j++)circle(frame, contour[j], 2, randColor(), -1);// 如果解码成功,写出解码文本if (isDecodable){ostringstream buf;buf << "[" << decode_type[idx] << "] " << decode_info[idx];putText(frame, buf.str(), contour[1], FONT_HERSHEY_COMPLEX, 0.8, yellowColor, 1);}}//! [visualize]}// 绘制FPS信息的函数void drawFPS(Mat &frame, double fps) const
{ostringstream buf;buf << modeString()<< " (" << corners.size() / 4 << "/" << decode_type.size() << "/" << decode_info.size() << ") "<< cv::format("%.2f", fps) << " FPS "; // 组织FPS显示的文本内容putText(frame, buf.str(), Point(25, 25), FONT_HERSHEY_COMPLEX, 0.8, redColor, 2); // 将FPS信息绘制到帧上}// 调用解码函数的中间处理函数inline void call_decode(Mat &frame)
{cleanup(); // 清理之前的结果if (detectOnly){//! [detect]bardet->detectMulti(frame, corners); // 仅进行检测,不解码//! [detect]}else{//! [detectAndDecode]bardet->detectAndDecodeWithType(frame, decode_info, decode_type, corners); // 检测并解码,获取类型信息//! [detectAndDecode]}}// 实时视频流中条码检测的函数int liveBarCodeDetect()
{VideoCapture cap(0); // 创建视频捕获对象,并打开默认摄像头if (!cap.isOpened()) // 如果摄像头没有成功打开{cout << "Cannot open a camera" << endl; // 输出错误信息return 2; // 返回错误码2}Mat frame; // 创建Mat对象用于存储捕获的帧Mat result; // 创建另一个Mat对象用于存放处理结果cap >> frame; // 从摄像头读取一帧到framecout << "Image size: " << frame.size() << endl; // 输出帧的尺寸// 输出提示信息cout << "Press 'd' to switch between <detect> and <detectAndDecode> modes" << endl;cout << "Press 'ESC' to exit" << endl;for (;;) // 无限循环读取帧并处理{cap >> frame; // 捕获一帧if (frame.empty()) // 如果帧为空,说明视频流结束{cout << "End of video stream" << endl; // 输出视频流结束信息break; // 跳出循环}if (frame.channels() == 1)cvtColor(frame, frame, COLOR_GRAY2BGR); // 如果帧是灰度图像,则转换为BGRTickMeter timer; // 创建计时器timer.start(); // 开始计时call_decode(frame); // 调用解码函数处理当前帧timer.stop(); // 停止计时drawResults(frame); // 绘制检测结果drawFPS(frame, timer.getFPS()); // 显示帧率(FPS)imshow("barcode", frame); // 显示带有条码信息的帧const char c = (char)waitKey(1); // 等待1ms,并读取用户按键if (c == 'd') // 如果按下'd'键{detectOnly = !detectOnly; // 切换检测模式cout << "Mode switched to " << modeString() << endl; // 输出当前模式}else if (c == 27) // 如果按下'ESC'键(ASCII码为27){cout << "'ESC' is pressed. Exiting..." << endl; // 输出退出信息break; // 跳出循环终止程序}}return 0; // 正常退出返回0}// 静态图像中条码检测的函数int imageBarCodeDetect(const string &in_file, const string &out_file)
{Mat frame = imread(in_file, IMREAD_COLOR); // 读入图像文件cout << "Image size: " << frame.size() << endl; // 输出图像尺寸cout << "Mode is " << modeString() << endl; // 输出当前模式const int count_experiments = 100; // 设置实验次数TickMeter timer; // 创建计时器for (size_t i = 0; i < count_experiments; i++){timer.start(); // 开始计时call_decode(frame); // 调用解码函数处理图像timer.stop(); // 停止计时}cout << "FPS: " << timer.getFPS() << endl; // 输出平均帧率(FPS)drawResults(frame); // 绘制检测结果if (!out_file.empty()) // 如果输出文件名不为空{cout << "Saving result: " << out_file << endl; // 输出保存文件信息imwrite(out_file, frame); // 将结果图像保存到文件}imshow("barcode", frame); // 显示结果图像cout << "Press any key to exit ..." << endl; // 输出退出提示waitKey(0); // 等待用户按键退出return 0; // 正常退出返回0}
};//==============================================================================int main(int argc, char **argv)
{// 命令行参数定义const string keys = "{h help ? | | print help messages }""{i in | | input image path (also switches to image detection mode) }""{detect | false | detect 1D barcode only (skip decoding) }""{o out | | path to result file (only for single image decode) }""{sr_prototxt| | super resolution prototxt path }""{sr_model | | super resolution model path }";CommandLineParser cmd_parser(argc, argv, keys); // 创建命令行解析对象cmd_parser.about("This program detects the 1D barcodes from camera or images using the OpenCV library."); // 程序介绍if (cmd_parser.has("help")) // 如果用户请求帮助信息{cmd_parser.printMessage(); // 打印帮助信息return 0; // 并退出程序}// 提取命令行指定的参数const string in_file = cmd_parser.get<string>("in");const string out_file = cmd_parser.get<string>("out");const string sr_prototxt = cmd_parser.get<string>("sr_prototxt");const string sr_model = cmd_parser.get<string>("sr_model");if (!cmd_parser.check()) // 检查参数解析是否有误{cmd_parser.printErrors(); // 打印错误信息return -1; // 有误则返回-1}TheApp app; // 创建应用程序实例app.detectOnly = cmd_parser.has("detect") && cmd_parser.get<bool>("detect"); // 设置检测模式//! [initialize]try // 尝试初始化条码检测器{app.bardet = makePtr<barcode::BarcodeDetector>(sr_prototxt, sr_model); // 使用超分辨率模型和配置文件创建条码检测器对象}catch (const std::exception& e) // 如果初始化失败则捕获异常{// 输出错误信息提示用户下载并放置相应文件cout <<"\n---------------------------------------------------------------\n""Failed to initialize super resolution.\n""Please, download 'sr.*' from\n""https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode\n""and put them into the current directory.\n""Or you can leave sr_prototxt and sr_model unspecified.\n""---------------------------------------------------------------\n";cout << e.what() << endl; // 打印异常信息return -1; // 初始化失败则返回-1}//! [initialize]// 根据命令行参数执行相应的检测函数if (in_file.empty())return app.liveBarCodeDetect(); // 如果未提供输入文件则启动实时摄像头检测elsereturn app.imageBarCodeDetect(in_file, out_file); // 否则进行单帧图像检测
}
bardet->detectAndDecodeWithType(frame, decode_info, decode_type, corners);