先日、SPRESENSEでQRコードが扱えないの?というスタックオーバーフローの質問が目にとまりました。QRコードが扱えるといろいろな場面で使えそうだなと思い立ち、前から存在を知っていた”quirc”というライブラリをArduinoライブラリとしてSPRESENSEに移植してみました。その経緯と結果をご紹介したいと思います。
Quirc とは?
Quirc とは、Daniel Beer さんが開発した組み込み向けのQRコードをデコードするライブラリです。モノクロ画像を与えるとデコードした情報を返してくれます。10年くらい前に作られたものなので、デモはlibjpegやらL4V2などが使われており複雑そうですが、ライブラリ本体はいたってシンプルです。
すでにESP32にも、alvarowolfx さんによって移植されており、ArduCAM で利用できるようになっています。
両方、SPRESENSEに移植をしてみたtokoro
、alvarowolfxさんのライブラリは最適化されているようで、デコードがオリジナルのものより高速でした。ですので、alvarowolfx さんのものをベースに使うことにしました。
Quirc を移植する
Alvarowolfxさんのリポジトリの中の 「src」以下のディレクトリのコードをライブラリにしました。ライブラリは次の Github にあげていますので、ZIPでダウンロードしてArduino IDEでインストールしてみてください。SPRESENSE特有のAPIは使っていないのでメモリが十分にあるArduino系のボードでも使えると思います。
以下移植の際の細かい話なので、興味がある方のみ読んでください。
ESP32用のライブラリ”ESP32QRCodeReader”はカメラ設定含めてライブラリ化していますが、SPRESENSEの場合はカメラは簡単に扱えるので、直接 quirc を呼ぶ形にしました。
このライブラリは最適化のために、オリジナルの quirc と違って openmv のAPIを使っています。しかし、SPRESENSEは OpenMV をサポートしていないため変更が必要でした。また、メモリ確保にESP32特有のAPIが使われていたためANSI-C標準の関数に変更しています。
OpenMV の定義は、数値演算関連の呼び出しが定義されています。そこで、OpenMV API の代わりに標準 math ライブラリを呼ぶ形に変更しています。(OpenMV の定義を無効化し、標準 mathライブラリの定義を有効にするだけです) この部分を ARM のDSPライブラリの関数に変更すればより高速化が期待できるかも知れません。
実際に使用してみる
quirc ライブラリを、次のSPRESENSEのサンプルコードで試してみました。
#include <Camera.h>
#include "quirc.h"
#define USE_LCD
#ifdef USE_LCD
#include "Adafruit_ILI9341.h"
#define TFT_DC 9
#define TFT_CS -1
#define TFT_RST 8
Adafruit_ILI9341 tft = Adafruit_ILI9341(&SPI, TFT_DC, TFT_CS, TFT_RST);
#endif
struct quirc* qr;
struct quirc_code code;
struct quirc_data data;
uint8_t *image;
int w, h;
void CamCB(CamImage img) {
if (!img.isAvailable()) return;
#ifdef USE_LCD
tft.drawRGBBitmap(0, 20, (uint16_t *)img.getImgBuff(), 320, 220);
#endif
image = quirc_begin(qr, &w, &h);
if (w != CAM_IMGSIZE_QVGA_H || h != CAM_IMGSIZE_QVGA_V) {
Serial.println("configration error");
while(1); // fatal error to enter the infinite loop (stop process)
}
// copy gray scale image
uint16_t* rgb_image = (uint16_t*)img.getImgBuff();
for (int n = 0; n < w*h; ++n) {
uint16_t pix = rgb_image[n];
image[n] = (pix & 0x7E0) >> 5; // extract g image
}
quirc_end(qr);
int num_codes = quirc_count(qr);
String str;
Serial.println("num codes: " + String(num_codes));
if (num_codes > 0) {
for (int i = 0; i < num_codes; i++) {
quirc_decode_error_t err;
quirc_extract(qr, i, &code);
err = quirc_decode(&code, &data);
if (err) str = "DECODE FAILED";
else str = data.payload;
}
} else {
str = "NO QR CODE";
}
Serial.println(str);
#ifdef USE_LCD
tft.fillRect(0,0,320,20,ILI9341_BLACK);
tft.setCursor(0,0);
tft.println(str);
#endif
}
void setup() {
Serial.begin(115200);
String ver = quirc_version();
Serial.println("quirc version: " + ver);
#ifdef USE_LCD
tft.begin();
tft.setRotation(3);
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_YELLOW);
tft.setTextSize(2);
#endif
theCamera.begin(1, CAM_VIDEO_FPS_15,
CAM_IMGSIZE_QVGA_H, CAM_IMGSIZE_QVGA_V, CAM_IMAGE_PIX_FMT_RGB565);
theCamera.startStreaming(true, CamCB);
qr = quirc_new();
if (qr == NULL) {
Serial.println("can't create quirc object");
return;
}
if (quirc_resize(qr, 320, 240) < 0) {
Serial.println("Failed to allocate video memory");
return;
}
}
void loop() { }
実際に動作している様子を移してみました。速度もそれほど遅くないため実用には十分使えるレベルだと思います。
使ってみた感想
quirc ライブラリは非常に出来が良く、想像以上に簡単に移植ができました。また画像から簡単にデコードできることに驚きました。認識速度も早くて、私がスマホで使っているQRコードアプリよりも使えそうです。流通系のアプリケーションに活用できそうですね。皆さんもぜひ試してみてください。