2021年7月24日 星期六

在 linux 系統下簡單解碼 qrcode, 中文也可以

1. 使用 nanojpeg 先將 Jpeg 解成 bitmap, 到官網 http://keyj.emphy.de/nanojpeg 下載原始碼 nanojpeg.c :

      http://svn.emphy.de/nanojpeg/trunk/nanojpeg/nanojpeg.c

2. 接著用 quirc 將 bitmap 內的 qrcode 解碼, 到官網 https://github.com/dlbeer/quirc 下載所有原始碼:

     https://github.com/dlbeer/quirc/tree/master/lib 

3. 將上述下載的檔案全放入一個專案目錄內, 編寫一個簡單的 Makefile, 方便編譯及執行程式:

opencv = `pkg-config --cflags --libs opencv4`
LIBS = decode.o identify.o quirc.o version_db.o
run: a.out
    ./$<


a.out: qr.o $(LIBS)
    g++ $^ $(opencv)  -lfreetype  -o $@
%.o : %.c
    gcc -c $<
%.o : %.cpp
    g++ $(opencv)  -I/usr/include/freetype2 -std=c++17 -c $<
clean:
    rm -f a.out *.o
   
opencv-dev:
    sudo apt-get update
    sudo apt-get  install   libopencv-dev   libfreetype-dev

若尚未安裝好 libopencv 或 libfreetype 程式庫, 可以先執行 make  opencv-dev 安裝它


4. 最後, 用手機對準 qrcode 拍照存成檔案 sample.jpg,  寫個 c++ 程式 qr.cpp 測試一下:

#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/freetype.hpp>
#include "nanojpeg.c"
#include "quirc.h"
using namespace std;

int  main( ) {
    FILE *fin = fopen("sample.jpg", "rb");
    if (fin== nullptr) return 1;

    fseek(fin, 0L, SEEK_END);
    long len = ftell(fin);
    unsigned char *jpeg = (unsigned char *)malloc(len);
    if (jpeg == nullptr) {
        fclose(fin);
        return 2;
    } else {
        fseek(fin, 0L, SEEK_SET);
        fread(jpeg, 1, len, fin);
        fclose(fin);
    }

    njInit();
    if (njDecode(jpeg, len) == NJ_OK) free(jpeg);
    else {
        free(jpeg);
        return 3;
    }

    auto  font = cv::freetype::createFreeType2();// use freetype2, which is a pointer
    font->loadFontData("/usr/share/fonts/opentype/noto/NotoSerifCJK-Bold.ttc", 0);
    auto xRotate = [ ](const cv::Mat &src,
        double sx = 1.0,
        double sy = 1.0,
        int deg = 0) {// // scale first, then rotate, if deg > 0, counterclockwise
        cv::Mat dst;
        cv::resize(src, dst, cv::Size(), sx, sy, cv::INTER_LINEAR);
        if (deg != 0) cv::warpAffine(dst,
            dst,
            cv::getRotationMatrix2D(
                cv::Point2f(dst.cols/2.0, dst.rows/2.0),
                deg,
                1.0
            ),
            dst.size()
        );
        return dst;
    };
    auto toGrey = [ ](const cv::Mat &src) { // conver to 8 bits Y only
        cv::Mat dst;
        cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY);
        return dst;
    };
    auto rgbBMP = xRotate(cv::Mat(njGetHeight(), njGetWidth(), CV_8UC3, njGetImage()),
        1.0/6,
        1.0/4,
        0
    );
    quirc *qr = quirc_new();
    if (qr) {
        auto grey = toGrey(rgbBMP);
        if (quirc_resize(qr, grey.cols, grey.rows) == QUIRC_SUCCESS) {
            int width  = grey.cols;
            int height = grey.rows;
            uint8_t *feed = quirc_begin(qr, &width, &height);
            uint8_t *greyLine = grey.ptr<uint8_t>(0); // line begin
            for (int j = 0; j < height; j++) {// scan every line
                for (int i = 0; i < width; i ++) *feed ++ = *(greyLine + i);// pixel feed one bye one
                greyLine += grey.cols; // next line begin
            }
            quirc_end(qr);
            int count = quirc_count(qr);// number of qrcode

            for (int i = 0; i < count; i ++) {
                quirc_data qData;
                quirc_code qCode;
                quirc_extract(qr, i, &qCode);
                int err = quirc_decode(&qCode, &qData);
                if (err == QUIRC_ERROR_DATA_ECC) {
                    quirc_flip(&qCode);
                    err = quirc_decode(&qCode, &qData);
                }
                if (err == QUIRC_SUCCESS) { // sucess
                    for (int j = 0; j < 4; j++) { // draw outline
                        auto &lba = qCode.corners[j];// line begin alias
                        auto &lea = qCode.corners[(j + 1) % 4];// line end alias
                        cv::line(rgbBMP,
                            cv::Point(lba.x, lba.y),
                            cv::Point(lea.x, lea.y),
                            cv::Scalar(0, 255, 0),
                            2
                        );
                    }                   
                    font->putText(rgbBMP,
                        string((char *)qData.payload),
                        cv::Point(0, grey.rows - 24 * (i + 1)),
                        24, // font height
                        cv::Scalar(0, 0, 255),// red color
                        -1, // solid line when thickness is negative
                        16, // to smooth
                        true
                    );
                    printf("[qrcode %d]-> %s <-[length = %d]\n", i, qData.payload, qData.payload_len);
                }
            }
        }
        quirc_destroy(qr);
    }

    njDone();
    cv::imshow("qrcode decode", rgbBMP);
    cv::waitKey(0);// show image, press any key to exit
    return 0;
}

執行 make run 看能不能解出 qrcode. 若不行, 可以調整 xRotate( ) 的長/寬放大率再試試看.

後記: 使用 freetype2 繪製文字(putText)時, 傳進去的影像型態 cv::Mat 必須使用 CV_8UC3 格式

   

簡單 c 程式碼, 根據五行八卦相生相剋推斷吉凶

#include "stdio.h" // 五行: //               木2 //      水1           火3 //         金0     土4 // // 先天八卦 vs 五行 //                    ...