00 OpenCV 編程簡介(矩陣/圖像/視頻的基本讀寫操作)

Post date: 2012/4/10 上午 08:33:24

Introduction to programming with OpenCV

OpenCV編程簡介

作者: Gady Agam

翻譯: chenyusiyuan

摘要: 本文旨在幫助讀者快速入門OpenCV,而無需閱讀冗長的參考手冊。掌握了OpenCV的以下基礎知識後,有需要的話再查閱相關的參考手冊。

一、簡介

1、OpenCV的特點

(1) 總體描述

  • OpenCV是一個基於C/C++語言的開源圖像處理函數庫
  • 其代碼都經過優化,可用於實時處理圖像
  • 具有良好的可移植性
  • 可以進行圖像/視頻載入、保存和採集的常規操作
  • 具有低級和高級的應用程式介面(API)
  • 提供了面向Intel IPP高效多媒體函數庫的介面,可針對你使用的Intel CPU優化代碼,提高程式性能(譯註:OpenCV 2.0版的代碼已顯著優化,無需IPP來提升性能,故2.0版不再提供IPP介面)

(2) 功能

  • 圖像數據操作(記憶體分配與釋放,圖像複製、設定和轉換)

Image data manipulation (allocation, release, copying, setting, conversion).

  • 圖像/視頻的輸入輸出(支持文件或攝像頭的輸入,圖像/視頻文件的輸出)

Image and video I/O (file and camera based input, image/video file output).

  • 矩陣/向量數據操作及線性代數運算(矩陣乘積、矩陣方程求解、特征值、奇異值分解)

Matrix and vector manipulation and linear algebra routines (products, solvers, eigenvalues, SVD).

  • 支持多種動態數據結構(鏈表、隊列、數據集、樹、圖)

Various dynamic data structures (lists, queues, sets, trees, graphs).

  • 基本圖像處理(去噪、邊緣檢測、角點檢測、採樣與插值、色彩變換、形態學處理、直方圖、圖像金字塔結構)

Basic image processing (filtering, edge detection, corner detection, sampling and interpolation, color conversion, morphological operations, histograms, image pyramids).

  • 結構分析(連通域/分支、輪廓處理、距離轉換、圖像矩、模板匹配、霍夫變換、多項式逼近、曲線擬合、橢圓擬合、狄勞尼三角化)

Structural analysis (connected components, contour processing, distance transform, various moments, template matching, Hough transform, polygonal approximation, line fitting, ellipse fitting, Delaunay triangulation).

  • 攝像頭定標(尋找和跟蹤定標模式、參數定標、基本矩陣估計、單應矩陣估計、立體視覺匹配)

Camera calibration (finding and tracking calibration patterns, calibration, fundamental matrix estimation, homography estimation, stereo correspondence).

  • 運動分析(光流、動作分割、目標跟蹤)

Motion analysis (optical flow, motion segmentation, tracking).

  • 目標識別(特征方法、HMM模型)

Object recognition (eigen-methods, HMM).

  • 基本的GUI(顯示圖像/視頻、鍵盤/滑鼠操作、滑動條)

Basic GUI (display image/video, keyboard and mouse handling, scroll-bars).

  • 圖像標註(直線、曲線、多邊形、文本標註)

Image labeling (line, conic, polygon, text drawing)

(3) OpenCV模塊

  • cv – 核心函數庫
  • cvaux – 輔助函數庫
  • cxcore – 數據結構與線性代數庫
  • highgui – GUI函數庫
  • ml – 機器學習函數庫

2、有用的學習資源

(1) 參考手冊:

  • <opencv-root>/docs/index.htm (譯註:在你的OpenCV安裝目錄<opencv-root>內)

(2) 網路資源:

(3) 書籍:

  • Open Source Computer Vision Library

by Gary R. Bradski, Vadim Pisarevsky, and Jean-Yves Bouguet, Springer, 1st ed. (June, 2006). chenyusiyuan: 補充以下書籍

  • Learning OpenCV - Computer Vision with the OpenCV Library

by Gary Bradski & Adrian Kaehler, O'Reilly Media, 1 st ed. (September, 2008).

  • OpenCV教程——基礎篇

作者:劉瑞禎 于仕琪,北京航空航天大學出版社,出版日期:200706

(4) 視頻處理常式(在 <opencv-root>/samples/c/):

  • 顏色跟蹤: camshiftdemo
  • 點跟蹤: lkdemo
  • 動作分割: motempl
  • 邊緣檢測: laplace

(5) 圖像處理常式 (在 <opencv-root>/samples/c/):

  • 邊緣檢測: edge
  • 圖像分割: pyramid_segmentation
  • 形態學: morphology
  • 直方圖: demhist
  • 距離變換: distrans
  • 橢圓擬合: fitellipse

3、OpenCV 命名規則

(1) 函數名:

cvActionTargetMod(...)

Action = 核心功能(core functionality) (e.g. set, create) Target = 目標圖像區域(target image area) (e.g. contour, polygon) Mod = (可選的)調整語(optional modifiers) (e.g. argument type)

(2) 矩陣數據類型:

CV_<bit_depth>(S|U|F)C<number_of_channels>

S = 符號整型 U = 無符號整型 F = 浮點型

E.g.: CV_8UC1 是指一個8位無符號整型單通道矩陣, CV_32FC2是指一個32位浮點型雙通道矩陣.

(3) 圖像數據類型:

IPL_DEPTH_<bit_depth>(S|U|F)

E.g.: IPL_DEPTH_8U 圖像像素數據是8位無符號整型. IPL_DEPTH_32F圖像像素數據是32位浮點型.

(4) 頭文件:

#include <cv.h> #include <cvaux.h> #include <highgui.h> #include <ml.h> #include <cxcore.h> // 一般不需要,cv.h 內已包含該頭文件

4、編譯建議

(1) Linux:

g++ hello-world.cpp -o hello-world \ -I /usr/local/include/opencv -L /usr/local/lib \ -lm -lcv -lhighgui -lcvaux

(2) Windows:

在Visual Studio的‘選項’和‘項目’中設置好OpenCV相關文件的路徑。

5、C常式

//////////////////////////////////////////////////////////////////////// // // hello-world.cpp // // 該程式從文件中讀入一幅圖像,將之反色,然後顯示出來. // //////////////////////////////////////////////////////////////////////// #include <stdlib.h> #include <stdio.h> #include <math.h> #include <cv.h> #include <highgui.h> int main(int argc, char *argv[]) { IplImage* img = 0; int height,width,step,channels; uchar *data; int i,j,k; if(argc<2){ printf("Usage: main <image-file-name>\n\7"); exit(0); } // load an image img=cvLoadImage(argv[1]); if(!img){ printf("Could not load image file: %s\n",argv[1]); exit(0); } // get the image data height = img->height; width = img->width; step = img->widthStep; channels = img->nChannels; data = (uchar *)img->imageData; printf("Processing a %dx%d image with %d channels\n",height,width,channels); // create a window cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE); cvMoveWindow("mainWin", 100, 100); // invert the image // 相當於 cvNot(img); // IplImage *pDstImg = cvCreateImage(cvGetSize(img),img->depth,img->nChannels); // cvNot(img, pDstImg); for(i=0;i<height;i++) for(j=0;j<width;j++) for(k=0;k<channels;k++) data[i*step+j*channels+k]=255-data[i*step+j*channels+k]; // show the image cvShowImage("mainWin", img ); // wait for a key cvWaitKey(0); // release the image cvReleaseImage(&img ); return 0; }

二、GUI 指令

1、視窗管理

(1) 創建和定位一個新視窗:

cvNamedWindow("win1", CV_WINDOW_AUTOSIZE); cvMoveWindow("win1", 100, 100); // offset from the UL corner of the screen

(2) 載入圖像:

IplImage* img=0; img=cvLoadImage(fileName, CV_LOAD_IMAGE_COLOR); if(!img) printf("Could not load image file: %s\n",fileName);

(3) 顯示圖像:

cvShowImage("win1",img);

該函數可以在上面建立的視窗(win1)中顯示彩色或灰度的位元組型/浮點型圖像。位元組型圖像像素值範圍為[0-255];浮點型圖像像素值範圍為[0-1]。彩色圖像的三色元素按BGR(藍-綠-紅)順序存儲。

(4) 關閉視窗:

cvDestroyWindow("win1");

(5) 改變視窗大小:

cvResizeWindow("win1",100,100); // new width/heigh in pixels

2、輸入處理

(1) 處理滑鼠事件:

  • 定義一個滑鼠處理程式:

void mouseHandler(int event, int x, int y, int flags, void* param) { switch(event){ case CV_EVENT_LBUTTONDOWN: if(flags & CV_EVENT_FLAG_CTRLKEY) printf("Left button down with CTRL pressed\n"); break; case CV_EVENT_LBUTTONUP: printf("Left button up\n"); break; } }

x,y: 相對於左上角的像素坐標

event: CV_EVENT_LBUTTONDOWN, CV_EVENT_RBUTTONDOWN, CV_EVENT_MBUTTONDOWN, CV_EVENT_LBUTTONUP, CV_EVENT_RBUTTONUP, CV_EVENT_MBUTTONUP, CV_EVENT_LBUTTONDBLCLK, CV_EVENT_RBUTTONDBLCLK, CV_EVENT_MBUTTONDBLCLK, CV_EVENT_MOUSEMOVE:

flags: CV_EVENT_FLAG_CTRLKEY, CV_EVENT_FLAG_SHIFTKEY, CV_EVENT_FLAG_ALTKEY, CV_EVENT_FLAG_LBUTTON, CV_EVENT_FLAG_RBUTTON, CV_EVENT_FLAG_MBUTTON

  • 註冊該事件處理程式:

mouseParam=5; cvSetMouseCallback("win1",mouseHandler,&mouseParam); //第三個參數可以設置為NULL

(2) 處理鍵盤事件:

  • 實際上對於鍵盤輸入並沒有專門的事件處理程式.
  • 按一定間隔檢測鍵盤輸入(適用於迴圈體中):

int key; key=cvWaitKey(10); // wait 10ms for input

  • 中止程式等待鍵盤輸入:

int key; key=cvWaitKey(0); // wait indefinitely for input

  • 鍵盤輸入的迴圈處理程式:

while(1){ key=cvWaitKey(10); if(key==27) break; switch(key){ case 'h': ... break; case 'i': ... break; } }

(3) 處理滑動條事件:

  • 定義一個滑動條處理程式:

void trackbarHandler(int pos) { printf("Trackbar position: %d\n",pos); }

  • 註冊該事件處理程式:

int trackbarVal=25; int maxVal=100; cvCreateTrackbar("bar1", "win1", &trackbarVal ,maxVal , trackbarHandler);

  • 獲取當前的滑動條位置:

int pos = cvGetTrackbarPos("bar1","win1");

  • 設置滑動條位置:

cvSetTrackbarPos("bar1", "win1", 25);

三、OpenCV的基本數據結構

(譯註:OpenCV 1.1、1.2或2.0版本中各數據結構的結構體元素有所調整,以下僅作參考)

1、圖像數據結構

(1) IPL 圖像:

IplImage |-- int nChannels; // 顏色通道數目 (1,2,3,4) |-- int depth; // 像素的位深: | // IPL_DEPTH_8U, IPL_DEPTH_8S, | // IPL_DEPTH_16U,IPL_DEPTH_16S, | // IPL_DEPTH_32S,IPL_DEPTH_32F, | // IPL_DEPTH_64F |-- int width; // 圖像寬度(像素為單位) |-- int height; // 圖像高度 |-- char* imageData; // 圖像數據指針 | // 註意彩色圖像按BGR順序存儲數據 |-- int dataOrder; // 0 - 將像素點不同通道的值交錯排在一起,形成單一像素平面 | // 1 - 把所有像素同通道值排在一起,形成若幹個通道平面,再把平面排列起來 | // cvCreateImage 只能創建像素交錯排列式的圖像 |-- int origin; // 0 – 像素原點為左上角, | // 1 – 像素原點為左下角 (Windows bitmaps style) |-- int widthStep; // 相鄰行的同列點之間的位元組數 |-- int imageSize; // 圖像的大小(位元組為單位) = height*widthStep |-- struct _IplROI *roi;// 圖像的感興趣區域(ROI). ROI非空時對圖像的 | // 處理僅限於ROI區域. |-- char *imageDataOrigin; // 圖像數據未對齊時的數據原點指針 | // (需要正確地重新分配圖像記憶體 ) | // (needed for correct image deallocation) |-- int align; // 圖像數據的行對齊: 4 or 8 byte alignment | // OpenCV 中無此項,採用widthStep代替 |-- char colorModel[4]; // 顏色模型 – OpenCV中忽略此項

2、矩陣與向量

(1) 矩陣:

CvMat // 2D 矩陣 |-- int type; // 元素類型 (uchar,short,int,float,double) 與標誌 |-- int step; // 整行長度位元組數 |-- int rows, cols; // 行、列數 |-- int height, width; // 矩陣高度、寬度,與rows、cols對應 |-- union data; |-- uchar* ptr; // data pointer for an unsigned char matrix |-- short* s; // data pointer for a short matrix |-- int* i; // data pointer for an integer matrix |-- float* fl; // data pointer for a float matrix |-- double* db; // data pointer for a double matrix

CvMatND // N-維矩陣 |-- int type; // 元素類型 (uchar,short,int,float,double) 與標誌 |-- int dims; // 矩陣維數 |-- union data; | |-- uchar* ptr; // data pointer for an unsigned char 矩陣 | |-- short* s; // data pointer for a short matrix | |-- int* i; // data pointer for an integer matrix | |-- float* fl; // data pointer for a float matrix | |-- double* db; // data pointer for a double matrix | |-- struct dim[]; // 各維信息 |-- size; // 元素數目 |-- step; // 元素間距(位元組為單位)

CvSparseMat // N-維稀疏矩陣

(2) 一般矩陣:

CvArr* // 僅作為函數定義的參數使用, // 表明函數可以接受不同類型的矩陣作為參數, // 例如:IplImage*, CvMat* 甚至是 CvSeq*. // 矩陣的類型通過矩陣頭的前4個位元組信息來確定

(3) 標量:

CvScalar |-- double val[4]; //4D 向量

初始化函數:

CvScalar s = cvScalar(double val0, double val1=0, double val2=0, double val3=0); // Example: CvScalar s = cvScalar(20.0); s.val[0]=20.0;

註意該初始化函數的函數名與對應的結構體名稱幾乎同名,差別僅在於函數名第一個字母是小寫的,而結構體名第一個字母是大寫的。它並不是一個 C++ 構造函數。(譯註:類似的還有 cvMat 與 CvMat、cvPoint 與 CvPoint 等等)

3、其它結構類型

(1) 點:

CvPoint p = cvPoint(int x, int y); CvPoint2D32f p = cvPoint2D32f(float x, float y); CvPoint3D32f p = cvPoint3D32f(float x, float y, float z); //E.g.: p.x=5.0; p.y=5.0;

(2) 矩形框大小(以像素為精度):

CvSize r = cvSize(int width, int height); CvSize2D32f r = cvSize2D32f(float width, float height);

(3) 矩形框的偏置和大小:

CvRect r = cvRect(int x, int y, int width, int height);

四、圖像處理

1、圖像的記憶體分配與釋放

(1) 分配記憶體給一幅新圖像:

IplImage* cvCreateImage(CvSize size, int depth, int channels);

size: cvSize(width,height);

depth: 像素深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,

IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F, IPL_DEPTH_64F

channels: 像素通道數. Can be 1, 2, 3 or 4.

各通道是交錯排列的. 一幅彩色圖像的數據排列格式如下:

b0 g0 r0 b1 g1 r1 ...

示例:

// Allocate a 1-channel byte image IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); // Allocate a 3-channel float image IplImage* img2=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);

(2) 釋放圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); cvReleaseImage(&img);

(3) 複製圖像:

IplImage* img1=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); IplImage* img2; img2=cvCloneImage(img1); // 註意通過cvCloneImage得到的圖像 // 也要用 cvReleaseImage 釋放,否則容易產生記憶體泄漏

(4) 設置/獲取感興趣區域ROI:

void cvSetImageROI(IplImage* image, CvRect rect); void cvResetImageROI(IplImage* image); CvRect cvGetImageROI(const IplImage* image);

大多數OpenCV函數都支持 ROI.

(5) 設置/獲取感興趣通道COI:

void cvSetImageCOI(IplImage* image, int coi); // 0=all int cvGetImageCOI(const IplImage* image);

大多數OpenCV函數不支持 COI.

2、圖像讀寫

(1) 從文件中讀入圖像:

IplImage* img=0; img=cvLoadImage(fileName); if(!img) printf("Could not load image file: %s\n",fileName);

支持的圖像格式: BMP, DIB, JPEG, JPG, JPE, PNG, PBM, PGM, PPM, SR, RAS, TIFF, TIF

OpenCV預設將讀入的圖像強制轉換為一幅三通道彩色圖像. 不過可以按以下方法修改讀入方式:

img=cvLoadImage(fileName,flag);

flag: >0 將讀入的圖像強制轉換為一幅三通道彩色圖像 =0 將讀入的圖像強制轉換為一幅單通道灰度圖像 <0 讀入的圖像通道數與所讀入的文件相同.

(2) 保存圖像:

if(!cvSaveImage(outFileName,img)) printf("Could not save: %s\n", outFileName);

保存的圖像格式由 outFileName 中的擴展名確定.

3、訪問圖像像素

(1) 假設你要訪問第k通道、第i行、第j列的像素。

(2) 間接訪問: (通用,但效率低,可訪問任意格式的圖像)

  • 對於單通道位元組型圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); CvScalar s; s=cvGet2D(img,i,j); // get the (j,i) pixel value, 註意cvGet2D與cvSet2D中坐標參數的順序與其它opencv函數坐標參數順序恰好相反.本函數中i代表y軸,即height;j代表x軸,即weight. printf("intensity=%f\n",s.val[0]); s.val[0]=111; cvSet2D(img,i,j,s); // set the (j,i) pixel value

  • 對於多通道位元組型/浮點型圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3); CvScalar s; s=cvGet2D(img,i,j); // get the (j,i) pixel value printf("B=%f, G=%f, R=%f\n",s.val[0],s.val[1],s.val[2]); s.val[0]=111; s.val[1]=111; s.val[2]=111; cvSet2D(img,i,j,s); // set the (j,i) pixel value

(3) 直接訪問: (效率高,但容易出錯)

  • 對於單通道位元組型圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); ((uchar *)(img->imageData + i*img->widthStep))[j]=111;

  • 對於多通道位元組型圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3); ((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 0]=111; // B ((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 1]=112; // G ((uchar *)(img->imageData + i*img->widthStep))[j*img->nChannels + 2]=113; // R

  • 對於多通道浮點型圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3); ((float *)(img->imageData + i*img->widthStep))[j*img->nChannels + 0]=111; // B ((float *)(img->imageData + i*img->widthStep))[j*img->nChannels + 1]=112; // G ((float *)(img->imageData + i*img->widthStep))[j*img->nChannels + 2]=113; // R

(4) 基於指針的直接訪問: (簡單高效)

  • 對於單通道位元組型圖像:

IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); int height = img->height; int width = img->width; int step = img->widthStep; uchar* data = (uchar *)img->imageData; data[i*step+j] = 111;

  • 對於多通道位元組型圖像:

IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3); int height = img->height; int width = img->width; int step = img->widthStep; int channels = img->nChannels; uchar* data = (uchar *)img->imageData; data[i*step+j*channels+k] = 111;

  • 對於多通道浮點型圖像(假設圖像數據採用4位元組(32位)行對齊方式):

IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3); int height = img->height; int width = img->width; int step = img->widthStep; int channels = img->nChannels; float * data = (float *)img->imageData; data[i*step+j*channels+k] = 111;

(5) 基於 c++ wrapper 的直接訪問: (更簡單高效)

  • 首先定義一個 c++ wrapper ‘Image’,然後基於Image定義不同類型的圖像:

template<class T> class Image { private: IplImage* imgp; public: Image(IplImage* img=0) {imgp=img;} ~Image(){imgp=0;} void operator=(IplImage* img) {imgp=img;} inline T* operator[](const int rowIndx) { return ((T *)(imgp->imageData + rowIndx*imgp->widthStep));} }; typedef struct{ unsigned char b,g,r; } RgbPixel; typedef struct{ float b,g,r; } RgbPixelFloat; typedef Image<RgbPixel> RgbImage; typedef Image<RgbPixelFloat> RgbImageFloat; typedef Image<unsigned char> BwImage; typedef Image<float> BwImageFloat;

  • 對於單通道位元組型圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); BwImage imgA(img); imgA[i][j] = 111;

  • 對於多通道位元組型圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3); RgbImage imgA(img); imgA[i][j].b = 111; imgA[i][j].g = 111; imgA[i][j].r = 111;

  • 對於多通道浮點型圖像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3); RgbImageFloat imgA(img); imgA[i][j].b = 111; imgA[i][j].g = 111; imgA[i][j].r = 111;

4、圖像轉換

(1) 位元組型圖像的灰度-彩色轉換:

cvConvertImage(src, dst, flags=0);

src = float/byte grayscale/color image dst = byte grayscale/color image flags = CV_CVTIMG_FLIP (垂直翻轉圖像) CV_CVTIMG_SWAP_RB (置換 R 和 B 通道)

(2) 彩色圖像->灰度圖像:

// Using the OpenCV conversion: cvCvtColor(cimg,gimg,CV_BGR2GRAY); // cimg -> gimg // Using a direct conversion: for(i=0;i<cimg->height;i++) for(j=0;j<cimg->width;j++) gimgA[i][j]= (uchar)(cimgA[i][j].b*0.114 + cimgA[i][j].g*0.587 + cimgA[i][j].r*0.299);

(3) 不同彩色空間之間的轉換:

cvCvtColor(src,dst,code); // src -> dst

code = CV_<X>2<Y> <X>/<Y> = RGB, BGR, GRAY, HSV, YCrCb, XYZ, Lab, Luv, HLS

e.g.: CV_BGR2GRAY, CV_BGR2HSV, CV_BGR2Lab

5、繪圖指令

(1) 繪製矩形:

// 在點 (100,100) 和 (200,200) 之間繪製一矩形,邊線用紅色、寬度為 1 cvRectangle(img, cvPoint(100,100), cvPoint(200,200), cvScalar(0,0,255), 1);

(2) 繪製圓形:

// 圓心為(100,100)、半徑為20. 圓周綠色、寬度為1 cvCircle(img, cvPoint(100,100), 20, cvScalar(0,255,0), 1);

(3) 繪製線段:

// 在 (100,100) 和 (200,200) 之間、線寬為 1 的綠色線段 cvLine(img, cvPoint(100,100), cvPoint(200,200), cvScalar(0,255,0), 1);

(4) 繪製一組線段:

CvPoint curve1[]={10,10, 10,100, 100,100, 100,10}; CvPoint curve2[]={30,30, 30,130, 130,130, 130,30, 150,10}; CvPoint* curveArr[2]={curve1, curve2}; int nCurvePts[2]={4,5}; int nCurves=2; int isCurveClosed=1; int lineWidth=1; cvPolyLine(img,curveArr,nCurvePts,nCurves,isCurveClosed,cvScalar(0,255,255),lineWidth); void cvPolyLine( CvArr* img, CvPoint** pts, int* npts, int contours, int is_closed, CvScalar color, int thickness=1, int line_type=8, int shift=0 ); img 圖像。 pts 折線的頂點指針數組。 npts 折線的定點個數數組。也可以認為是pts指針數組的大小 contours 折線的線段數量。 is_closed 指出多邊形是否封閉。如果封閉,函數將起始點和結束點連線。 color 折線的顏色。 thickness 線條的粗細程度。 line_type 線段的類型。參見cvLine。 shift 頂點的小數點位數

(5) 繪製一組填充顏色的多邊形:

cvFillPoly(img,curveArr,nCurvePts,nCurves,cvScalar(0,255,255)); cvFillPoly用於一個單獨被多邊形輪廓所限定的區域內進行填充。函數可以填充複雜的區域,例如,有漏洞的區域和有交叉點的區域等等。 void cvFillPoly( CvArr* img, CvPoint** pts, int* npts, int contours,CvScalar color, int line_type=8, int shift=0 ); img 圖像。 pts 指向多邊形的數組指針。 npts 多邊形的頂點個數的數組。 contours 組成填充區域的線段的數量。 color 多邊形的顏色。 line_type 組成多邊形的線條的類型。 shift 頂點坐標的小數點位數。

(6) 文本標註:

CvFont font; double hScale=1.0; double vScale=1.0; int lineWidth=1; cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, hScale,vScale,0,lineWidth); cvPutText (img,"My comment",cvPoint(200,400), &font, cvScalar(255,255,0));

其它可用的字體類型有: CV_FONT_HERSHEY_SIMPLEX, CV_FONT_HERSHEY_PLAIN, CV_FONT_HERSHEY_DUPLEX, CV_FONT_HERSHEY_COMPLEX, CV_FONT_HERSHEY_TRIPLEX, CV_FONT_HERSHEY_COMPLEX_SMALL, CV_FONT_HERSHEY_SCRIPT_SIMPLEX, CV_FONT_HERSHEY_SCRIPT_COMPLEX,

五、矩陣處理

1、矩陣的記憶體分配與釋放

(1) 總體上:

  • OpenCV 使用C語言來進行矩陣操作。不過實際上有很多C++語言的替代方案可以更高效地完成。
  • 在OpenCV中向量被當做是有一個維數為1的N維矩陣.
  • 矩陣按行-行方式存儲,每行以4位元組(32位)對齊.

(2) 為新矩陣分配記憶體:

CvMat* cvCreateMat(int rows, int cols, int type);

type: 矩陣元素類型. 按CV_<bit_depth>(S|U|F)C<number_of_channels> 方式指定. 例如: CV_8UC1 、CV_32SC2. 示例:

CvMat* M = cvCreateMat(4,4,CV_32FC1);

(3) 釋放矩陣記憶體:

CvMat* M = cvCreateMat(4,4,CV_32FC1); cvReleaseMat(&M);

(4) 複製矩陣:

CvMat* M1 = cvCreateMat(4,4,CV_32FC1); CvMat* M2; M2=cvCloneMat(M1);

(5) 初始化矩陣:

double a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; CvMat Ma=cvMat(3, 4, CV_64FC1, a); //等價於: CvMat Ma; cvInitMatHeader(&Ma, 3, 4, CV_64FC1, a);

(6) 初始化矩陣為單位矩陣:

CvMat* M = cvCreateMat(4,4,CV_32FC1); cvSetIdentity(M); // does not seem to be working properly

2、訪問矩陣元素

(1) 假設需要訪問一個2D浮點型矩陣的第(i, j)個單元.

(2) 間接訪問:

cvmSet(M,i,j,2.0); // Set M(i,j) t = cvmGet(M,i,j); // Get M(i,j)

(3) 直接訪問(假設矩陣數據按4位元組行對齊):

CvMat* M = cvCreateMat(4,4,CV_32FC1); int n = M->cols; float *data = M->data.fl; data[i*n+j] = 3.0;

(4) 直接訪問(當數據的行對齊可能存在間隙時 possible alignment gaps):

CvMat* M = cvCreateMat(4,4,CV_32FC1); int step = M->step/sizeof(float); float *data = M->data.fl; (data+i*step)[j] = 3.0;

(5) 對於初始化後的矩陣進行直接訪問:

double a[16]; CvMat Ma = cvMat(3, 4, CV_64FC1, a); a[i*4+j] = 2.0; // Ma(i,j)=2.0;

3、矩陣/向量運算

(1) 矩陣之間的運算:

CvMat *Ma, *Mb, *Mc; cvAdd(Ma, Mb, Mc); // Ma+Mb -> Mc cvSub(Ma, Mb, Mc); // Ma-Mb -> Mc cvMatMul(Ma, Mb, Mc); // Ma*Mb -> Mc

(2) 矩陣之間的元素級運算:

CvMat *Ma, *Mb, *Mc; cvMul(Ma, Mb, Mc); // Ma.*Mb -> Mc cvDiv(Ma, Mb, Mc); // Ma./Mb -> Mc cvAddS(Ma, cvScalar(-10.0), Mc); // Ma.-10 -> Mc

(3) 向量乘積:

double va[] = {1, 2, 3}; double vb[] = {0, 0, 1}; double vc[3]; CvMat Va=cvMat(3, 1, CV_64FC1, va); CvMat Vb=cvMat(3, 1, CV_64FC1, vb); CvMat Vc=cvMat(3, 1, CV_64FC1, vc); double res=cvDotProduct(&Va,&Vb); // 向量點乘: Va . Vb -> res cvCrossProduct(&Va, &Vb, &Vc); // 向量叉乘: Va x Vb -> Vc

註意在進行叉乘運算時,Va, Vb, Vc 必須是僅有3個元素的向量.

(4) 單一矩陣的運算:

CvMat *Ma, *Mb; cvTranspose(Ma, Mb); // 轉置:transpose(Ma) -> Mb (註意轉置陣不能返回給Ma本身) CvScalar t = cvTrace(Ma); // 跡:trace(Ma) -> t.val[0] double d = cvDet(Ma); // 行列式:det(Ma) -> d cvInvert(Ma, Mb); // 逆矩陣:inv(Ma) -> Mb

(5) 非齊次線性方程求解:

CvMat* A = cvCreateMat(3,3,CV_32FC1); CvMat* x = cvCreateMat(3,1,CV_32FC1); CvMat* b = cvCreateMat(3,1,CV_32FC1); cvSolve(&A, &b, &x); // solve (Ax=b) for x

(6) 特征值與特征向量 (矩陣為方陣):

CvMat* A = cvCreateMat(3,3,CV_32FC1); CvMat* E = cvCreateMat(3,3,CV_32FC1); CvMat* l = cvCreateMat(3,1,CV_32FC1); cvEigenVV(A, E, l); // l = A 的特征值(遞減順序) // E = 對應的特征向量 (行向量)

(7) 奇異值分解(SVD):====

CvMat* A = cvCreateMat(3,3,CV_32FC1); CvMat* U = cvCreateMat(3,3,CV_32FC1); CvMat* D = cvCreateMat(3,3,CV_32FC1); CvMat* V = cvCreateMat(3,3,CV_32FC1); cvSVD(A, D, U, V, CV_SVD_U_T|CV_SVD_V_T); // A = U D V^T

標誌位使矩陣U或V按轉置形式返回 (若不轉置可能運算出錯).

六、視頻處理

1、從視頻流中捕捉一幀畫面

(1) OpenCV 支持從攝像頭或視頻文件(AVI格式)中捕捉幀畫面.

(2) 初始化一個攝像頭捕捉器:

CvCapture* capture = cvCaptureFromCAM(0); // capture from video device #0

(3) 初始化一個視頻文件捕捉器:

CvCapture* capture = cvCaptureFromAVI("infile.avi");

(4) 捕捉一幀畫面:

IplImage* img = 0; if(!cvGrabFrame(capture)){ // capture a frame printf("Could not grab a frame\n\7"); exit(0); } img=cvRetrieveFrame(capture); // retrieve the captured frame

若要從多個攝像頭中同步捕捉畫面,則須首先從每個攝像頭中抓取一幀,緊接著要將被捕捉的幀畫面恢復到一個IplImage*型圖像中。(譯註:這一過程其實可以用 cvQueryFrame() 函數一步完成)

(5) 釋放視頻流捕捉器:

cvReleaseCapture(&capture);

註意由視頻流捕捉器得到的圖像是由捕捉器分配和釋放記憶體的,不需要單獨對圖像進行釋放記憶體的操作。

2、獲取/設置視頻流信息

(1) 獲取視頻流設備信息:

cvQueryFrame(capture); // 在讀取視頻流信息前,要先執行此操作 int frameH = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT); int frameW = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH); int fps = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FPS); int numFrames = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_COUNT);

統計總幀數僅對視頻文件有效,但似乎不太準確(譯註:也許OpenCV2.0中此問題已解決)

(2) 獲取幀圖信息:

float posMsec = cvGetCaptureProperty(capture, CV_CAP_PROP_POS_MSEC); int posFrames = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_POS_FRAMES); float posRatio = cvGetCaptureProperty(capture, CV_CAP_PROP_POS_AVI_RATIO);

所抓取的幀的位置有三種表達方式:距離第一幀畫面的時間間隔(毫秒為單位), 或者距離第一幀畫面(序列號為0)的序列數. 第三種方式是按相對比率,第一幀的相對比率為0,最後一幀的相對比率為1. 此方式僅對讀取視頻文件時有效.

(3) 設置從視頻文件抓取的第一幀畫面的位置:

// start capturing from a relative position of 0.9 of a video file cvSetCaptureProperty(capture, CV_CAP_PROP_POS_AVI_RATIO, (double)0.9);

註意此方法定位並不准確。

3、保存視頻文件

(1) 初始化視頻編寫器:

CvVideoWriter *writer = 0; int isColor = 1; int fps = 25; // or 30 int frameW = 640; // 744 for firewire cameras int frameH = 480; // 480 for firewire cameras writer=cvCreateVideoWriter("out.avi",CV_FOURCC('P','I','M','1'), fps,cvSize(frameW,frameH),isColor);

其它的編碼器代號包括: CV_FOURCC('P','I','M','1') = MPEG-1 codec CV_FOURCC('M','J','P','G') = motion-jpeg codec (does not work well) CV_FOURCC('M', 'P', '4', '2') = MPEG-4.2 codec CV_FOURCC('D', 'I', 'V', '3') = MPEG-4.3 codec CV_FOURCC('D', 'I', 'V', 'X') = MPEG-4 codec CV_FOURCC('U', '2', '6', '3') = H263 codec CV_FOURCC('I', '2', '6', '3') = H263I codec CV_FOURCC('F', 'L', 'V', '1') = FLV1 codec 若編碼器代號為 -1,則運行時會彈出一個編碼器選擇框.

(2) 保持視頻文件:

IplImage* img = 0; int nFrames = 50; for(i=0;i<nFrames;i++){ cvGrabFrame(capture); // capture a frame img=cvRetrieveFrame(capture); // retrieve the captured frame // img = cvQueryFrame(capture); cvWriteFrame(writer,img); // add the frame to the file }

要查看所抓取到的幀畫面,可以在迴圈中加入以下語句:

cvShowImage("mainWin", img); key=cvWaitKey(20); // wait 20 ms

註意 cvWaitKey 參數應該不小於 20 ms,否則畫面的顯示可能出錯.

(3) 釋放視頻編寫器:

cvReleaseVideoWriter(&writer);

By Gady Agam 2006-03-31 翻譯:chenyusiyuan 2010-1-26