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 格式