其他参考
基于二维码的室内定位技术(一)——原理
哎,不知道怎么说呢。自从朱富帅丢下了这个锅,我就没有安宁过。大致说一下这个项目吧——一个小车,前面装了一个摄像头,当车看到一个二维码时,就要朝二维码开过去,而且需要保证最后是正对二维码中心顶上去。这个需求来自于导师要的自动无线充电的功能。无线充电需要比较高的对准精度,而目前的室内定位技术(基本是基于无线电的)还没这么高的精度,因此需要搞出一个基于机器视觉的。这个需求中,先不说路径规划的问题,首先要解决的就是基于二维码的定位问题。
第一步,很显然,就是通过摄像头不停读取图像,然后识别二维码,得到二维码的各种信息。读摄像头和识别二维码在前一篇《OpenCV+Zbar通过摄像头实时识别二维码》中已经讲得很清楚了。是的,我就是为了这一篇做铺垫的。得到了摄像头的四个顶点在图片中的坐标,应该就能够通过数学运算计算出我们想要的信息。说白了,就是通过这样一张图片中,二维码的扭曲状态来计算出二维码的位姿。
如果你接触过单目定位技术,那么应该马上就想到了,这就是一个PnP(Perspective n Point)问题。解PnP问题的方法很多,不过基本没有直接解法,都是迭代解法,因为PnP最终是一个n元二次方程(每个方程都是一个余弦定理)。不管是实验观察还是参考文献,都会发现PnP方程组对于噪声是很敏感的,摄像头和二维码都保持不动,结果可以看到识别出来的角度不停跳动,有时跳动还很剧烈。个中五味杂陈,我就不再累述。总之,我需要搞出一种针对当前需求的基于二维码的定位技术。
通用的PnP解法误差太大,我就应该多多利用该需求的应用环境的特殊性。如果基于这么两个假设:
(1)用作定位的二维码都规矩地处于一个与地面垂直的平面上;
(2)摄像头的视线平行于地面。
那么问题似乎会简便很多。所谓规矩,就是说二维码的上下两条边水平,而左右两条边竖直。其实这个假设基本是成立的。因为一般二维码都会贴在墙上,而且底边与地面平行。OK,有了这个假设,我们就来看一下二维码的左右两条边沿的成像有什么规律。
以摄像头的光心为原点,正前方为Z轴正方向,正上方为Y轴正方向,正右方为X轴正方向,建立空间直角坐标系。二维码左右边沿就是两条与Y轴平行的线段。假设摄像头上下视角为θ,那么在Z坐标为z的地方,其视野高度为
如下图中所示:
长度为L的线段(图中红色线段)相对于视野高度的比例为
而该比例应该等于图片中线段的长度l比上图片的高度h。即
二维码的边长L是已知的,图片的高度h也是已知的。左边沿和右边沿在图片中的长度l1和l2可以通过识别出来的四个顶点坐标计算出来,而上下视角θ是个常数,可以通过测量得到。把左边沿的Z轴坐标记作z1,把右边沿的Z轴坐标记作z2,那么可以得到
既然知道了左右边沿的z坐标,那么二维码中心点的z坐标就是其均值,即
好,接下来看俯视图:
(注意,z是二维码边沿到摄像机距离,o为摄像机位置)
左右边沿z坐标之差比上二维码边长L,就是sinβ,其中β就是二维码的法向量与Z轴的夹角。如果规定二维码偏左(如图中这样)时β为负,否则为正,那么有
于是有了二维码的姿态信息β。
二维码的中心的z坐标z0有了,那么如果能够知道二维码的中心所在铅垂线与原点构成的平面和Z轴形成的夹角,那么二维码的中心所在的铅垂线就可以唯一确定了。由之前的研究我已经知道,如果能够得知某个点在图片上的2D坐标,那么我就能够得知该点在空间中的方向。那么如何得知二维码的中心点在图片中的坐标呢?
一开始我想到的是,在二维码的四个顶点中取两个对角点,取均值应该就是中心点的坐标。但是!这种想法是错误的!因为当二维码所在平面不垂直与成像平面时,线段的长度关系就会扭曲。不过,直线经过成像变换依旧是直线。那么二维码的两条对角线的交点就是二维码的中心点呀!所以思路就是:在图片上,计算两条对角线的直线方程,然后求交点的2D坐标,然后转换成空间中的方向!
假设四个顶点点分别为P0(x0,y0)、P1(x1,y1)、P2(x2,y2)和P3(x3,y3),Zbar保证了它们的分别是图中的这四个点:
设直线P0P2的方程为
可以解得
设直线P1P3的方程为
可以解得
而两条直线的交点的横坐标为
接下来看成像模型:
其中θ'是水平视角。在空间中,线段MP比上线段MN的比例为
而在拍摄的图片上,该比例应该为
所以有
解得
而水平视角的正切值和上下视角的正切值就是图像宽和高之比,即
代入上式,解得
至此,α、β和z0都已经直接确定了。那么在上述两个假设下的二维码位姿信息就唯一确定了。
如果已经得知二维码自身在世界坐标系中的坐标和朝向,那么就可以通过α、β和z0得知小车自身在世界坐标系中的坐标和朝向。
基于二维码的室内定位技术(二)——实现
在《基于二维码的室内定位技术(一)——原理》中我已经讲解了计算α、β和z0的方法了,这里我就要实现它。
大致的思路是这样的:
(1)使用摄像头获取一帧;
(2)识别摄像头中的二维码;
(3)如果二维码的内容以“QRLocation,”开头,则继续第(4)步,否则返回第(1)步;
(4)识别“QRLocation,”后面的小数,作为二维码的边长
(5)识别二维码左右两条边沿,如果边沿太倾斜,则返回第(1)步,否则继续第(6)步;
(6)使用二维码的四个顶点坐标按前文所述的算法计算α、β和z0。
直接上代码,代码里注释很详细,而且我的代码一向很干净~
QRLocation.h
#ifndef QRLOCATION_H
#define QRLOCATION_H
/*
二维码的内容必须符合格式:
QRLocation,<qrSize>
其中<qrSize>是一个实数,表示二维码边长
*/
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <zbar.h>
//二维码倾斜阈值
#define QRLOCATION_INCLINATION_THRESHOLD 0.1
//调试窗口标题
#define QRLOCATION_DEBUGUI_TITLE "debugui"
//二维码位姿
typedef struct QRPose
{
//二维码中心所在铅垂线与O点构成的平面和Z轴形成的夹角
double a;
//二维码所在平面与X轴构成的夹角
double b;
//二维码中心到XOY平面的距离
double z;
}
QRPose_t;
//二维码定位算法
class QRLocation
{
public:
//初始化,第一个参数为摄像头编号,第二个参数为摄像头上下视角,第三个参数为是否开启调试窗口
bool init(int webcamIndex,double hViewAngle,bool debugUI);
//获取二维码位姿
bool getQRPose(QRPose_t* qrPose);
//销毁
bool destroy();
private:
//摄像头
CvCapture* capture;
//摄像头上下视角
double hViewAngle;
//是否开启调试窗口
bool debugUI;
//灰度图
IplImage* grayFrame;
//图片扫描器
zbar::ImageScanner scanner;
private:
//计算位姿(格式合法性判断)
bool getQRPose(zbar::Image::SymbolIterator symbol,QRPose_t* qrPose);
//计算位姿(算法)
bool getQRPose(zbar::Image::SymbolIterator symbol,double qrSize,QRPose_t* qrPose);
};
#endif
QRLocation.cpp
#include "QRLocation.h"
#include <string.h>
#include <stdio.h>
using namespace std;
using namespace zbar;
bool QRLocation::init(int webcamIndex,double hViewAngle,bool debugUI)
{
//打开摄像头
capture=cvCreateCameraCapture(webcamIndex);
//摄像头不存在
if(!capture)
return false;
this->hViewAngle=hViewAngle;
this->debugUI=debugUI;
grayFrame=0;
//配置zbar图片扫描器
scanner.set_config(zbar::ZBAR_NONE,zbar::ZBAR_CFG_ENABLE,1);
//如果开启调试,则创建窗口,名称为“debugui”,自动调整大小
if(debugUI)
cvNamedWindow(QRLOCATION_DEBUGUI_TITLE,CV_WINDOW_AUTOSIZE);
}
bool QRLocation::getQRPose(QRPose_t* qrPose)
{
//从摄像头中抓取一帧
IplImage* frame=cvQueryFrame(capture);
//图像为空
if(!frame)
return false;
//如果灰度图没有创建,就创建一个和原图一样大小的灰度图(8位色深,单通道)
if(!grayFrame)
grayFrame=cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U,1);
//原图转灰度图
cvCvtColor(frame,grayFrame,CV_BGR2GRAY);
//如果开启调试,则显示灰度图
if(debugUI)
{
cvShowImage(QRLOCATION_DEBUGUI_TITLE,grayFrame);
cvWaitKey(50);
}
//创建zbar图像
Image image(frame->width,frame->height,"Y800",grayFrame->imageData,frame->width*frame->height);
//扫描图像,识别二维码,获取个数
int symbolCount=scanner.scan(image);
//获取第一个二维码
Image::SymbolIterator symbol=image.symbol_begin();
//遍历所有识别出来的二维码
while(symbolCount--)
{
//能够识别
if(getQRPose(symbol,qrPose))
return true;
//下一个二维码
++symbol;
}
return false;
}
bool QRLocation::getQRPose(Image::SymbolIterator symbol,QRPose_t* qrPose)
{
//首先得是一个二维码
if(symbol->get_type_name()!="QR-Code")
return false;
//获取内容
char data[128];
strncpy(data,symbol->get_data().c_str(),sizeof(data)-1);
data[sizeof(data)-1]=0;
//内容得是以“QRLocation,”开头
if(strncmp(data,"QRLocation,",11)!=0)
return false;
//获取二维码边长
double qrSize=0;
sscanf(data+11,"%lf",&qrSize);
if(qrSize==0)
return false;
//计算位姿
return getQRPose(symbol,qrSize,qrPose);
}
bool QRLocation::getQRPose(Image::SymbolIterator symbol,double qrSize,QRPose_t* qrPose)
{
//获得四个点的坐标
double x0=symbol->get_location_x(0);
double y0=symbol->get_location_y(0);
double x1=symbol->get_location_x(1);
double y1=symbol->get_location_y(1);
double x2=symbol->get_location_x(2);
double y2=symbol->get_location_y(2);
double x3=symbol->get_location_x(3);
double y3=symbol->get_location_y(3);
//左边沿纵向差
double leftH=y1-y0;
//右边沿纵向差
double rightH=y2-y3;
//必须保证0点高于1点,3点高于2点
if(leftH<0||rightH<0)
return false;
//左边沿横向差
double leftW=abs(x0-x1);
//右边沿横向差
double rightW=abs(x2-x3);
//不能太倾斜
if(max(leftW/leftH,rightW/rightH)>QRLOCATION_INCLINATION_THRESHOLD)
return false;
//上下视角一半的正切值,因为一直要用,所以先计算出来
double tanHalfView=tan(hViewAngle/2);
double leftLen=sqrt(leftH*leftH+leftW*leftW);
double rightLen=sqrt(rightH*rightH+rightW*rightW);
//左边沿的深度
double leftZ=grayFrame->height*qrSize/tanHalfView/2/leftLen;
//右边沿的深度
double rightZ=grayFrame->height*qrSize/tanHalfView/2/rightLen;
//得到中心点的深度
double z=(leftZ+rightZ)/2;
//计算b的正弦值
double sinB=(leftZ-rightZ)/qrSize;
if(sinB>1)
return false;
//得到b
double b=asin(sinB);
//两条对角线的系数和偏移
double k1=(y2-y0)/(x2-x0);
double b1=(x2*y0-x0*y2)/(x2-x0);
double k2=(y3-y1)/(x3-x1);
double b2=(x3*y1-x1*y3)/(x3-x1);
//两条对角线交点的X坐标
double crossX=-(b1-b2)/(k1-k2);
//计算a的正切值
double tanA=tanHalfView*(2*crossX-grayFrame->width)/grayFrame->height;
//得到a
double a=atan(tanA);
qrPose->a=a;
qrPose->b=b;
qrPose->z=z;
return true;
}
bool QRLocation::destroy()
{
//释放灰度图
cvReleaseImage(&grayFrame);
//销毁窗口
cvDestroyWindow(QRLOCATION_DEBUGUI_TITLE);
//释放内存
cvReleaseCapture(&capture);
}
测试程序:
QRLocationTest.cpp
#include "QRLocation.h"
#include <stdio.h>
int main()
{
QRLocation qrLoc;
if(!qrLoc.init(1,0.60,true))
return 1;
QRPose_t pose;
while(true)
{
if(qrLoc.getQRPose(&pose))
{
double aInDegree=pose.a*180/3.1415;
double bInDegree=pose.b*180/3.1415;
printf("a=%.2lf,b=%.2lf,z=%.2lf\n",aInDegree,bInDegree,pose.z);
}
}
}
代码中使用的摄像头的索引是1,如果你的电脑只有1个摄像头,要改为0。0.60是我的这个摄像头的上下视角的弧度。不同的摄像头上下视角不同,需要测量。
Makefile:
all:$(subst src/,obj/,$(subst .cpp,.o,$(wildcard src/*.cpp)))
g++ $^ -o QRLocationTest `pkg-config opencv --libs --cflags opencv` -lzbar
obj/%.o: src/%.cpp
g++ -c $^ -o $@
clean:
rm obj/*
嗯,所有代码放在src子目录下,然后创建一个obj子目录用来存在.o文件。make之后,产生一个可执行文件QRLocationTest。以普通权限就可以运行了。运行后,在摄像头前面放置一张二维码,比如下面这张:
如果你的显示器正常的话,这个二维码的边长应该5.7cm,它的内容则是“QRLocation,5.7”。
摄像头以一定的倾斜角拍摄二维码,场景如下:
可以看到控制台的输出:
可以看到输出还是比较准确的~
使用Zbar定位、识别二维码
之前整个项目用的是Java,结果在树莓派上运行速度很慢。现在要用C++来改写。Java平台上我是使用ZXing来识别二维码的,ZXing是一个纯Java实现,所以也就没法用在C++中。于是我找到了Zbar,它的核心是用C写的。据说Zbar的性能是ZXing的两倍以上~
要使用Zbar,首先肯定是要安装Zbar库。当然也可以使用源码安装,虽然我没有成功......
sudo apt install libzbar-dev
之后,就可以编写一个测试代码:
scan_image.c
#include <stdio.h>
#include <stdlib.h>
#include <png.h>
#include <zbar.h>
#define zbar_fourcc(a, b, c, d) \
((unsigned long)(a) | \
((unsigned long)(b) << 8) | \
((unsigned long)(c) << 16) | \
((unsigned long)(d) << 24))
#if !defined(PNG_LIBPNG_VER) || \
PNG_LIBPNG_VER < 10018 || \
(PNG_LIBPNG_VER > 10200 && \
PNG_LIBPNG_VER < 10209)
/* Changes to Libpng from version 1.2.42 to 1.4.0 (January 4, 2010)
* ...
* 2. m. The function png_set_gray_1_2_4_to_8() was removed. It has been
* deprecated since libpng-1.0.18 and 1.2.9, when it was replaced with
* png_set_expand_gray_1_2_4_to_8() because the former function also
* expanded palette images.
*/
#define png_set_expand_gray_1_2_4_to_8 png_set_gray_1_2_4_to_8
#endif
zbar_image_scanner_t *scanner = NULL;
/* to complete a runnable example, this abbreviated implementation of
* get_data() will use libpng to read an image file. refer to libpng
* documentation for details
*/
static void get_data (const char *name,
int *width, int *height,
void **raw)
{
FILE *file = fopen(name, "rb");
if(!file) exit(2);
png_structp png =
png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if(!png) exit(3);
if(setjmp(png_jmpbuf(png))) exit(4);
png_infop info = png_create_info_struct(png);
if(!info) exit(5);
png_init_io(png, file);
png_read_info(png, info);
/* configure for 8bpp grayscale input */
int color = png_get_color_type(png, info);
int bits = png_get_bit_depth(png, info);
if(color & PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
if(color == PNG_COLOR_TYPE_GRAY && bits < 8)
png_set_expand_gray_1_2_4_to_8(png);
if(bits == 16)
png_set_strip_16(png);
if(color & PNG_COLOR_MASK_ALPHA)
png_set_strip_alpha(png);
if(color & PNG_COLOR_MASK_COLOR)
png_set_rgb_to_gray_fixed(png, 1, -1, -1);
/* allocate image */
*width = png_get_image_width(png, info);
*height = png_get_image_height(png, info);
*raw = malloc(*width * *height);
png_bytep rows[*height];
int i;
for(i = 0; i < *height; i++)
rows[i] = *raw + (*width * i);
png_read_image(png, rows);
}
int main (int argc, char **argv)
{
if(argc < 2) return(1);
/* create a reader */
scanner = zbar_image_scanner_create();
/* configure the reader */
zbar_image_scanner_set_config(scanner, 0, ZBAR_CFG_ENABLE, 1);
/* obtain image data */
int width = 0, height = 0;
void *raw = NULL;
get_data(argv[1], &width, &height, &raw);
/* wrap image data */
zbar_image_t *image = zbar_image_create();
zbar_image_set_format(image, zbar_fourcc('Y','8','0','0'));
zbar_image_set_size(image, width, height);
zbar_image_set_data(image, raw, width * height, zbar_image_free_data);
/* scan the image for barcodes */
int n = zbar_scan_image(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);
printf("decoded %s symbol \"%s\"\n",zbar_get_symbol_name(typ), data);
int pointCount=zbar_symbol_get_loc_size(symbol);
printf("point count: %d\n",pointCount);
int i;
for(i=0;i<pointCount;i++)
{
int x=zbar_symbol_get_loc_x(symbol,i);
int y=zbar_symbol_get_loc_y(symbol,i);
printf("point%d=(%d,%d)\n",i,x,y);
}
}
/* clean up */
zbar_image_destroy(image);
zbar_image_scanner_destroy(scanner);
return(0);
}
这段代码是我基于官方给的examples/scan_image.c略做修改得到的。官方的最新代码中,zbar_fourcc这个宏定义是zbar.h自带的,可是我通过apt安装的zbar库可能版本落后,没有这个宏定义,所以我就只能复制了一份过来。
我承认一开始看这段代码还是有点抵触的,但是研究透彻了以后觉得还是很清晰的。这段代码获取命令行的参数作为一个文件名,然后使用png格式读取图片,并转换成一张灰度图,然后使用zbar库来识别图中的码(可能是条形码也可能是二维码),依次输出每一个码的类型、内容和定位点。
gcc scan_image.c -lpng -lzbar -o scan_image
一句命令就可以编译这段代码了,然后产生一个scan_image可执行文件。接下来准备一张包含二维码的png图片,比如下面这张:
1.png
然后运行scan_image:
./scan_image 1.png
可以看到如下输出:
说明zbar成功识别了图中的二维码。当然,还可以使用这一张P过的图片:
3.png
然后运行scan_image:
./scan_image 3.png
可以看到如下输出:
如果读了get_data()的代码,那么可以发现它的作用是把一张png图像转成了raw数组,而这个raw数组是这么来的:
void *raw = NULL;
//...
*raw = malloc(*width * *height);
而*width和*height分别是图像的宽和高。因此,一个字节代表了一个像素,所以我立刻猜测这个字节很可能就是这个像素的灰度值。接下来我做了一个实验来验证我的想法。我在
get_data(argv[1], &width, &height, &raw);
之后添加了这么一段代码:
printf("size=%dx%d\n",width,height);
int x,y;
for(y=0;y<10;y++)
{
for(x=0;x<10;x++)
{
int pixel=((unsigned char*)raw)[y*width+x];
printf("%x ",pixel);
}
printf("\n");
}
这段代码先打印图片的宽和高,然后把最左上角一个10x10的方块内的值打印出来。接着,我把1.png中(0,0)位置的像素点涂成了#808080,(1,0)位置的像素点涂成了#ababab,(2,0)位置的像素点涂成了#ff0000。如图:
再次运行,程序输出结果为:
很明显,raw数组中保存的确实是灰度矩阵。至于RGB是用何种算法变成灰度的,还需要具体研究一下。不过至此已经能够知道,只要获取了一个灰度矩阵,就能用Zbar方便地解析其中的条码或者二维码了。
OpenCV+Zbar通过摄像头实时识别二维码
在上一篇《使用Zbar定位、识别二维码》中,我已经能够通过Zbar从一个灰度矩阵中识别出二维码(其实还包括条形码)。这一篇文章要更进一步,通过摄像头不停拍摄图片,然后交给Zbar来识别其中的二维码(如果存在的话)。
在Linux上调用摄像头拍照,我本来想用的是V4L2。但是V4L2实在太复杂了,一时间搞不定,所以转而使用OpenCV。本来对于OpenCV还是有点抵触的,但是自从今天下午用OpenCV寥寥几行代码就实现了摄像头拍摄,我对OpenCV有了新的认识,一下子喜欢多了,大致学完驱动开发就学OpenCV!
=============阶段一:OpenCV调用摄像头============
先上一个OpenCV调用摄像头实时显示图像的程序,非常简单:
opencv_webcam.cpp
#include <opencv/cv.h>
#include <opencv/highgui.h>
int main()
{
//打开0号摄像头,从摄像头中获取视频
CvCapture *capture=cvCreateCameraCapture(0);
//摄像头不存在
if(!capture)
return 1;
//创建窗口,名称为“debug”,自动调整大小
cvNamedWindow("debug",CV_WINDOW_AUTOSIZE);
while(1)
{
//从摄像头中抓取一帧
IplImage* frame=cvQueryFrame(capture);
//在窗口上显示
if(frame)
cvShowImage("debug",frame);
//延时50ms,如果按了ESC就退出
if(cvWaitKey(50)==27)
break;
}
//销毁窗口
cvDestroyWindow("debug");
//释放内存
cvReleaseCapture(&capture);
return 0;
}
要编译这段代码,需要首先安装OpenCV库。很简单:
sudo apt install libopencv-dev
然后就是调用g++编译即可:
g++ opencv_webcam.cpp -o opencv_webcam `pkg-config opencv --libs --cflags opencv`
见证奇迹的时刻到了!运行程序:
./opencv_webcam
非常稳定吧~点击关闭按钮是无法把窗口关掉的,直接按下Esc键就能退出。
==============阶段二:图像灰度化==============
《使用Zbar定位、识别二维码》中提到,Zbar接受灰度矩阵作为输入数据。那么接下来就需要把图像灰度化,然后获得灰度矩阵。
OpenCV中把图片转换为灰度图非常简单,只需要使用函数:
void cvCvtColor(const IplImage* src,IplImage* dst,int code);
比如上面那个例子中,如果要在界面上显示灰度图像,可以这样:
opencv_webcam.cpp
#include <opencv/cv.h>
#include <opencv/highgui.h>
int main()
{
//打开0号摄像头,从摄像头中获取视频
CvCapture *capture=cvCreateCameraCapture(0);
//摄像头不存在
if(!capture)
return 1;
//创建窗口,名称为“debug”,自动调整大小
cvNamedWindow("debug",CV_WINDOW_AUTOSIZE);
//灰度图
IplImage* grayFrame=0;
while(1)
{
//从摄像头中抓取一帧
IplImage* frame=cvQueryFrame(capture);
//图像不为空
if(frame)
{
//如果灰度图没有创建,就创建一个和原图一样大小的灰度图(8位色深,单通道)
if(!grayFrame)
grayFrame=cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U,1);
//原图转灰度图
cvCvtColor(frame,grayFrame,CV_BGR2GRAY);
//显示灰度图
cvShowImage("debug",grayFrame);
}
//延时50ms,如果按了ESC就退出
if(cvWaitKey(50)==27)
break;
}
//释放灰度图
cvReleaseImage(&grayFrame);
//销毁窗口
cvDestroyWindow("debug");
//释放内存
cvReleaseCapture(&capture);
return 0;
}
.运行后显示的就是灰度图了:
==============阶段三:OpenCV+Zbar==============
只差最后一步——把OpenCV和Zbar衔接在一起了。由于opencv使用的是C++版本,那么Zbar也用C++版本吧,反倒是看着简单。很简单,直接看代码吧:
opencv_zbar.cpp
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <zbar.h>
#include <iostream>
using namespace std;
using namespace zbar;
int main()
{
//打开0号摄像头,从摄像头中获取视频
CvCapture *capture=cvCreateCameraCapture(0);
//摄像头不存在
if(!capture)
return 1;
//创建窗口,名称为“debug”,自动调整大小
cvNamedWindow("debug",CV_WINDOW_AUTOSIZE);
//灰度图
IplImage* grayFrame=0;
//创建zbar图像扫描器
ImageScanner scanner;
//配置zbar图片扫描器
scanner.set_config(ZBAR_NONE,ZBAR_CFG_ENABLE,1);
while(1)
{
//从摄像头中抓取一帧
IplImage* frame=cvQueryFrame(capture);
//图像不为空
if(frame)
{
//如果灰度图没有创建,就创建一个和原图一样大小的灰度图(8位色深,单通道)
if(!grayFrame)
grayFrame=cvCreateImage(cvGetSize(frame),IPL_DEPTH_8U,1);
//原图转灰度图
cvCvtColor(frame,grayFrame,CV_BGR2GRAY);
//显示灰度图
cvShowImage("debug",grayFrame);
//创建zbar图像
Image image(frame->width,frame->height,"Y800",grayFrame->imageData,frame->width*frame->height);
//扫描图像,识别二维码,获取个数
int symbolCount=scanner.scan(image);
//获取第一个二维码
Image::SymbolIterator symbol=image.symbol_begin();
//遍历所有识别出来的二维码
while(symbolCount--)
{
//输出二维码内容
cout<<"'"<<symbol->get_data()<<"'"<<endl;
//获取定位点个数
int pointCount=symbol->get_location_size();
//遍历所有定位点
for(int i=0;i<pointCount;i++)
cout<<'('<<symbol->get_location_x(i)<<','<<symbol->get_location_y(i)<<")"<<endl;
//下一个二维码
++symbol;
}
}
//延时50ms,如果按了ESC就退出
if(cvWaitKey(50)==27)
break;
}
//释放灰度图
cvReleaseImage(&grayFrame);
//销毁窗口
cvDestroyWindow("debug");
//释放内存
cvReleaseCapture(&capture);
return 0;
}
没有什么深奥难懂的地方。唯一需要注意的是代码中用红色标记出来的地方。Zbar中,Image构造函数的第三个参数为“Y800”,表明是灰度图,那么第四个参数就是一个灰度矩阵。当然,这里说是矩阵,既可以是一个二维数组,也可以是一个一维数组,只要按照“一行一行”的顺序、每一个像素占用一字节就行了。而恰巧,OpenCV中灰度图也是这个顺序,而且当图片是灰度图时,imageData字段就是这么一个灰度数组。所以直接代入即可。
使用如下命令编译:
g++ opencv_zbar.cpp -o opencv_zbar `pkg-config opencv --libs --cflags opencv` -lzbar
然后运行:
./opencv_zbar
程序运行后,如果在摄像头前摆放一个能够识别的二维码,那么就会不断输出二维码的内容和四个顶点的坐标: