其他參考
基於二維碼的室內定位技術(一)——原理
哎,不知道怎麼說呢。自從朱富帥丟下了這個鍋,我就沒有安寧過。大致說一下這個項目吧——一個小車,前面裝了一個攝像頭,當車看到一個二維碼時,就要朝二維碼開過去,而且需要保證最後是正對二維碼中心頂上去。這個需求來自於導師要的自動無線充電的功能。無線充電需要比較高的對準精度,而目前的室內定位技術(基本是基於無線電的)還沒這麼高的精度,因此需要搞出一個基於機器視覺的。這個需求中,先不說路徑規劃的問題,首先要解決的就是基於二維碼的定位問題。
第一步,很顯然,就是通過攝像頭不停讀取圖像,然後識別二維碼,得到二維碼的各種信息。讀攝像頭和識別二維碼在前一篇《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
程序運行後,如果在攝像頭前擺放一個能夠識別的二維碼,那麼就會不斷輸出二維碼的內容和四個頂點的座標: