单目相机测距之一

其他参考

  1. 基于OpenCV实现二维码发现与定位
  2. Zbar+ROS+opencv二维码识别与定位研究(一)

  3. 基于二维码的单目视觉测距移动机器人定位研究

  4. (opencv+Qt)的QR码精确定位与识别完全解析(精度可达±0.1mm,±0.1°)

  5. 单目摄像机测距(python+opencv)

  6. PnP 单目相机位姿估计(二):solvePnP利用二维码求解相机世界座标 

  7. PnP 单目相机位姿估计(三):二维码角点检测

基于二维码的室内定位技术(一)——原理

转自https://zhou-yuxin.github.io/articles/2017/%E5%9F%BA%E4%BA%8E%E4%BA%8C%E7%BB%B4%E7%A0%81%E7%9A%84%E5%AE%A4%E5%86%85%E5%AE%9A%E4%BD%8D%E6%8A%80%E6%9C%AF%EF%BC%88%E4%B8%80%EF%BC%89%E2%80%94%E2%80%94%E5%8E%9F%E7%90%86/index.html

https://zhou-yuxin.github.io/

哎,不知道怎么说呢。自从朱富帅丢下了这个锅,我就没有安宁过。大致说一下这个项目吧——一个小车,前面装了一个摄像头,当车看到一个二维码时,就要朝二维码开过去,而且需要保证最后是正对二维码中心顶上去。这个需求来自于导师要的自动无线充电的功能。无线充电需要比较高的对准精度,而目前的室内定位技术(基本是基于无线电的)还没这么高的精度,因此需要搞出一个基于机器视觉的。这个需求中,先不说路径规划的问题,首先要解决的就是基于二维码的定位问题。

第一步,很显然,就是通过摄像头不停读取图像,然后识别二维码,得到二维码的各种信息。读摄像头和识别二维码在前一篇《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

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章