OpenCVを使ってバーコードを読み取ってみた

#Background
OpenCV4.5.3が2021年7月にリリースされました。ここ最近はDNNモジュールやG-APIのアップデートが多いのですが、それでもいくつかのマイナーな改変があったりします。今回はなんと意外になかったバーコードリーダーがOpenCV(contrib)で追加されました。
ここでは、設定方法から開発、検出結果まで書いてみようと思います。

#Method
まず最初に下記のリンクをクローンしてファイルをダウンロードします。
https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode
この中にある `sr.prototxt` `sr.caffemodel` はモデルとして使います。

次にバーコード読み取り用のオブジェクトを生成します。

```c++
    std::string prot_path = "opencv_3rdparty/sr.prototxt";
    std::string model_path = "opencv_3rdparty/sr.caffemodel";

    //バーコード文字列の配列
    std::vector< std::string > decode_info;
    //バーコードタイプの配列 例)EAN_8 EAN_13
    std::vector<cv::barcode::BarcodeType> decoded_type;
    //バーコードを検出した短形の頂点 (row )
    std::vector<cv::Point> corners;

    std::vector< std::vector<cv::Point> > rects;
    
    //検出用のオブジェクト生成
    auto bardet = cv::makePtr<cv::barcode::BarcodeDetector>(prot_path, model_path);
```

`detectAndDecode`を使って画像からバーコードを検出します。
バーコードの数は複数ある場合はその分のデータが配列として格納されています。

```c++
    // srcMat:入力画像(in)
    // decode_info:バーコード文字列(out)
    // decoded_type:バーコードタイプ(out)
    // corners:バーコード短形(out)
    bardet->detectAndDecode(srcMat,decode_info,decoded_type,corners);
```

あとは`decode_info`からバーコードの文字列が入っているのでここから抽出したり、`corners`からバーコードを検出した場所の座標点が入っているので`cv:rectangle`で四角形を描画すればいいと思います。

#Development

```c++
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/barcode.hpp>

/*
 * バーコード検出
 * @param  cv::Mat srcMat 入力画像
 * @return cv::Mat dstMat バーコードをマークした画像
 */
cv::Mat trackBarcode(cv::Mat srcMat){

    cv::Mat dstMat;
    srcMat.copyTo(dstMat);
    std::string prot_path = "opencv_3rdparty/sr.prototxt";
    std::string model_path = "opencv_3rdparty/sr.caffemodel";

    std::vector< std::string > decode_info;
    std::vector<cv::barcode::BarcodeType> decoded_type;
    std::vector<cv::Point> corners;
    std::vector< std::vector<cv::Point> > rects;
    
    //ここでバーコード検出
    auto bardet = cv::makePtr<cv::barcode::BarcodeDetector>(prot_path, model_path);
    bardet->detectAndDecode(srcMat,decode_info,decoded_type,corners);

    if (decode_info.empty()){
            std::cout <<  "バーコードなし" << std::endl;
    } else{
        
        // row:(図形の数) * 4 columns:2 で出力されるので
　　　　　　　　　　　　　　　　// 図形数 * 4 * 2 の 3次元にrectsへ格納
        std::vector<cv::Point> figs;
        for (int i = 0; i < corners.size(); i++){
            figs.push_back(corners[i]);
            if(i % 4 == 3){
                rects.push_back(figs);
                figs.erase(figs.begin(), figs.end());
            }
        }

        for(int i = 0; i < rects.size(); i++){
            //バーコードのある場所に緑で描画
            cv::rectangle(dstMat,
                            rects[i][1],
                            rects[i][3],
                            cv::Scalar(0,255,0),
                            1,
                            cv::LINE_AA);

            std::cout << rects[i] << std::endl;
            std::cout << decode_info[i] << std::endl;

            //バーコード文字列を赤字で描画
            cv::putText(dstMat,
                        decode_info[i],
                        rects[i][0],
                        cv::FONT_HERSHEY_SIMPLEX,
                        0.5, 
                        cv::Scalar(0,0,200), 
                        1, 
                        cv::LINE_AA
                        );

            std::cout << string_barcode_type(decoded_type[i]) << std::endl; 
        }
    }
    return dstMat;
}

int main(int argc, const char* argv[])
{
    std::string name = "Effect";
    cv::VideoCapture cap(0); //カメラを起動
    cap.set(cv::CAP_PROP_FRAME_WIDTH, 1280);
    cap.set(cv::CAP_PROP_FRAME_HEIGHT, 960);

    cv::namedWindow(name, cv::WINDOW_AUTOSIZE);

    int count = 0;
    while(true){
        cv::Mat frame;
        cap.read(frame); 
        
        //ここで画像処理
        cv::Mat dst = trackBarcode(frame);
        int key = cv::waitKey(5);
        if (key == 27){　 //`esc` キー
            break;
        } else if(key == 115){ //`s` キー
            cv::imwrite("output_save_" + std::to_string(count++) + ".png", dst);
        }

        cv::imshow(name, dst);
    }

    cv::destroyAllWindows();    
    return 0;
}


```

#Consequence

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/fb78ffd1-60fc-c3bd-c057-d5fd154a293a.png" width=50%>
高解像度のカメラであればバーコードの読み取りはできます。Mac内蔵のカメラを使っています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/e6e8a36a-f957-b093-d1ed-b9045d05715c.png" width=50%>
バーコードが複数あっても読み取れます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/587670f1-7136-97bb-5d8c-3b912aef5da7.png" width=50%>
大きすぎるとバーコードの文字列の抽出が不安定になります。
布なのでヨレヨレなのが原因、、、なはず。

# PostScript
記事の投稿が3ヶ月空いてしまった:tired_face: