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

程序运行后,如果在摄像头前摆放一个能够识别的二维码,那么就会不断输出二维码的内容和四个顶点的坐标: