概况
二维码又称二维条码,常见的二维码为QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的Bar Code条形码能存更多的信息,也能表示更多的数据类型。详细的可以参考二维码介绍链接 https://cli.im/news/help/10601 如今二维码和一维码存在于我们生活的每一个角落,由于智能手机的普及几乎每个地方都会用,二维码和一维码的存在减少了大量的人工输入文字和读取文字的操作。
本文挑选的识别和生成二维码/条形码库的为(zbar和qrencode):
zbar 的github地址: https://github.com/JetLinWork/ZBar
+这个是官网也有tar格式的包提供下载: http://zbar.sourceforge.net/download.html
qrencode 的github地址: https://github.com/JetLinWork/libqrencode
Zbar : 二维码识别
关于zbar的 交叉编译可以参照文章 https://blog.youkuaiyun.com/Guet_Kite/article/details/78881318 。一下是我使用瑞芯微平台的rv1126的交叉编译文档参考。
# 下载源码
wget --no-check-certificate https://nchc.dl.sourceforge.net/project/zbar/zbar/0.10/zbar-0.10.tar.bz2
# 解压
tar -xvf zbar-0.10.tar.gz
# 进入源码目录
cd zbar-0.10
# 配置编译选项 选项详情查看 ./configure --help
./configure --prefix=$(pwd)/output --host=arm-linux-gnueabihf --enable-shared --enable-static --without-imagemagick --without-jpeg --without-python --without-gtk --without-qt --disable-video
# 编译,导出
make && make install
–prefix=$(pwd)/output 这个编译选项为 指定输出的目录为源码目录下的 output/ 文件夹下生成 库和头文件。
–host=arm-linux-gnueabihf 这个编译选项为指定交叉编译器,例如我当前的交叉编译器为 arm-linux-gnueabihf-gcc 。
其他编译选项参照 ./configure --help 应该描述的很清楚,库的soname 为 libzbar.so.0,进行软连接或者重命名放到设备里。
根据需要拷贝静态库/动态库(lib)和头文件(include/),编译时链接即可。效果如下
下面两个使用的例子都是用的openCV余先梳理得到zbar需要的8位黑白图像数据:
- 从图片识别的实现(代码片段删除类相关信息截取)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>
#include "opencv2/opencv.hpp"
#include "zbar/zbar.h"
typedef enum TQrCodeTypeTag
{
kQrCodeTypeNONE = 0, //< no symbol decoded
kQrCodeTypePARTIAL = 1, //< intermediate status
kQrCodeTypeEAN8 = 8, //< EAN-8
kQrCodeTypeUPCE = 9, //< UPC-E
kQrCodeTypeISBN10 = 10, //< ISBN-10 (from EAN-13). @since 0.4
kQrCodeTypeUPCA = 12, //< UPC-A
kQrCodeTypeEAN13 = 13, //< EAN-13
kQrCodeTypeISBN13 = 14, //< ISBN-13 (from EAN-13). @since 0.4
kQrCodeTypeI25 = 25, //< Interleaved 2 of 5. @since 0.4
kQrCodeTypeCODE39 = 39, //< Code 39. @since 0.4
kQrCodeTypePDF417 = 57, //< PDF417. @since 0.6
kQrCodeTypeQRCODE = 64, //< QR Code. @since 0.10
kQrCodeTypeCODE128 = 128, //< Code 128
kQrCodeTypeSYMBOL = 0x00ff, //< mask for base symbol type
kQrCodeTypeADDON2 = 0x0200, //< 2-digit add-on flag
kQrCodeTypeADDON5 = 0x0500, //< 5-digit add-on flag
kQrCodeTypeADDON = 0x0700, //< add-on flag mask
}TQrCodeType;
/// 识别结果 result buf大小根据识别结果配置
typedef struct TQrCodeResultTag
{
TQrCodeType QrCodeType; ///< 识别结果类型
uint16_t len; ///< 识别结果内容长度
char *result; ///< 识别结果内容数据
}TQrCodeResult;
bool DecodeImage(const char *image_path, TQrCodeResult *result)
{
if (!image_path || !strlen(image_path) || !result)
return false;
if (access(image_path, F_OK) != 0)
{
printf("ERR: image %s is not exist!!!\n", image_path);
return false;
}
zbar::ImageScanner scanner;
/// configure the reader
scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
cv::String img_path(image_path);
Mat image = imread(img_path);
Mat imageGray;
cvtColor(image, imageGray, CV_RGB2GRAY);
if (!imageGray.data)
{
printf("read picture error!\n");
return false;
}
int width = imageGray.cols;
int height = imageGray.rows;
uchar *raw = (uchar *)imageGray.data;
zbar::Image imageZbar(width, height, "Y800", raw, width * height);
// imageZbar.convert(fourcc('Y','8','0','0'));
/// scan the image for barcodes
scanner.scan(imageZbar);
zbar::Image::SymbolIterator symbol = imageZbar.symbol_begin();
if (imageZbar.symbol_begin() == imageZbar.symbol_end())
{
printf("decode failed!\n");
result->len = 0;
result->QrCodeType = kQrCodeTypeNONE;
}
for (; symbol != imageZbar.symbol_end(); ++symbol)
{
result->QrCodeType = (TQrCodeType)symbol->get_type();
result->len = (uint16_t)symbol->get_data().size();
memcpy(result->result, symbol->get_data().c_str(), symbol->get_data().size());
printf("type: %s\n", symbol->get_type_name().c_str());
printf("data[%d]: %s\n", symbol->get_data().size(), symbol->get_data().c_str());
}
// clean up
imageZbar.set_data(NULL, 0);
return true;
}
- 从图像流中获取 我这边使用的RGB的数据,只需要调整openCV相关操作如下即可。
/// vec->data() 为图像数据, gray转换结果使用图片处理相同流程即可
cv::Mat gray;
cv::Mat temp_img(height_, width_, CV_8UC3, vec->data());
cvtColor(temp_img, gray, CV_RGB2GRAY);
以上即可进行二维码识别,摄像头输入的流数据可能存在图像畸变的情况,解决可以参考 https://blog.youkuaiyun.com/qq_32864683/article/details/83019619 进行摄像头的棋盘格标定即可。
QrEncode : 二维码生成
二维码生成就相对比较简单,当时需要结合对应嵌入式设备的GUI进行展示效果。该库生成的为每个像素点1pixel的二维码对应到图像中需要进行放大处理。
交叉编译不再赘述,同上面到根目录。
mkdir build
cd build
../configure --without-png --host=arm-linux-gnueabihf
make && make install
# 拷贝 libqrencode.so qrencode.h 对lib进行软连接 libqrencode.so.4 libqrencode.so.4.0.2
生成16位(565)BMP和24位BMP图像的示例代码如下参考:
#include "stdio.h"
#include "string.h"
#include "qrencode/qrencode.h"
/// 位图head的结构
#pragma pack(push, 1)
typedef struct BITMAPFILEHEADER
{
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
}BITMAPFILEHEADER;
typedef struct BITMAPINFOHEADER
{
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
}BITMAPINFODEADER;
typedef struct BITMAPINFOHEADER565
{
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
uint32_t bi4RedMask;
uint32_t bi4GreenMask;
uint32_t bi4BlueMask;
}BITMAPINFOHEADER565;
#pragma pack(pop)
typedef enum TQrCodeBmpTag
{
kQrCodeBmp565 = 0, //< 16位 565 BMP
kQrCodeBmp888 = 1, //< 24位 888 BMP
}TQrCodeBmp;
bool EncodeImage(const char *image_path, const char *content, uint32_t color/* 二维码生成颜色 B G R 各一个字节 范围0~0xff 例如 0xff0000为 蓝色 */, TQrCodeBmp type)
{
if(!content || !image_path || !strlen(content) || color == 0xffffff)
{
printf("EncodeImage Invalid Param!\n");
return false;
}
QRcode* p_qrc;
if ((bool)(p_qrc = QRcode_encodeData(strlen(content), (const unsigned char*)content, 0, QR_ECLEVEL_L)))
{
unsigned int pic_width, width_adjusted, data_bytes, x, y, l, n;
unsigned char* bmp_24_data = NULL;
unsigned char* bmp_565_data = NULL;
BITMAPFILEHEADER file_head_888;
BITMAPINFOHEADER info_head_888;
BITMAPFILEHEADER file_head_565;
BITMAPINFOHEADER565 info_head_565;
pic_width = p_qrc->width;
width_adjusted = pic_width * 8 * 3;
if (width_adjusted % 4)
width_adjusted = (width_adjusted / 4 + 1) * 4;
data_bytes = width_adjusted * pic_width * 8;
if(kQrCodeBmp888 == type)
{
/// Prepare bmp headers 24 bit
if (!(bmp_24_data = (unsigned char*)malloc(data_bytes)))
{
printf("EncodeImage malloc fail\n");
return false;
}
/// wihte back ground
memset(bmp_24_data, 0xff, data_bytes);
/// fill head
file_head_888.bfType = 0x4d42; ///< "BM"
file_head_888.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + data_bytes;
printf("24 bit bmp size =0x%x\n", file_head_888.bfSize);
file_head_888.bfReserved1 = 0;
file_head_888.bfReserved2 = 0;
file_head_888.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
info_head_888.biSize = sizeof(BITMAPINFOHEADER);
info_head_888.biWidth = pic_width*8;
info_head_888.biHeight = -((int)pic_width*8);
info_head_888.biPlanes = 1;
info_head_888.biBitCount = 24;
info_head_888.biCompression = 0; ///< BI_RGB
info_head_888.biSizeImage = 0;
info_head_888.biXPelsPerMeter = 0;
info_head_888.biYPelsPerMeter = 0;
info_head_888.biClrUsed = 0;
info_head_888.biClrImportant = 0;
unsigned char* qr_source_data = p_qrc->data;
for (y = 0; y < pic_width; y++)
{
unsigned char* pixel_24_data = bmp_24_data + width_adjusted * y * 8;
for (x = 0; x < pic_width; x++)
{
if (*qr_source_data & 1)
{
for (l = 0; l < 8; l++)
{
for (n = 0; n < 8; n++)
{
/// fill color
*(pixel_24_data + n * 3 + width_adjusted * l) = color & 0xff0000;
*(pixel_24_data + 1 + n * 3 + width_adjusted * l) = color & 0xff00;
*(pixel_24_data + 2 + n * 3 + width_adjusted * l) = color & 0xff;
}
}
}
pixel_24_data += 3 * 8;
qr_source_data++;
}
}
FILE* f;
if ((bool)(f = fopen(image_path, "wb")))
{
fwrite(&file_head_888, sizeof(BITMAPFILEHEADER), 1, f);
fwrite(&info_head_888, sizeof(BITMAPINFOHEADER), 1, f);
fwrite(bmp_24_data, sizeof(unsigned char), data_bytes, f);
printf("qrcode has generated 24 bit bmp in %s\n", image_path);
fclose(f);
}
else
{
free(bmp_24_data);
QRcode_free(p_qrc);
printf("Unable to open file\n");
return false;
}
}
else
{
/// Prepare bmp headers 16 bit 565
if (!(bmp_565_data = (unsigned char*)malloc(data_bytes*2/3+100)))
{
printf("EncodeImage malloc fail\n");
return false;
}
memset(bmp_565_data, 0xff, data_bytes*2/3+100);
file_head_565.bfType = 0x4d42; ///< "BM"
file_head_565.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER565) + data_bytes*2/3;
printf("16 bit bmp size =0x%x\n", file_head_565.bfSize);
file_head_565.bfReserved1 = 0;
file_head_565.bfReserved2 = 0;
file_head_565.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER565);
info_head_565.biSize = 0x28;
info_head_565.biWidth = pic_width*8;
info_head_565.biHeight = -((int)pic_width*8);
info_head_565.biPlanes = 1;
info_head_565.biBitCount = 16;
info_head_565.biCompression = 3; ///< BI_BITFIELDS
info_head_565.biSizeImage = 0;
info_head_565.biXPelsPerMeter = 0;
info_head_565.biYPelsPerMeter = 0;
info_head_565.biClrUsed = 0;
info_head_565.biClrImportant = 0;
info_head_565.bi4RedMask = 0xF800;
info_head_565.bi4GreenMask = 0x7E0;
info_head_565.bi4BlueMask = 0x1F;
unsigned char* qr_source_data = p_qrc->data;
for (y = 0; y < pic_width; y++)
{
unsigned char* pixel_565_data = bmp_565_data + pic_width * 8 * 2 * y * 8;
for (x = 0; x < pic_width; x++)
{
if (*qr_source_data & 1)
{
for (l = 0; l < 8; l++)
{
for (n = 0; n < 8; n++)
{
/// fill color
*(pixel_565_data + n * 2 + pic_width * 8 * 2 * l) = ((((color & 0xff0000)>>16) * 32 / 256) << 3) + (((color & 0xff) * 64 / 256)>>3);
*(pixel_565_data + 1 + n * 2 + pic_width * 8 * 2 * l) = (((color & 0xff) * 64 / 256)<<5) + (((color & 0xff00)>>8) * 32 / 256);
}
}
}
pixel_565_data += 2 * 8;
qr_source_data++;
}
}
FILE* f;
if ((bool)(f = fopen(image_path, "wb")))
{
fwrite(&file_head_565, sizeof(BITMAPFILEHEADER), 1, f);
fwrite(&info_head_565, sizeof(BITMAPINFOHEADER565), 1, f);
fwrite(bmp_565_data, sizeof(unsigned char), data_bytes*2/3, f);
printf("qrcode has generated 16 bit bmp in %s\n", image_path);
fclose(f);
}
else
{
free(bmp_24_data);
free(bmp_565_data);
QRcode_free(p_qrc);
printf("Unable to open file\n");
return false;
}
}
/// Free data
free(bmp_24_data);
free(bmp_565_data);
QRcode_free(p_qrc);
}
else
{
printf("NULL returned\n");
return false;
}
printf("EncodeImage Finish!\n");
return true;
}
也可以直接调用GUI进行绘制图像数据效果如下(内容为百度的网址),一般加个白色背景即可。
识别框显示
zbar识别结果是带识别位置信息的,只要读取一下点的序列即可
for(int cnt = 0; cnt < symbol->get_location_size(); cnt++)
{
printf("[%d](%d,%d) ", cnt, symbol->get_location_x(cnt), symbol->get_location_y(cnt));
}
二维码的点的顺序是逆时针显示的,映射到140*140的画布上,如果有需要按照比例转换到原始图像分辨率,调用gui画框即可。