目前能在网上找到的二维码资料,都是编码居多,也就是从一串字符串生成二维码(常用算法如 QRCode),而解码识别方面的参考教程比较少。二维码解码库有 Quirc、Zbar 等,更多的开发者是用在安卓、OpenCV 和 Python 等高级环境上,对于 MCU 级别的使用,并且基本是经过封装后的实现,灵活性较差。于是便想着自己从头使用 Zbar 库的接口,实现图片中的二维码检测及识别。
测试环境
开发板:STM32F429I Disco
CPU:Arm® 32-bit Cortex®-M4 180 MHz
内存:2Mbytes Flash, 256KMbytes RAM, 64-Mbit 外部 SDRAM
软件:RT-Thread 操作系统
实现过程
这里说明一下,Zbar 库要求输入的是灰度图像数据(也就是黑白照),每个像素用一个字节表示灰度,从 0x00 全白到 0xff 全黑,有了图像数据,再传入图像的宽高便能解码了。
通过 Zbar 库识别二维码,大致可以分为下面 3 个过程:
1.创建配置 image_scanner、zbar_image 对象,并设置图像信息
2.扫描图像并识别
3.提取解码结果
我写了一个函数,能够直接读取 bmp 8 位灰度图片(因为 bmp 文件包含了图像数据便于处理),并打印输出其中的二维码识别结果:
#include <rtthread.h>
#include <rtdevice.h>
#include "zbar.h"
#define BITMAP_IMG_OFFSET 0x0A
#define BITMAP_IMG_WIDTH_OFFSET 0x12
#define BITMAP_IMG_HEIGHT_OFFSET 0x16
#define BITMAP_IMG_FILE_SIZE_OFFSET 0x02
zbar_image_scanner_t *rt_scanner = NULL;
void qr_bitmap_decoder(const rt_uint8_t *img_buffer)
{
/* create a reader */
rt_scanner = zbar_image_scanner_create();
/* configure the reader */
zbar_image_scanner_set_config(rt_scanner, 0, ZBAR_CFG_ENABLE, 1);
/* obtain image data */
void *raw = (void *)img_buffer;
int img_width = *(int *)(&img_buffer[BITMAP_IMG_WIDTH_OFFSET]);
int img_height = *(int *)(&img_buffer[BITMAP_IMG_HEIGHT_OFFSET]);
int img_offset = *(int *)(&img_buffer[BITMAP_IMG_OFFSET]);
int file_size = *(int *)(&img_buffer[BITMAP_IMG_FILE_SIZE_OFFSET]);
rt_kprintf("img_info: width=%d, height=%d, file_size=%d\n", img_width, img_height, file_size);
/* wrap image data */
zbar_image_t *image = zbar_image_create();
zbar_image_set_format(image, *(int*)"Y800");
zbar_image_set_size(image, img_width, img_height);
zbar_image_set_data(image, (void *)(&img_buffer[img_offset]), img_width * img_height, zbar_image_free_data);
/* scan the image for barcodes */
int n = zbar_scan_image(rt_scanner, image);
/* extract results */
const zbar_symbol_t *symbol = zbar_image_first_symbol(image);
for(; symbol; symbol = zbar_symbol_next(symbol)) {
/* do something useful with results */
zbar_symbol_type_t typ = zbar_symbol_get_type(symbol);
const char *data = zbar_symbol_get_data(symbol);
rt_kprintf("decoded %s symbol "%s"\n",
zbar_get_symbol_name(typ), data);
}
/* clean up */
zbar_image_destroy(image);
zbar_image_scanner_destroy(rt_scanner);
}
运行结果
来看下实测的识别效果,RT-Thread 提供了 finsh Shell,可以很方便地使用命令行的方式运行写好的测试例程:
样本1(图片尺寸 256256,纯二维码):
样本2(图片尺寸 480360,摄像头拍摄):