【opencv】示例-barcode.cpp 条形码检测和解码

时间:2024-04-07 07:56:46

1ce9180bf8c47adb82523917f999eb40.png

1c74df5541a26c4098cec9e129df0914.png

702d02bf7722514270c75a9636335253.png

#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; // 标记是否只进行检测而不解码


    // 清理函数,用于清除存储结果的vector
    void 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; // 从摄像头读取一帧到frame
        cout << "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); // 如果帧是灰度图像,则转换为BGR
            TickMeter 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(); // 如果未提供输入文件则启动实时摄像头检测
    else
        return app.imageBarCodeDetect(in_file, out_file); // 否则进行单帧图像检测
}

bardet->detectAndDecodeWithType(frame, decode_info, decode_type, corners);

5e2ece7e4f0977f912342d32acc88289.png