OpenCV之core 模块. 核心功能(2)基本绘图 随机数发生器绘制文字 离散傅立叶变换 输入输出XML和YAML文件 与 OpenCV 1 同时使用
基本繪圖
目的
本節(jié)你將學(xué)到:
- 如何用?Point?在圖像中定義 2D 點(diǎn)
- 如何以及為何使用?Scalar
- 用OpenCV的函數(shù)?line?繪?直線
- 用OpenCV的函數(shù)?ellipse?繪?橢圓
- 用OpenCV的函數(shù)?rectangle?繪?矩形
- 用OpenCV的函數(shù)?circle?繪?圓
- 用OpenCV的函數(shù)?fillPoly?繪?填充的多邊形
OpenCV 原理
本節(jié)中,我門將大量使用?Point?和?Scalar?這兩個(gè)結(jié)構(gòu):
Point
次數(shù)據(jù)結(jié)構(gòu)表示了由其圖像坐標(biāo)??和??指定的2D點(diǎn)。可定義為: Point pt; pt.x = 10; pt.y = 8;或者
Point pt = Point(10, 8);Scalar
-
表示了具有4個(gè)元素的數(shù)組。次類型在OpenCV中被大量用于傳遞像素值。
-
本節(jié)中,我們將進(jìn)一步用它來表示RGB顏色值(三個(gè)參數(shù))。如果用不到第四個(gè)參數(shù),則無需定義。
-
我們來看個(gè)例子,如果給出以下顏色參數(shù)表達(dá)式:
Scalar( a, b, c )那么定義的RGB顏色值為:?Red = c,?Green = b?and?Blue = a
代碼
- 這些代碼都來自O(shè)penCV代碼的sample文件夾。或者可?點(diǎn)擊此處?獲取。
代碼分析
我們打算畫兩個(gè)例子(原子和賭棍), 所以必須創(chuàng)建兩個(gè)圖像和對(duì)應(yīng)的窗口以顯示。
/// 窗口名字 char atom_window[] = "Drawing 1: Atom"; char rook_window[] = "Drawing 2: Rook";/// 創(chuàng)建空全黑像素的空?qǐng)D像 Mat atom_image = Mat::zeros( w, w, CV_8UC3 ); Mat rook_image = Mat::zeros( w, w, CV_8UC3 );創(chuàng)建用來畫不同幾何形狀的函數(shù)。比如用?MyEllipse?和?MyFilledCircle?來畫原子。
/// 1. 畫一個(gè)簡(jiǎn)單的原子。/// 1.a. 創(chuàng)建橢圓 MyEllipse( atom_image, 90 ); MyEllipse( atom_image, 0 ); MyEllipse( atom_image, 45 ); MyEllipse( atom_image, -45 );/// 1.b. 創(chuàng)建圓 MyFilledCircle( atom_image, Point( w/2.0, w/2.0) );接下來用?MyLine*,*rectangle?和 a?MyPolygon?來畫賭棍:
/// 2. 畫一個(gè)賭棍/// 2.a. 創(chuàng)建一個(gè)凸多邊形 MyPolygon( rook_image );/// 2.b. 創(chuàng)建矩形 rectangle( rook_image,Point( 0, 7*w/8.0 ),Point( w, w),Scalar( 0, 255, 255 ),-1,8 );/// 2.c. 畫幾條直線 MyLine( rook_image, Point( 0, 15*w/16 ), Point( w, 15*w/16 ) ); MyLine( rook_image, Point( w/4, 7*w/8 ), Point( w/4, w ) ); MyLine( rook_image, Point( w/2, 7*w/8 ), Point( w/2, w ) ); MyLine( rook_image, Point( 3*w/4, 7*w/8 ), Point( 3*w/4, w ) );現(xiàn)在來看看每個(gè)函數(shù)內(nèi)部如何定義:
-
MyLine
void MyLine( Mat img, Point start, Point end ) {int thickness = 2;int lineType = 8;line( img,start,end,Scalar( 0, 0, 0 ),thickness,lineType ); }正如我們所見,?MyLine?調(diào)用函數(shù)?line?來實(shí)現(xiàn)以下操作:
- 畫一條從點(diǎn)?start?到點(diǎn)?end?的直線段
- 此線段將被畫到圖像?img?上
- 線的顏色由?Scalar( 0, 0, 0)?來定義,在此其相應(yīng)RGB值為?黑色
- 線的粗細(xì)由?thickness?設(shè)定(此處設(shè)為 2)
- 此線為8聯(lián)通 (lineType?= 8)
-
MyEllipse
void MyEllipse( Mat img, double angle ) {int thickness = 2;int lineType = 8;ellipse( img,Point( w/2.0, w/2.0 ),Size( w/4.0, w/16.0 ),angle,0,360,Scalar( 255, 0, 0 ),thickness,lineType ); }根據(jù)以上代碼,我們可看到函數(shù)?ellipse?按照以下規(guī)則繪制橢圓:
- 橢圓將被畫到圖像?img?上
- 橢圓中心為點(diǎn)?(w/2.0, w/2.0)?并且大小位于矩形?(w/4.0, w/16.0)?內(nèi)
- 橢圓旋轉(zhuǎn)角度為?angle
- 橢圓擴(kuò)展的弧度從?0?度到?360?度
- 圖形顏色為?Scalar( 255, 255, 0)?,既藍(lán)色
- 繪橢圓的線粗為?thickness?,此處是2
-
MyFilledCircle
void MyFilledCircle( Mat img, Point center ) {int thickness = -1;int lineType = 8;circle( img,center,w/32.0,Scalar( 0, 0, 255 ),thickness,lineType ); }類似于橢圓函數(shù),我們可以看到?circle?函數(shù)的參數(shù)意義如下:
- 圓將被畫到圖像 (?img?)上
- 圓心由點(diǎn)?center?定義
- 圓的半徑為:?w/32.0
- 圓的顏色為:?Scalar(0, 0, 255)?,按BGR的格式為?紅色
- 線粗定義為?thickness?= -1, 因此次圓將被填充
-
MyPolygon
void MyPolygon( Mat img ) {int lineType = 8;/** 創(chuàng)建一些點(diǎn) */Point rook_points[1][20];rook_points[0][0] = Point( w/4.0, 7*w/8.0 );rook_points[0][1] = Point( 3*w/4.0, 7*w/8.0 );rook_points[0][2] = Point( 3*w/4.0, 13*w/16.0 );rook_points[0][3] = Point( 11*w/16.0, 13*w/16.0 );rook_points[0][4] = Point( 19*w/32.0, 3*w/8.0 );rook_points[0][5] = Point( 3*w/4.0, 3*w/8.0 );rook_points[0][6] = Point( 3*w/4.0, w/8.0 );rook_points[0][7] = Point( 26*w/40.0, w/8.0 );rook_points[0][8] = Point( 26*w/40.0, w/4.0 );rook_points[0][9] = Point( 22*w/40.0, w/4.0 );rook_points[0][10] = Point( 22*w/40.0, w/8.0 );rook_points[0][11] = Point( 18*w/40.0, w/8.0 );rook_points[0][12] = Point( 18*w/40.0, w/4.0 );rook_points[0][13] = Point( 14*w/40.0, w/4.0 );rook_points[0][14] = Point( 14*w/40.0, w/8.0 );rook_points[0][15] = Point( w/4.0, w/8.0 );rook_points[0][16] = Point( w/4.0, 3*w/8.0 );rook_points[0][17] = Point( 13*w/32.0, 3*w/8.0 );rook_points[0][18] = Point( 5*w/16.0, 13*w/16.0 );rook_points[0][19] = Point( w/4.0, 13*w/16.0) ;const Point* ppt[1] = { rook_points[0] };int npt[] = { 20 };fillPoly( img,ppt,npt,1,Scalar( 255, 255, 255 ),lineType );}我們用函數(shù) :fill_poly:`fillPoly <>` 來繪制填充的多邊形。請(qǐng)注意:- 多邊形將被畫到圖像?img?上
- 多邊形的頂點(diǎn)集為?ppt
- 要繪制的多邊形頂點(diǎn)數(shù)目為?npt
- 要繪制的多邊形數(shù)量僅為?1
- 多邊形的顏色定義為?Scalar( 255, 255, 255), 既BGR值為?白色
-
rectangle
rectangle( rook_image,Point( 0, 7*w/8.0 ),Point( w, w),Scalar( 0, 255, 255 ),-1,8 );最后是函數(shù):rectangle:rectangle <>?(我們并沒有為這家伙創(chuàng)建特定函數(shù))。請(qǐng)注意:
- 矩形將被畫到圖像?rook_image?上
- 矩形兩個(gè)對(duì)角頂點(diǎn)為?Point( 0, 7*w/8.0 )?和?Point( w, w)
- 矩形的顏色為?Scalar(0, 255, 255)?,既BGR格式下的?黃色
- 由于線粗為?-1, 此矩形將被填充
結(jié)果
編譯并運(yùn)行例程,你將看到如下結(jié)果:
隨機(jī)數(shù)發(fā)生器&繪制文字
目的
本節(jié)你將學(xué)到:
- 使用?隨機(jī)數(shù)發(fā)生器類?(RNG) 并得到均勻分布的隨機(jī)數(shù)。
- 通過使用函數(shù)?putText?顯示文字。
代碼
- 在之前的章節(jié)中 (基本繪圖) 我們繪制過不同的幾何圖形, 我提供了一些繪制參數(shù),比如 coordinates(坐標(biāo)) (在繪制點(diǎn)Points?的時(shí)候 ), color(顏色), thickness(線條-粗細(xì),點(diǎn)-大小), 等等... ,你會(huì)發(fā)現(xiàn)我們給出了這些參數(shù)明確的數(shù)值。
- 在本章中, 我們會(huì)試著賦予這些參數(shù)?random隨機(jī)?的數(shù)值。 并且, 我們會(huì)試圖在圖像上繪制大量的幾何圖形. 因?yàn)槲覀儗⒂秒S機(jī)的方式初始化這些圖形, 這個(gè)過程將很自然的用到?loops循環(huán)?.
- 本代碼在OpenCV的sample文件夾下,如果招不到,你可以從這里?here 得到它:?.
說明
讓我們檢視?main?函數(shù)。我們發(fā)現(xiàn)第一步是實(shí)例化一個(gè)?Random Number Generator(隨機(jī)數(shù)發(fā)生器對(duì)象)?(RNG):
RNG rng( 0xFFFFFFFF );RNG的實(shí)現(xiàn)了一個(gè)隨機(jī)數(shù)發(fā)生器。 在上面的例子中,?rng?是用數(shù)值?0xFFFFFFFF?來實(shí)例化的一個(gè)RNG對(duì)象。
然后我們初始化一個(gè)?0?矩陣(代表一個(gè)全黑的圖像), 并且指定它的寬度,高度,和像素格式:
/// 初始化一個(gè)0矩陣 Mat image = Mat::zeros( window_height, window_width, CV_8UC3 );/// 把它會(huì)知道一個(gè)窗口中 imshow( window_name, image );然后我們開始瘋狂的繪制。看過代碼時(shí)候你會(huì)發(fā)現(xiàn)它主要分八個(gè)部分,正如函數(shù)定義的一樣:
/// 現(xiàn)在我們先畫線 c = Drawing_Random_Lines(image, window_name, rng); if( c != 0 ) return 0;/// 繼續(xù),這次是一些矩形 c = Drawing_Random_Rectangles(image, window_name, rng); if( c != 0 ) return 0;/// 畫一些弧線 c = Drawing_Random_Ellipses( image, window_name, rng ); if( c != 0 ) return 0;/// 畫一些折線 c = Drawing_Random_Polylines( image, window_name, rng ); if( c != 0 ) return 0;/// 畫被填充的多邊形 c = Drawing_Random_Filled_Polygons( image, window_name, rng ); if( c != 0 ) return 0;/// 畫圓 c = Drawing_Random_Circles( image, window_name, rng ); if( c != 0 ) return 0;/// 在隨機(jī)的地方繪制文字 c = Displaying_Random_Text( image, window_name, rng ); if( c != 0 ) return 0;/// Displaying the big end! c = Displaying_Big_End( image, window_name, rng );所有這些范數(shù)都遵循相同的模式,所以我們只分析其中的一組,因?yàn)檫@適用于所有。
查看函數(shù)?Drawing_Random_Lines:
int Drawing_Random_Lines( Mat image, char* window_name, RNG rng ) {int lineType = 8;Point pt1, pt2;for( int i = 0; i < NUMBER; i++ ){pt1.x = rng.uniform( x_1, x_2 );pt1.y = rng.uniform( y_1, y_2 );pt2.x = rng.uniform( x_1, x_2 );pt2.y = rng.uniform( y_1, y_2 );line( image, pt1, pt2, randomColor(rng), rng.uniform(1, 10), 8 );imshow( window_name, image );if( waitKey( DELAY ) >= 0 ){ return -1; }}return 0; }我們可以看到:
-
for?循環(huán)將重復(fù)?NUMBER?次。 并且函數(shù)?line?在循環(huán)中, 這意味著要生成?NUMBER?條線段。
-
線段的兩個(gè)端點(diǎn)分別是?pt1?和?pt2. 對(duì)于?pt1?我們看到:
pt1.x = rng.uniform( x_1, x_2 ); pt1.y = rng.uniform( y_1, y_2 );-
我們知道?rng?是一個(gè)?隨機(jī)數(shù)生成器?對(duì)象。在上面的代碼中我們調(diào)用了?rng.uniform(a,b)?。這指定了一個(gè)在?a和?b?之間的均勻分布(包含?a, 但不含?b)。
-
由上面的說明,我們可以推斷出?pt1?和?pt2?將會(huì)是隨機(jī)的數(shù)值,因此產(chǎn)生的線段是變幻不定的,這會(huì)產(chǎn)生一個(gè)很好的視覺效果(從下面繪制的圖片可以看出)。
-
我們還可以發(fā)現(xiàn), 在?line?的參數(shù)設(shè)置中,對(duì)于?color?的設(shè)置我們用了:
randomColor(rng)讓我們來看看函數(shù)的實(shí)現(xiàn):
static Scalar randomColor( RNG& rng ){int icolor = (unsigned) rng;return Scalar( icolor&255, (icolor>>8)&255, (icolor>>16)&255 );}正如我們看到的,函數(shù)的返回值是一個(gè)用三個(gè)隨機(jī)數(shù)初始化的?Scalar?對(duì)象,這三個(gè)隨機(jī)數(shù)代表了顏色的?R,?G,?B分量。所以,線段的顏色也是隨機(jī)的!
-
上面的解釋同樣適用于其它的幾何圖形,比如說參數(shù)?center(圓心)?和?vertices(頂點(diǎn))?也是隨機(jī)的。
在結(jié)束之前,我們還應(yīng)該看看函數(shù)?Display_Random_Text?和?Displaying_Big_End, 因?yàn)樗鼈冇幸恍┯腥さ奶卣?
Display_Random_Text:
int Displaying_Random_Text( Mat image, char* window_name, RNG rng ) {int lineType = 8;for ( int i = 1; i < NUMBER; i++ ){Point org;org.x = rng.uniform(x_1, x_2);org.y = rng.uniform(y_1, y_2);putText( image, "Testing text rendering", org, rng.uniform(0,8),rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);imshow( window_name, image );if( waitKey(DELAY) >= 0 ){ return -1; }}return 0; }這些看起來都很熟悉,但是這一句:
putText( image, "Testing text rendering", org, rng.uniform(0,8),rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);函數(shù)?putText?都做了些什么?在我們的例子中:
- 在?image?上繪制文字?“Testing text rendering”?。
- 文字的左下角將用點(diǎn)?org?指定。
- 字體參數(shù)是用一個(gè)在??之間的整數(shù)來定義。
- 字體的縮放比例是用表達(dá)式?rng.uniform(0, 100)x0.05 + 0.1?指定(表示它的范圍是?)。
- 字體的顏色是隨機(jī)的 (記為?randomColor(rng))。
- 字體的粗細(xì)范圍是從 1 到 10, 表示為?rng.uniform(1,10)?。
因此, 我們將繪制 (與其余函數(shù)類似)?NUMBER?個(gè)文字到我們的圖片上,以位置隨機(jī)的方式。
Displaying_Big_End
int Displaying_Big_End( Mat image, char* window_name, RNG rng ) {Size textsize = getTextSize("OpenCV forever!", CV_FONT_HERSHEY_COMPLEX, 3, 5, 0);Point org((window_width - textsize.width)/2, (window_height - textsize.height)/2);int lineType = 8;Mat image2;for( int i = 0; i < 255; i += 2 ){image2 = image - Scalar::all(i);putText( image2, "OpenCV forever!", org, CV_FONT_HERSHEY_COMPLEX, 3,Scalar(i, i, 255), 5, lineType );imshow( window_name, image2 );if( waitKey(DELAY) >= 0 ){ return -1; }}return 0; }除了?getTextSize?(用于獲取文字的大小參數(shù)), 我們可以發(fā)現(xiàn)在?for?循環(huán)里的新操作:
image2 = image - Scalar::all(i)**image2** 是 **image** 和 **Scalar::all(i)** 的差。事實(shí)上,**image2** 的每個(gè)像素都是 **image** 的每個(gè)像素減去 **i** (對(duì)于每個(gè)像素,都是由R,G,B三個(gè)分量組成,每個(gè)分量都會(huì)獨(dú)立做差)的差。結(jié)果
正如你在代碼部分看到的, 程序?qū)⒁来螆?zhí)行不同的繪圖函數(shù),這將:
首先?NUMBER?條線段將出現(xiàn)在屏幕上,正如截圖所示:
然后,一個(gè)新的圖形,這次是一些矩形:
現(xiàn)在,一些弧線會(huì)出現(xiàn),每一個(gè)弧線都有隨機(jī)的位置,大小,邊緣的粗細(xì)和弧長:
現(xiàn)在,帶有三個(gè)參數(shù)的?polylines(折線)?將會(huì)出現(xiàn)在屏幕上,同樣以隨機(jī)的方式:
填充的多邊形 (這里是三角形) 會(huì)出現(xiàn).
最后出現(xiàn)的圖形:圓
在結(jié)尾處,文字?“Testing Text Rendering”?將會(huì)以不同的字體,大小,顏色和位置出現(xiàn)在屏幕上。
最后 (這也順便表達(dá)了OpenCV的宗旨):
離散傅立葉變換
目標(biāo)
本文檔嘗試解答如下問題:
- 什么是傅立葉變換及其應(yīng)用?
- 如何使用OpenCV提供的傅立葉變換?
- 相關(guān)函數(shù)的使用,如:?copyMakeBorder(),?merge(),?dft(),?getOptimalDFTSize(),?log()?和?normalize()?.
源碼
你可以?從此處下載源碼?或者通過OpenCV源碼庫文件samples/cpp/tutorial_code/core/discrete_fourier_transform/discrete_fourier_transform.cpp?查看.
以下為函數(shù)?dft()?使用范例:
| 123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | #include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> int main(int argc, char ** argv) {const char* filename = argc >=2 ? argv[1] : "lena.jpg";Mat I = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);if( I.empty())return -1;Mat padded; //expand input image to optimal sizeint m = getOptimalDFTSize( I.rows );int n = getOptimalDFTSize( I.cols ); // on the border add zero valuescopyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};Mat complexI;merge(planes, 2, complexI); // Add to the expanded another plane with zerosdft(complexI, complexI); // this way the result may fit in the source matrix// compute the magnitude and switch to logarithmic scale// => log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I))magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude Mat magI = planes[0];magI += Scalar::all(1); // switch to logarithmic scalelog(magI, magI);// crop the spectrum, if it has an odd number of rows or columnsmagI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2));// rearrange the quadrants of Fourier image so that the origin is at the image center int cx = magI.cols/2;int cy = magI.rows/2;Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - Create a ROI per quadrant Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-RightMat q2(magI, Rect(0, cy, cx, cy)); // Bottom-LeftMat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-RightMat tmp; // swap quadrants (Top-Left with Bottom-Right)q0.copyTo(tmp);q3.copyTo(q0);tmp.copyTo(q3);q1.copyTo(tmp); // swap quadrant (Top-Right with Bottom-Left)q2.copyTo(q1);tmp.copyTo(q2);normalize(magI, magI, 0, 1, CV_MINMAX); // Transform the matrix with float values into a // viewable image form (float between values 0 and 1).imshow("Input Image" , I ); // Show the resultimshow("spectrum magnitude", magI); waitKey();return 0; } |
原理
對(duì)一張圖像使用傅立葉變換就是將它分解成正弦和余弦兩部分。也就是將圖像從空間域(spatial domain)轉(zhuǎn)換到頻域(frequency domain)。 這一轉(zhuǎn)換的理論基礎(chǔ)來自于以下事實(shí):任一函數(shù)都可以表示成無數(shù)個(gè)正弦和余弦函數(shù)的和的形式。傅立葉變換就是一個(gè)用來將函數(shù)分解的工具。 2維圖像的傅立葉變換可以用以下數(shù)學(xué)公式表達(dá):
式中 f 是空間域(spatial domain)值, F 則是頻域(frequency domain)值。 轉(zhuǎn)換之后的頻域值是復(fù)數(shù), 因此,顯示傅立葉變換之后的結(jié)果需要使用實(shí)數(shù)圖像(real image) 加虛數(shù)圖像(complex image), 或者幅度圖像(magitude image)加相位圖像(phase image)。 在實(shí)際的圖像處理過程中,僅僅使用了幅度圖像,因?yàn)榉葓D像包含了原圖像的幾乎所有我們需要的幾何信息。 然而,如果你想通過修改幅度圖像或者相位圖像的方法來間接修改原空間圖像,你需要使用逆傅立葉變換得到修改后的空間圖像,這樣你就必須同時(shí)保留幅度圖像和相位圖像了。
在此示例中,我將展示如何計(jì)算以及顯示傅立葉變換后的幅度圖像。由于數(shù)字圖像的離散性,像素值的取值范圍也是有限的。比如在一張灰度圖像中,像素灰度值一般在0到255之間。 因此,我們這里討論的也僅僅是離散傅立葉變換(DFT)。 如果你需要得到圖像中的幾何結(jié)構(gòu)信息,那你就要用到它了。請(qǐng)參考以下步驟(假設(shè)輸入圖像為單通道的灰度圖像?I):
將圖像延擴(kuò)到最佳尺寸. 離散傅立葉變換的運(yùn)行速度與圖片的尺寸息息相關(guān)。當(dāng)圖像的尺寸是2, 3,5的整數(shù)倍時(shí),計(jì)算速度最快。 因此,為了達(dá)到快速計(jì)算的目的,經(jīng)常通過添湊新的邊緣像素的方法獲取最佳圖像尺寸。函數(shù)?getOptimalDFTSize()返回最佳尺寸,而函數(shù)?copyMakeBorder()?填充邊緣像素:
Mat padded; //將輸入圖像延擴(kuò)到最佳的尺寸 int m = getOptimalDFTSize( I.rows ); int n = getOptimalDFTSize( I.cols ); // 在邊緣添加0 copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));添加的像素初始化為0.
為傅立葉變換的結(jié)果(實(shí)部和虛部)分配存儲(chǔ)空間. 傅立葉變換的結(jié)果是復(fù)數(shù),這就是說對(duì)于每個(gè)原圖像值,結(jié)果是兩個(gè)圖像值。 此外,頻域值范圍遠(yuǎn)遠(yuǎn)超過空間值范圍, 因此至少要將頻域儲(chǔ)存在?float?格式中。 結(jié)果我們將輸入圖像轉(zhuǎn)換成浮點(diǎn)類型,并多加一個(gè)額外通道來儲(chǔ)存復(fù)數(shù)部分:
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)}; Mat complexI; merge(planes, 2, complexI); // 為延擴(kuò)后的圖像增添一個(gè)初始化為0的通道進(jìn)行離散傅立葉變換. 支持圖像原地計(jì)算 (輸入輸出為同一圖像):
dft(complexI, complexI); // 變換結(jié)果很好的保存在原始矩陣中將復(fù)數(shù)轉(zhuǎn)換為幅度.復(fù)數(shù)包含實(shí)數(shù)部分(Re)和復(fù)數(shù)部分 (imaginary -?Im)。 離散傅立葉變換的結(jié)果是復(fù)數(shù),對(duì)應(yīng)的幅度可以表示為:
轉(zhuǎn)化為OpenCV代碼:
split(complexI, planes); // planes[0] = Re(DFT(I), planes[1] = Im(DFT(I)) magnitude(planes[0], planes[1], planes[0]);// planes[0] = magnitude Mat magI = planes[0];對(duì)數(shù)尺度(logarithmic scale)縮放. 傅立葉變換的幅度值范圍大到不適合在屏幕上顯示。高值在屏幕上顯示為白點(diǎn),而低值為黑點(diǎn),高低值的變化無法有效分辨。為了在屏幕上凸顯出高低變化的連續(xù)性,我們可以用對(duì)數(shù)尺度來替換線性尺度:
轉(zhuǎn)化為OpenCV代碼:
magI += Scalar::all(1); // 轉(zhuǎn)換到對(duì)數(shù)尺度 log(magI, magI);剪切和重分布幅度圖象限. 還記得我們?cè)诘谝徊綍r(shí)延擴(kuò)了圖像嗎? 那現(xiàn)在是時(shí)候?qū)⑿绿砑拥南袼靥蕹恕榱朔奖泔@示,我們也可以重新分布幅度圖象限位置(注:將第五步得到的幅度圖從中間劃開得到四張1/4子圖像,將每張子圖像看成幅度圖的一個(gè)象限,重新分布即將四個(gè)角點(diǎn)重疊到圖片中心)。 這樣的話原點(diǎn)(0,0)就位移到圖像中心。
magI = magI(Rect(0, 0, magI.cols & -2, magI.rows & -2)); int cx = magI.cols/2; int cy = magI.rows/2;Mat q0(magI, Rect(0, 0, cx, cy)); // Top-Left - 為每一個(gè)象限創(chuàng)建ROI Mat q1(magI, Rect(cx, 0, cx, cy)); // Top-Right Mat q2(magI, Rect(0, cy, cx, cy)); // Bottom-Left Mat q3(magI, Rect(cx, cy, cx, cy)); // Bottom-RightMat tmp; // 交換象限 (Top-Left with Bottom-Right) q0.copyTo(tmp); q3.copyTo(q0); tmp.copyTo(q3);q1.copyTo(tmp); // 交換象限 (Top-Right with Bottom-Left) q2.copyTo(q1); tmp.copyTo(q2);歸一化. 這一步的目的仍然是為了顯示。 現(xiàn)在我們有了重分布后的幅度圖,但是幅度值仍然超過可顯示范圍[0,1] 。我們使用normalize()?函數(shù)將幅度歸一化到可顯示范圍。
結(jié)果
離散傅立葉變換的一個(gè)應(yīng)用是決定圖片中物體的幾何方向.比如,在文字識(shí)別中首先要搞清楚文字是不是水平排列的? 看一些文字,你就會(huì)注意到文本行一般是水平的而字母則有些垂直分布。文本段的這兩個(gè)主要方向也是可以從傅立葉變換之后的圖像看出來。我們使用這個(gè)?水平文本圖像?以及?旋轉(zhuǎn)文本圖像?來展示離散傅立葉變換的結(jié)果 。
水平文本圖像:
旋轉(zhuǎn)文本圖像:
觀察這兩張幅度圖你會(huì)發(fā)現(xiàn)頻域的主要內(nèi)容(幅度圖中的亮點(diǎn))是和空間圖像中物體的幾何方向相關(guān)的。 通過這點(diǎn)我們可以計(jì)算旋轉(zhuǎn)角度并修正偏差。
輸入輸出XML和YAML文件
目的
你將得到以下幾個(gè)問題的答案:
- 如何將文本寫入YAML或XML文件,及如何從從OpenCV中讀取YAML或XML文件中的文本
- 如何利用YAML或XML文件存取OpenCV數(shù)據(jù)結(jié)構(gòu)
- 如何利用YAML或XML文件存取自定義數(shù)據(jù)結(jié)構(gòu)?
- OpenCV中相關(guān)數(shù)據(jù)結(jié)構(gòu)的使用方法,如 :xmlymlpers:FileStorage <filestorage>,?FileNode?或?FileNodeIterator.
代碼
你可以?點(diǎn)擊此處下載?或直接從OpenCV代碼庫中找到源文件。samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp?。
以下用簡(jiǎn)單的示例代碼演示如何逐一實(shí)現(xiàn)所有目的.
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | #include <opencv2/core/core.hpp> #include <iostream> #include <string>using namespace cv; using namespace std;class MyData { public:MyData() : A(0), X(0), id(){}explicit MyData(int) : A(97), X(CV_PI), id("mydata1234") // explicit to avoid implicit conversion{}void write(FileStorage& fs) const //Write serialization for this class{fs << "{" << "A" << A << "X" << X << "id" << id << "}";}void read(const FileNode& node) //Read serialization for this class{A = (int)node["A"];X = (double)node["X"];id = (string)node["id"];} public: // Data Membersint A;double X;string id; };//These write and read functions must be defined for the serialization in FileStorage to work void write(FileStorage& fs, const std::string&, const MyData& x) {x.write(fs); } void read(const FileNode& node, MyData& x, const MyData& default_value = MyData()){if(node.empty())x = default_value;elsex.read(node); }// This function will print our custom class to the console ostream& operator<<(ostream& out, const MyData& m) { out << "{ id = " << m.id << ", ";out << "X = " << m.X << ", ";out << "A = " << m.A << "}";return out; }int main(int ac, char** av) {if (ac != 2){help(av);return 1;}string filename = av[1];{ //writeMat R = Mat_<uchar>::eye(3, 3),T = Mat_<double>::zeros(3, 1);MyData m(1);FileStorage fs(filename, FileStorage::WRITE);fs << "iterationNr" << 100;fs << "strings" << "["; // text - string sequencefs << "image1.jpg" << "Awesomeness" << "baboon.jpg";fs << "]"; // close sequencefs << "Mapping"; // text - mappingfs << "{" << "One" << 1;fs << "Two" << 2 << "}"; fs << "R" << R; // cv::Matfs << "T" << T;fs << "MyData" << m; // your own data structuresfs.release(); // explicit closecout << "Write Done." << endl;}{//readcout << endl << "Reading: " << endl;FileStorage fs; fs.open(filename, FileStorage::READ);int itNr; //fs["iterationNr"] >> itNr;itNr = (int) fs["iterationNr"];cout << itNr;if (!fs.isOpened()){cerr << "Failed to open " << filename << endl;help(av);return 1;}FileNode n = fs["strings"]; // Read string sequence - Get nodeif (n.type() != FileNode::SEQ){cerr << "strings is not a sequence! FAIL" << endl;return 1;}FileNodeIterator it = n.begin(), it_end = n.end(); // Go through the nodefor (; it != it_end; ++it)cout << (string)*it << endl;n = fs["Mapping"]; // Read mappings from a sequencecout << "Two " << (int)(n["Two"]) << "; "; cout << "One " << (int)(n["One"]) << endl << endl; MyData m;Mat R, T;fs["R"] >> R; // Read cv::Matfs["T"] >> T;fs["MyData"] >> m; // Read your own structure_cout << endl << "R = " << R << endl;cout << "T = " << T << endl << endl;cout << "MyData = " << endl << m << endl << endl;//Show default behavior for non existing nodescout << "Attempt to read NonExisting (should initialize the data structure with its default)."; fs["NonExisting"] >> m;cout << endl << "NonExisting = " << endl << m << endl;}cout << endl << "Tip: Open up " << filename << " with a text editor to see the serialized data." << endl;return 0; } |
代碼分析
這里我們僅討論XML和YAML文件輸入。你的輸出(和相應(yīng)的輸入)文件可能僅具有其中一個(gè)擴(kuò)展名以及對(duì)應(yīng)的文件結(jié)構(gòu)。XML和YAML的串行化分別采用兩種不同的數(shù)據(jù)結(jié)構(gòu):?mappings?(就像STL map) 和?element sequence?(比如 STL vector>。二者之間的區(qū)別在map中每個(gè)元素都有一個(gè)唯一的標(biāo)識(shí)名供用戶訪問;而在sequences中你必須遍歷所有的元素才能找到指定元素。
XML\YAML 文件的打開和關(guān)閉。?在你寫入內(nèi)容到此類文件中前,你必須先打開它,并在結(jié)束時(shí)關(guān)閉它。在OpenCV中標(biāo)識(shí)XML和YAML的數(shù)據(jù)結(jié)構(gòu)是?FileStorage?。要將此結(jié)構(gòu)和硬盤上的文件綁定時(shí),可使用其構(gòu)造函數(shù)或者?open()?函數(shù):
string filename = "I.xml"; FileStorage fs(filename, FileStorage::WRITE); \\... fs.open(filename, FileStorage::READ);無論以哪種方式綁定,函數(shù)中的第二個(gè)參數(shù)都以常量形式指定你要對(duì)文件進(jìn)行操作的類型,包括:WRITE, READ 或 APPEND。文件擴(kuò)展名決定了你將采用的輸出格式。如果你指定擴(kuò)展名如?.xml.gz?,輸出甚至可以是壓縮文件。
當(dāng)?FileStorage?對(duì)象被銷毀時(shí),文件將自動(dòng)關(guān)閉。當(dāng)然你也可以顯示調(diào)用?release?函數(shù):
fs.release(); // 顯示關(guān)閉輸入\輸出文本和數(shù)字。?數(shù)據(jù)結(jié)構(gòu)使用與STL相同的 << 輸出操作符。輸出任何類型的數(shù)據(jù)結(jié)構(gòu)時(shí),首先都必須指定其標(biāo)識(shí)符,這通過簡(jiǎn)單級(jí)聯(lián)輸出標(biāo)識(shí)符即可實(shí)現(xiàn)。基本類型數(shù)據(jù)輸出必須遵循此規(guī)則:
fs << "iterationNr" << 100;讀入則通過簡(jiǎn)單的尋址(通過 [] 操作符)操作和強(qiáng)制轉(zhuǎn)換或 >> 操作符實(shí)現(xiàn):
int itNr; fs["iterationNr"] >> itNr; itNr = (int) fs["iterationNr"];輸入\輸出OpenCV數(shù)據(jù)結(jié)構(gòu)。?其實(shí)和對(duì)基本類型的操作方法是相同的:
Mat R = Mat_<uchar >::eye (3, 3),T = Mat_<double>::zeros(3, 1);fs << "R" << R; // 寫 cv::Mat fs << "T" << T;fs["R"] >> R; // 讀 cv::Mat fs["T"] >> T;輸入\輸出 vectors(數(shù)組)和相應(yīng)的maps.?之前提到我們也可以輸出maps和序列(數(shù)組, vector)。同樣,首先輸出變量的標(biāo)識(shí)符,接下來必須指定輸出的是序列還是map。
對(duì)于序列,在第一個(gè)元素前輸出”[“字符,并在最后一個(gè)元素后輸出”]“字符:
fs << "strings" << "["; // 文本 - 字符串序列 fs << "image1.jpg" << "Awesomeness" << "baboon.jpg"; fs << "]"; // 序列結(jié)束對(duì)于maps使用相同的方法,但采用”{“和”}“作為分隔符。
fs << "Mapping"; // 文本 - mapping fs << "{" << "One" << 1; fs << "Two" << 2 << "}";對(duì)于數(shù)據(jù)讀取,可使用?FileNode?和?FileNodeIterator?數(shù)據(jù)結(jié)構(gòu)。?FileStorage?的[] 操作符將返回一個(gè)?FileNode?數(shù)據(jù)類型。如果這個(gè)節(jié)點(diǎn)是序列化的,我們可以使用?FileNodeIterator?來迭代遍歷所有元素。
FileNode n = fs["strings"]; // 讀取字符串序列 - 獲取節(jié)點(diǎn) if (n.type() != FileNode::SEQ) {cerr << "strings is not a sequence! FAIL" << endl;return 1; }FileNodeIterator it = n.begin(), it_end = n.end(); // 遍歷節(jié)點(diǎn) for (; it != it_end; ++it)cout << (string)*it << endl;對(duì)于maps類型,可以用 [] 操作符訪問指定的元素(或者 >> 操作符):
n = fs["Mapping"]; // 從序列中讀取map cout << "Two " << (int)(n["Two"]) << "; "; cout << "One " << (int)(n["One"]) << endl << endl;讀寫自定義數(shù)據(jù)類型。?假設(shè)你定義了如下數(shù)據(jù)類型:
class MyData { public:MyData() : A(0), X(0), id() {} public: // 數(shù)據(jù)成員int A;double X;string id; };添加內(nèi)部和外部的讀寫函數(shù),就可以使用OpenCV I/O XML/YAML接口對(duì)其進(jìn)行序列化(就像對(duì)OpenCV數(shù)據(jù)結(jié)構(gòu)進(jìn)行序列化一樣)。內(nèi)部函數(shù)定義如下:
void write(FileStorage& fs) const //對(duì)自定義類進(jìn)行寫序列化 {fs << "{" << "A" << A << "X" << X << "id" << id << "}"; }void read(const FileNode& node) //從序列讀取自定義類 {A = (int)node["A"];X = (double)node["X"];id = (string)node["id"]; }接下來在類的外部定義以下函數(shù):
void write(FileStorage& fs, const std::string&, const MyData& x) { x.write(fs); }void read(const FileNode& node, MyData& x, const MyData& default_value = MyData()) { if(node.empty())x = default_value; elsex.read(node); }這兒可以看到,如果讀取的節(jié)點(diǎn)不存在,我們返回默認(rèn)值。更復(fù)雜一些的解決方案是返回一個(gè)對(duì)象ID為負(fù)值的實(shí)例。
一旦添加了這四個(gè)函數(shù),就可以用 >> 操作符和 << 操作符分別進(jìn)行讀,寫操作:
MyData m(1); fs << "MyData" << m; // 寫自定義數(shù)據(jù)結(jié)構(gòu) fs["MyData"] >> m; // 讀自定義數(shù)據(jù)結(jié)構(gòu)或試著讀取不存在的值:
fs["NonExisting"] >> m; // 請(qǐng)注意不是 fs << "NonExisting" << m cout << endl << "NonExisting = " << endl << m << endl;結(jié)果
好的,大多情況下我們只輸出定義過的成員。在控制臺(tái)程序的屏幕上,你將看到:
Write Done.Reading: 100image1.jpg Awesomeness baboon.jpg Two 2; One 1R = [1, 0, 0;0, 1, 0;0, 0, 1] T = [0; 0; 0]MyData = { id = mydata1234, X = 3.14159, A = 97}Attempt to read NonExisting (should initialize the data structure with its default). NonExisting = { id = , X = 0, A = 0}Tip: Open up output.xml with a text editor to see the serialized data.然而, 在輸出的xml文件中看到的結(jié)果將更加有趣:
<?xml version="1.0"?> <opencv_storage> <iterationNr>100</iterationNr> <strings>image1.jpg Awesomeness baboon.jpg</strings> <Mapping><One>1</One><Two>2</Two></Mapping> <R type_id="opencv-matrix"><rows>3</rows><cols>3</cols><dt>u</dt><data>1 0 0 0 1 0 0 0 1</data></R> <T type_id="opencv-matrix"><rows>3</rows><cols>1</cols><dt>d</dt><data>0. 0. 0.</data></T> <MyData><A>97</A><X>3.1415926535897931e+000</X><id>mydata1234</id></MyData> </opencv_storage>或YAML文件:
%YAML:1.0 iterationNr: 100 strings:- "image1.jpg"- Awesomeness- "baboon.jpg" Mapping:One: 1Two: 2 R: !!opencv-matrixrows: 3cols: 3dt: udata: [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ] T: !!opencv-matrixrows: 3cols: 1dt: ddata: [ 0., 0., 0. ] MyData:A: 97X: 3.1415926535897931e+000id: mydata1234你也可以看到動(dòng)態(tài)實(shí)例:?YouTube here?.
與 OpenCV 1 同時(shí)使用
目的
對(duì)于OpenCV的開發(fā)團(tuán)隊(duì)來說,持續(xù)穩(wěn)定地提高代碼庫非常重要。我們一直在思考如何在使其易用的同時(shí)保持靈活性。新的C++接口即為此而來。盡管如此,向下兼容仍然十分重要。我們并不想打斷你基于早期OpenCV庫的開發(fā)。因此,我們添加了一些函數(shù)來處理這種情況。在以下內(nèi)容中你將學(xué)到:
- 相比第一個(gè)版本,第二版的OpenCV在用法上有何改變
- 如何在一幅圖像中加入高斯噪聲
- 什么事查找表及如何使用
概述
在用新版本之前,你首先需要學(xué)習(xí)一些新的圖像數(shù)據(jù)結(jié)構(gòu):?Mat - 基本圖像容器?,它取代了舊的?CvMat?和?IplImage?。轉(zhuǎn)換到新函數(shù)非常容易,你僅需記住幾條新的原則。
OpenCV 2 接受按需定制。所有函數(shù)不再裝入一個(gè)單一的庫中。我們會(huì)提供許多模塊,每個(gè)模塊都包含了與其功能相關(guān)的數(shù)據(jù)結(jié)構(gòu)和函數(shù)。這樣一來,如果你僅僅需要使用OpenCV的一部分功能,你就不需要把整個(gè)巨大的OpenCV庫都裝入你的程序中。使用時(shí),你僅需要包含用到的頭文件,比如:
#include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp>所有OpenCV用到的東西都被放入名字空間?cv?中以避免與其他庫的數(shù)據(jù)結(jié)構(gòu)和函數(shù)名稱的命名沖突。因此,在使用OpenCV庫中的任何定義和函數(shù)時(shí),你必須在名稱之前冠以?cv::?,或者在包含頭文件后,加上以下指令:
using namespace cv; // 新的C++接口API都在此名字空間中,需要導(dǎo)入。因?yàn)樗袔熘泻瘮?shù)都已在此名字空間中,所以無需加?cv?作為前綴。據(jù)此所有新的C++兼容函數(shù)都無此前綴,并且遵循駝峰命名準(zhǔn)則。也就是第一個(gè)字母為小寫(除非是單個(gè)單詞作為函數(shù)名,如 Canny)并且后續(xù)單詞首字母大寫(如?copyMakeBorder?).
接下來,請(qǐng)記住你需要將所有用到的模塊鏈接到你的程序中。如果你在Windows下開發(fā)且用到了?動(dòng)態(tài)鏈接庫(DLL)?,你還需要將OpenCV對(duì)應(yīng)動(dòng)態(tài)鏈接庫的路徑加入程序執(zhí)行路徑中。關(guān)于Windows下開發(fā)的更多信息請(qǐng)閱讀?How to build applications with OpenCV inside the Microsoft Visual Studio?;對(duì)于Linux用戶,可參考?Using OpenCV with Eclipse (plugin CDT)?中的實(shí)例及說明。
你可以使用?IplImage?或?CvMat?操作符來轉(zhuǎn)換?Mat?對(duì)象。在C接口中,你習(xí)慣于使用指針,但此處將不再需要。在C++接口中,我們大多數(shù)情況下都是用?Mat?對(duì)象。此對(duì)象可通過簡(jiǎn)單的賦值操作轉(zhuǎn)換為?IplImage?和?CvMat?。示例如下:
Mat I; IplImage pI = I; CvMat mI = I;現(xiàn)在,如果你想獲取指針,轉(zhuǎn)換就變得麻煩一點(diǎn)。編譯器將不能自動(dòng)識(shí)別你的意圖,所以你需要明確指出你的目的。可以通過調(diào)用IplImage?和?CvMat?操作符來獲取他們的指針。我們可以用 & 符號(hào)獲取其指針如下:
Mat I; IplImage* pI = &I.operator IplImage(); CvMat* mI = &I.operator CvMat();來自C接口最大的抱怨是它將所有內(nèi)存管理工作交給你來做。你需要知道何時(shí)可以安全釋放不再使用的對(duì)象,并且確定在程序結(jié)束之前釋放它,否則就會(huì)造成討厭的內(nèi)存泄露。為了繞開這一問題,OpenCV引進(jìn)了一種智能指針。它將自動(dòng)釋放不再使用的對(duì)象。使用時(shí),指針將被聲明為?Ptr?模板的特化:
Ptr<IplImage> piI = &I.operator IplImage();將C接口的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為?Mat?時(shí),可將其作為構(gòu)造函數(shù)的參數(shù)傳入,例如:
Mat K(piL), L; L = Mat(pI);實(shí)例學(xué)習(xí)
現(xiàn)在,你已經(jīng)學(xué)習(xí)了最基本的知識(shí)。?這里?你將會(huì)看到一個(gè)混合使用C接口和C++接口的例子。你也可以在可以再OpenCV的代碼庫中的sample目錄中找到此文件samples/cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp?。為了進(jìn)一步幫助你認(rèn)清其中區(qū)別,程序支持兩種模式:C和C++混合,以及純C++。如果你宏定義了?DEMO_MIXED_API_USE?,程序?qū)吹谝环N模式編譯。程序的功能是劃分顏色平面,對(duì)其進(jìn)行改動(dòng)并最終將其重新合并。
| 123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include <stdio.h> #include <iostream>#include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp>using namespace cv; // The new C++ interface API is inside this namespace. Import it. using namespace std; #define DEMO_MIXED_API_USE int main( int argc, char** argv ) {const char* imagename = argc > 1 ? argv[1] : "lena.jpg";#ifdef DEMO_MIXED_API_USEPtr<IplImage> IplI = cvLoadImage(imagename); // Ptr<T> is safe ref-counting pointer classif(IplI.empty()){cerr << "Can not load image " << imagename << endl;return -1;}Mat I(IplI); // Convert to the new style container. Only header created. Image not copied. #elseMat I = imread(imagename); // the newer cvLoadImage alternative, MATLAB-style functionif( I.empty() ) // same as if( !I.data ){cerr << "Can not load image " << imagename << endl;return -1;} #endif |
在此,你可一看到新的結(jié)構(gòu)再無指針問題,哪怕使用舊的函數(shù),并在最后結(jié)束時(shí)將結(jié)果轉(zhuǎn)換為?Mat?對(duì)象。
| 1 2 3 4 5 6 | // convert image to YUV color space. The output image will be created automatically. Mat I_YUV;cvtColor(I, I_YUV, CV_BGR2YCrCb); vector<Mat> planes; // Use the STL's vector structure to store multiple Mat objects split(I_YUV, planes); // split the image into separate color planes (Y U V) |
因?yàn)槲覀兇蛩愀銇y圖像的亮度通道,所以首先將圖像由默認(rèn)的RGB顏色空間轉(zhuǎn)為YUV顏色空間,然后將其劃分為獨(dú)立顏色平面(Y,U,V)。第一個(gè)例子中,我們對(duì)每一個(gè)平面用OpenCV中三個(gè)主要圖像掃描算法(C []操作符,迭代,單獨(dú)元素訪問)中的一個(gè)進(jìn)行處理。在第二個(gè)例子中,我們給圖像添加一些高斯噪聲,然后依據(jù)一些準(zhǔn)則融合所有通道。
運(yùn)用掃描算法的代碼如下:
| 123456789 10 11 12 13 14 15 16 17 18 19 20 21 | // Method 1. process Y plane using an iteratorMatIterator_<uchar> it = planes[0].begin<uchar>(), it_end = planes[0].end<uchar>();for(; it != it_end; ++it){double v = *it * 1.7 + rand()%21 - 10;*it = saturate_cast<uchar>(v*v/255);}for( int y = 0; y < I_YUV.rows; y++ ){// Method 2. process the first chroma plane using pre-stored row pointer.uchar* Uptr = planes[1].ptr<uchar>(y);for( int x = 0; x < I_YUV.cols; x++ ){Uptr[x] = saturate_cast<uchar>((Uptr[x]-128)/2 + 128);// Method 3. process the second chroma plane using individual element accessuchar& Vxy = planes[2].at<uchar>(y, x);Vxy = saturate_cast<uchar>((Vxy-128)/2 + 128);}} |
此處可看到,我們可以以三種方式遍歷圖像的所有像素:迭代器,C指針和單獨(dú)元素訪問方式你可在?OpenCV如何掃描圖像、利用查找表和計(jì)時(shí)?中獲得更深入的了解。從舊的函數(shù)名轉(zhuǎn)換新版本非常容易,僅需要?jiǎng)h除 cv 前綴,并且使用?Mat?數(shù)據(jù)結(jié)構(gòu)。下面的例子中使用了加權(quán)加法:
| 123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | Mat noisyI(I.size(), CV_8U); // Create a matrix of the specified size and type// Fills the matrix with normally distributed random values (around number with deviation off).// There is also randu() for uniformly distributed random number generationrandn(noisyI, Scalar::all(128), Scalar::all(20)); // blur the noisyI a bit, kernel size is 3x3 and both sigma's are set to 0.5GaussianBlur(noisyI, noisyI, Size(3, 3), 0.5, 0.5); const double brightness_gain = 0;const double contrast_gain = 1.7;#ifdef DEMO_MIXED_API_USE// To pass the new matrices to the functions that only work with IplImage or CvMat do:// step 1) Convert the headers (tip: data will not be copied).// step 2) call the function (tip: to pass a pointer do not forget unary "&" to form pointers)IplImage cv_planes_0 = planes[0], cv_noise = noisyI; cvAddWeighted(&cv_planes_0, contrast_gain, &cv_noise, 1, -128 + brightness_gain, &cv_planes_0); #elseaddWeighted(planes[0], contrast_gain, noisyI, 1, -128 + brightness_gain, planes[0]); #endifconst double color_scale = 0.5;// Mat::convertTo() replaces cvConvertScale. // One must explicitly specify the output matrix type (we keep it intact - planes[1].type())planes[1].convertTo(planes[1], planes[1].type(), color_scale, 128*(1-color_scale));// alternative form of cv::convertScale if we know the datatype at compile time ("uchar" here).// This expression will not create any temporary arrays ( so should be almost as fast as above)planes[2] = Mat_<uchar>(planes[2]*color_scale + 128*(1-color_scale));// Mat::mul replaces cvMul(). Again, no temporary arrays are created in case of simple expressions.planes[0] = planes[0].mul(planes[0], 1./255); |
正如你所見,變量?planes?也是?Mat?類型的。無論如何,將?Mat?轉(zhuǎn)換為?IplImage?都可通過簡(jiǎn)單的賦值操作符自動(dòng)實(shí)現(xiàn)。
| 123456789 10 11 12 13 | merge(planes, I_YUV); // now merge the results backcvtColor(I_YUV, I, CV_YCrCb2BGR); // and produce the output RGB imagenamedWindow("image with grain", CV_WINDOW_AUTOSIZE); // use this to create images#ifdef DEMO_MIXED_API_USE// this is to demonstrate that I and IplI really share the data - the result of the above// processing is stored in I and thus in IplI too.cvShowImage("image with grain", IplI); #elseimshow("image with grain", I); // the new MATLAB style function show |
新的?imshow?highgui函數(shù)可接受?Mat?和?IplImage?數(shù)據(jù)結(jié)構(gòu)。 編譯并運(yùn)行例程,如果輸入以下第一幅圖像,程序?qū)⑤敵鲆韵碌诙蛘叩谌鶊D像。
你可以在點(diǎn)擊此處看到動(dòng)態(tài)示例:?YouTube here?,并可以?點(diǎn)擊此處?下載源文件,或者在OpenCV源代碼庫中找到源文件:samples/cpp/tutorial_code/core/interoperability_with_OpenCV_1/interoperability_with_OpenCV_1.cpp?。
from: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/table_of_content_core/table_of_content_core.html#table-of-content-core
總結(jié)
以上是生活随笔為你收集整理的OpenCV之core 模块. 核心功能(2)基本绘图 随机数发生器绘制文字 离散傅立叶变换 输入输出XML和YAML文件 与 OpenCV 1 同时使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV之core 模块. 核心功能
- 下一篇: 我给媳妇解释设计模式:第一部分