OpenCV实现基于傅里叶变换(FFT)的旋转文本校正(文字方向检测)
OpenCV實(shí)現(xiàn)基于傅里葉變換的旋轉(zhuǎn)文本校正
from: http://johnhany.net/2013/11/dft-based-text-rotation-correction/
代碼
? ? ? ??先給出代碼,再詳細(xì)解釋一下過(guò)程:
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 | #include <opencv2/core/core.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/highgui/highgui.hpp>#include <iostream>using namespacecv;usingnamespacestd;#define GRAY_THRESH 150#define HOUGH_VOTE 100//#define DEGREE 27int main(intargc,char**argv){//Read a single-channel imageconstchar*filename="imageText.jpg";Mat srcImg=imread(filename,CV_LOAD_IMAGE_GRAYSCALE);if(srcImg.empty())return-1;imshow("source",srcImg);Pointcenter(srcImg.cols/2,srcImg.rows/2);#ifdef DEGREE//Rotate source imageMatrotMatS=getRotationMatrix2D(center,DEGREE,1.0);warpAffine(srcImg,srcImg,rotMatS,srcImg.size(),1,0,Scalar(255,255,255));imshow("RotatedSrc",srcImg);//imwrite("imageText_R.jpg",srcImg);#endif//Expand image to an optimal size, for faster processing speed//Set widths of borders in four directions//If borderType==BORDER_CONSTANT, fill the borders with (0,0,0)Mat padded;intopWidth=getOptimalDFTSize(srcImg.rows);intopHeight=getOptimalDFTSize(srcImg.cols);copyMakeBorder(srcImg,padded,0,opWidth-srcImg.rows,0,opHeight-srcImg.cols,BORDER_CONSTANT,Scalar::all(0));Matplanes[]={Mat_<float>(padded),Mat::zeros(padded.size(),CV_32F)};Mat comImg;//Merge into a double-channel imagemerge(planes,2,comImg);//Use the same image as input and output,//so that the results can fit in Mat welldft(comImg,comImg);//Compute the magnitude//planes[0]=Re(DFT(I)), planes[1]=Im(DFT(I))//magnitude=sqrt(Re^2+Im^2)split(comImg,planes);magnitude(planes[0],planes[1],planes[0]);//Switch to logarithmic scale, for better visual results//M2=log(1+M1)Mat magMat=planes[0];magMat+=Scalar::all(1);log(magMat,magMat);//Crop the spectrum//Width and height of magMat should be even, so that they can be divided by 2//-2 is 11111110 in binary system, operator & make sure width and height are always evenmagMat=magMat(Rect(0,0,magMat.cols&-2,magMat.rows&-2));//Rearrange the quadrants of Fourier image,//so that the origin is at the center of image,//and move the high frequency to the cornersintcx=magMat.cols/2;intcy=magMat.rows/2;Matq0(magMat,Rect(0,0,cx,cy));Mat q1(magMat,Rect(0,cy,cx,cy));Matq2(magMat,Rect(cx,cy,cx,cy));Mat q3(magMat,Rect(cx,0,cx,cy));Mat tmp;q0.copyTo(tmp);q2.copyTo(q0);tmp.copyTo(q2);q1.copyTo(tmp);q3.copyTo(q1);tmp.copyTo(q3);//Normalize the magnitude to [0,1], then to[0,255]normalize(magMat,magMat,0,1,CV_MINMAX);MatmagImg(magMat.size(),CV_8UC1);magMat.convertTo(magImg,CV_8UC1,255,0);imshow("magnitude",magImg);//imwrite("imageText_mag.jpg",magImg);//Turn into binary imagethreshold(magImg,magImg,GRAY_THRESH,255,CV_THRESH_BINARY);imshow("mag_binary",magImg);//imwrite("imageText_bin.jpg",magImg);//Find lines with Hough Transformationvector<Vec2f>lines;floatpi180=(float)CV_PI/180;Mat linImg(magImg.size(),CV_8UC3);HoughLines(magImg,lines,1,pi180,HOUGH_VOTE,0,0);intnumLines=lines.size();for(intl=0;l<numLines;l++){floatrho=lines[l][0],theta=lines[l][1];Point pt1,pt2;doublea=cos(theta),b=sin(theta);doublex0=a*rho,y0=b*rho;pt1.x=cvRound(x0+1000*(-b));pt1.y=cvRound(y0+1000*(a));pt2.x=cvRound(x0-1000*(-b));pt2.y=cvRound(y0-1000*(a));line(linImg,pt1,pt2,Scalar(255,0,0),3,8,0);}imshow("lines",linImg);//imwrite("imageText_line.jpg",linImg);if(lines.size()==3){cout<<"found three angels:"<<endl;cout<<lines[0][1]*180/CV_PI<<endl<<lines[1][1]*180/CV_PI<<endl<<lines[2][1]*180/CV_PI<<endl<<endl;}//Find the proper angel from the three found angelsfloatangel=0;floatpiThresh=(float)CV_PI/90;floatpi2=CV_PI/2;for(intl=0;l<numLines;l++){floattheta=lines[l][1];if(abs(theta)<piThresh||abs(theta-pi2)<piThresh)continue;else{angel=theta;break;}}//Calculate the rotation angel//The image has to be square,//so that the rotation angel can be calculate rightangel=angel<pi2?angel:angel-CV_PI;if(angel!=pi2){floatangelT=srcImg.rows*tan(angel)/srcImg.cols;angel=atan(angelT);}floatangelD=angel*180/(float)CV_PI;cout<<"the rotation angel to be applied:"<<endl<<angelD<<endl<<endl;//Rotate the image to recoverMatrotMat=getRotationMatrix2D(center,angelD,1.0);Mat dstImg=Mat::ones(srcImg.size(),CV_8UC3);warpAffine(srcImg,dstImg,rotMat,srcImg.size(),1,0,Scalar(255,255,255));imshow("result",dstImg);//imwrite("imageText_D.jpg",dstImg);waitKey(0);return0;} |
過(guò)程
讀取圖片
| 123 | MatsrcImg=imread(filename,CV_LOAD_IMAGE_GRAYSCALE);if(srcImg.empty())return-1; |
? ? ? ? srcImg.empty()用來(lái)判斷是否成功讀進(jìn)圖像,如果srcImg中沒(méi)有數(shù)據(jù),在后面的步驟會(huì)產(chǎn)生內(nèi)存錯(cuò)誤。
? ? ? ? 由于處理的是文本,彩色信息不會(huì)提供額外幫助,所以要用CV_LOAD_IMAGE_GRAYSCALE表明以灰度形式讀進(jìn)圖像。
? ? ? ? 假定讀取的圖像如下:
旋轉(zhuǎn)原圖像(可選)
Point center(srcImg.cols/2, srcImg.rows/2);#ifdef DEGREE//Rotate source imageMat rotMatS = getRotationMatrix2D(center, DEGREE, 1.0);warpAffine(srcImg, srcImg, rotMatS, srcImg.size(), 1, 0, Scalar(255,255,255));imshow("RotatedSrc", srcImg);//imwrite("H:\\imageText_02_R.jpg",srcImg);#endif| 123456789 | Pointcenter(srcImg.cols/2,srcImg.rows/2);#ifdef DEGREE//Rotate source imageMatrotMatS=getRotationMatrix2D(center,DEGREE,1.0);warpAffine(srcImg,srcImg,rotMatS,srcImg.size(),1,0,Scalar(255,255,255));imshow("RotatedSrc",srcImg);//imwrite("H:\\imageText_02_R.jpg",srcImg);#endif |
? ? ? ? 如果手頭沒(méi)有這樣的傾斜圖像,可以選擇一張正放的文本圖像,再把第12行#define DEGREE那行前的注釋符號(hào)去掉。然后這部分代碼就會(huì)把所給的圖像旋轉(zhuǎn)你規(guī)定的角度,再交給后面處理。
圖像延擴(kuò)
Mat padded;int opWidth = getOptimalDFTSize(srcImg.rows);int opHeight = getOptimalDFTSize(srcImg.cols);copyMakeBorder(srcImg, padded, 0, opWidth-srcImg.rows, 0, opHeight-srcImg.cols, BORDER_CONSTANT, Scalar::all(0));| 1234 | Matpadded;int opWidth=getOptimalDFTSize(srcImg.rows);intopHeight=getOptimalDFTSize(srcImg.cols);copyMakeBorder(srcImg,padded,0,opWidth-srcImg.rows,0,opHeight-srcImg.cols,BORDER_CONSTANT,Scalar::all(0)); |
? ? ? ? OpenCV中的DFT采用的是快速算法,這種算法要求圖像的尺寸是2、3和5的倍數(shù)時(shí)處理速度最快。所以需要用getOptimalDFTSize()找到最適合的尺寸,然后用copyMakeBorder()填充多余的部分。這里是讓原圖像和擴(kuò)大的圖像左上角對(duì)齊。填充的顏色如果是純色對(duì)變換結(jié)果的影響不會(huì)很大,后面尋找傾斜線的過(guò)程又會(huì)完全忽略這一點(diǎn)影響。
DFT
Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)};Mat comImg;merge(planes,2,comImg);dft(comImg, comImg);| 123456 | Matplanes[]={Mat_<float>(padded),Mat::zeros(padded.size(),CV_32F)};Mat comImg;merge(planes,2,comImg);dft(comImg,comImg); |
? ? ? ? DFT要分別計(jì)算實(shí)部和虛部,把要處理的圖像作為輸入的實(shí)部、一個(gè)全零的圖像作為輸入的虛部。dft()輸入和輸出應(yīng)該分別為單張圖像,所以要先用merge()把實(shí)虛部圖像合并,分別處于圖像comImg的兩個(gè)通道內(nèi)。計(jì)算得到的實(shí)虛部仍然保存在comImg的兩個(gè)通道內(nèi)。
獲得DFT圖像
split(comImg, planes);magnitude(planes[0], planes[1], planes[0]);Mat magMat = planes[0];magMat += Scalar::all(1);log(magMat, magMat);| 123456 | split(comImg,planes);magnitude(planes[0],planes[1],planes[0]);Mat magMat=planes[0];magMat+=Scalar::all(1);log(magMat,magMat); |
? ? ? ? 一般都會(huì)用幅度圖像來(lái)表示圖像傅里葉的變換結(jié)果(傅里葉譜)。
? ? ? ? 幅度的計(jì)算公式:magnitude = sqrt(Re(DFT)^2 + Im(DFT)^2)。
? ? ? ? 由于幅度的變化范圍很大,而一般圖像亮度范圍只有[0,255],容易造成一大片漆黑,只有幾個(gè)點(diǎn)很亮。所以要用log函數(shù)把數(shù)值的范圍縮小。
?
magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2));int cx = magMat.cols/2;int cy = magMat.rows/2;Mat q0(magMat, Rect(0, 0, cx, cy));Mat q1(magMat, Rect(0, cy, cx, cy));Mat q2(magMat, Rect(cx, cy, cx, cy));Mat q3(magMat, Rect(cx, 0, cx, cy));Mat tmp;q0.copyTo(tmp);q2.copyTo(q0);tmp.copyTo(q2);q1.copyTo(tmp);q3.copyTo(q1);tmp.copyTo(q3);normalize(magMat, magMat, 0, 1, CV_MINMAX);Mat magImg(magMat.size(), CV_8UC1);magMat.convertTo(magImg,CV_8UC1,255,0);| 12345678910111213141516171819202122 | magMat=magMat(Rect(0,0,magMat.cols&-2,magMat.rows&-2));intcx=magMat.cols/2;int cy=magMat.rows/2;Mat q0(magMat,Rect(0,0,cx,cy));Matq1(magMat,Rect(0,cy,cx,cy));Mat q2(magMat,Rect(cx,cy,cx,cy));Matq3(magMat,Rect(cx,0,cx,cy));Mattmp;q0.copyTo(tmp);q2.copyTo(q0);tmp.copyTo(q2);q1.copyTo(tmp);q3.copyTo(q1);tmp.copyTo(q3);normalize(magMat,magMat,0,1,CV_MINMAX);MatmagImg(magMat.size(),CV_8UC1);magMat.convertTo(magImg,CV_8UC1,255,0); |
? ? ? ? dft()直接獲得的結(jié)果中,低頻部分位于四角,高頻部分位于中間。習(xí)慣上會(huì)把圖像做四等份,互相對(duì)調(diào),使低頻部分位于圖像中心,也就是讓頻域原點(diǎn)位于中心。
?
? ? ? ? 雖然用log()縮小了數(shù)據(jù)范圍,但仍然不能保證數(shù)值都落在[0,255]之內(nèi),所以要先用normalize()規(guī)范化到[0,1]內(nèi),再用convertTo()把小數(shù)映射到[0,255]內(nèi)的整數(shù)。結(jié)果保存在一幅單通道圖像內(nèi):
?
Hough直線檢測(cè)
? ? ? ? 從傅里葉譜可以明顯地看到一條過(guò)中心點(diǎn)的傾斜直線。要想求出這個(gè)傾斜角,首先要在圖像上找出這條直線。
? ? ? ? 一個(gè)很方便的方法是采用霍夫(Hough)變換檢測(cè)直線。
| 1 | threshold(magImg,magImg,GRAY_THRESH,255,CV_THRESH_BINARY); |
? ? ? ? Hough變換要求輸入圖像是二值的,所以要用threshold()把圖像二值化。
? ? ? ? 二值化的一種結(jié)果:
| 1234567891011121314151617 | vector<Vec2f>lines;float pi180=(float)CV_PI/180;MatlinImg(magImg.size(),CV_8UC3);HoughLines(magImg,lines,1,pi180,HOUGH_VOTE,0,0);intnumLines=lines.size();for(intl=0;l<numLines;l++){floatrho=lines[l][0],theta=lines[l][1];Pointpt1,pt2;doublea=cos(theta),b=sin(theta);doublex0=a*rho,y0=b*rho;pt1.x=cvRound(x0+1000*(-b));pt1.y=cvRound(y0+1000*(a));pt2.x=cvRound(x0-1000*(-b));pt2.y=cvRound(y0-1000*(a));line(linImg,pt1,pt2,Scalar(255,0,0),3,8,0);} |
? ? ? ? 這一部分用HoughLines()檢測(cè)圖像中可能存在的直線,并把直線參數(shù)保存在向量組lines中,然后繪制出找到的直線。
? ? ? ? 兩個(gè)參數(shù)GRAY_THRESH和HOUGH_VOTE需要手動(dòng)指定,不同的圖像需要設(shè)置不同的參數(shù),同一段文本旋轉(zhuǎn)不同的角度也需要不同的參數(shù)。GRAY_THRESH越大,二值化的閾值就越高;HOUGH_VOTE越大,霍夫檢測(cè)的投票數(shù)就越高(需要更多的共線點(diǎn)來(lái)確定一條直線)。說(shuō)白了,如果發(fā)現(xiàn)二值化圖像中直線附近有很多散點(diǎn),就要適當(dāng)提高GRAY_THRESH;如果發(fā)現(xiàn)從二值圖像的一條直線上檢測(cè)到了幾條角度相差很小的直線,就需要適當(dāng)提高HOUGH_VOTE。我們希望得到的結(jié)果時(shí)剛好檢測(cè)到三條直線(有時(shí)只能檢測(cè)到一條直線,后面會(huì)給出一個(gè)例子)。
? ? ? ? 檢測(cè)到的直線:
計(jì)算傾斜角
? ? ? ? 上面得到了三個(gè)角度,一個(gè)是0度,一個(gè)是90度,另一個(gè)就是我們所需要的傾斜角。要把這個(gè)角找出來(lái),而且要考慮誤差。
| 1234567891011121314151617181920 | floatangel=0;float piThresh=(float)CV_PI/90;floatpi2=CV_PI/2;for(intl=0;l<numLines;l++){floattheta=lines[l][1];if(abs(theta)<piThresh||abs(theta-pi2)<piThresh)continue;else{angel=theta;break;}}angel=angel<pi2?angel:angel-CV_PI;if(angel!=pi2){floatangelT=srcImg.rows*tan(angel)/srcImg.cols;angel=atan(angelT);}float angelD=angel*180/(float)CV_PI; |
? ? ? ? 由于DFT的特點(diǎn),只有輸入圖像是正方形時(shí),檢測(cè)到的角才是文本真正旋轉(zhuǎn)的角度。但我們的輸入圖像不一定是正方形的,所以要根據(jù)圖像的長(zhǎng)寬比改變這個(gè)角度。
? ? ? ? 還有一個(gè)需要注意的細(xì)節(jié),雖然HoughLines()輸出的傾斜角在[0,180)之間,但在[0,90]和(90,180)之間這個(gè)角的含義是不同的。請(qǐng)看圖示:
?
? ? ? ? 當(dāng)傾斜角大于90度時(shí),(180-傾斜角)才是直線相對(duì)豎直方向的偏離角度。在OpenCV中,逆時(shí)針旋轉(zhuǎn),角度為正。要把圖像轉(zhuǎn)回去,這個(gè)角度就變成了(傾斜角-180)。
校正圖像
? ? ? ? 最后一步,當(dāng)然是把圖像轉(zhuǎn)回去~
| 123 | MatrotMat=getRotationMatrix2D(center,angelD,1.0);Mat dstImg=Mat::ones(srcImg.size(),CV_8UC3);warpAffine(srcImg,dstImg,rotMat,srcImg.size(),1,0,Scalar(255,255,255)); |
?
? ? ? ? 先用getRotationMatrix2D()獲得一個(gè)2*3的仿射變換矩陣,再把這個(gè)矩陣輸入warpAffine(),做一個(gè)單純旋轉(zhuǎn)的仿射變換。warpAffine()的最后一個(gè)參數(shù)Scalar(255,255,255)是把由于旋轉(zhuǎn)產(chǎn)生的空白用白色填充。
? ? ? ? 校正的結(jié)果:
一個(gè)檢測(cè)單條直線的例子
原始圖像:
傅里葉譜:
? ? ? ? 只有一條明顯的直線。還好僅有的這條直線正是我們所需要的。
檢測(cè)直線:
校正結(jié)果:
對(duì)中文的效果
? ? ? ? 我們來(lái)試試看這段程序?qū)χ形牡男UЧ?br />輸入圖像:
傅里葉譜:
? ? ? ? 可以發(fā)現(xiàn)有許多條平行的亮線,其中過(guò)頻域原點(diǎn)的那條長(zhǎng)度最長(zhǎng),最容易檢測(cè)出來(lái)。
檢測(cè)直線:
校正結(jié)果:
? ? ? ? 雖然中文和英文在文字上有很大的不同,但字母(或者文字)的高度比較一致,使得行與行之間的分隔很明顯。所以它們的頻域特征是相似的。
對(duì)其他語(yǔ)言文字的效果
? ? ? ? 我從IMDB.com摘取影片《教父》的英文介紹,然后用谷歌翻譯成其他文字進(jìn)行測(cè)試。
阿拉伯語(yǔ)
一枚反例
老撾語(yǔ):
傅里葉譜:
一種二值化的結(jié)果:
直線檢測(cè):
? ? ? ? 這種文字的很多字母的上下方多了很多“筆畫(huà)”(我不知道該怎么稱呼那些小曲線),讓行與行之間的分離變得不明顯,使得頻域特征變得不明顯。
? ? ? ? 雖然用肉眼可以看出傅里葉譜中存在一條傾斜的直線,但它的亮度太低,二值化過(guò)程很難排除噪聲,導(dǎo)致直線檢測(cè)會(huì)首先檢出噪聲產(chǎn)生的直線。這也是我的程序目前受限之處。需要增加一個(gè)過(guò)濾散點(diǎn)噪聲的步驟以增加程序的適用范圍。
參考:Discrete Fourier Transform — OpenCV 2.4.7.0 documentation
代碼還可以在這里下載:https://github.com/johnhany/textRotCorrect
2014.1.3更新:
? ? ? ? 由于文章內(nèi)的圖片右下角存在水印,若直接使用文章內(nèi)的圖片進(jìn)行處理會(huì)使頻域原點(diǎn)附近增加一團(tuán)亮點(diǎn),妨礙直線的檢出。而且為了節(jié)省空間,圖片是經(jīng)過(guò)縮小的,使得字母的邊緣變得模糊,頻域特征也減弱。為此我提供了十幅沒(méi)有水印的圖片,供想要親手實(shí)驗(yàn)的朋友使用。下載鏈接
類別:圖像處理標(biāo)簽:opencv,文本處理文章導(dǎo)航
N條線段求交的掃描線算法Java+MySQL實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲(chóng)程序共有21條評(píng)論
您好,請(qǐng)問(wèn)一下您使用的這個(gè)方法是有沒(méi)有發(fā)過(guò)相應(yīng)的文章,因?yàn)槲业膶?shí)驗(yàn)里用到了您的方法,論文的時(shí)候需要寫(xiě)這個(gè)方法的來(lái)源,所以想問(wèn)一下您這個(gè)方法來(lái)自您自己發(fā)過(guò)的文章里的還是其他書(shū)本或期刊上的。謝謝您。
回復(fù)我的方法并不是完全原創(chuàng),參考的資料也只有文章末尾的那個(gè)官方文檔頁(yè)面。那個(gè)頁(yè)面的最后一節(jié)只是提到水平與傾斜的文本頻譜圖像是有差異的,也沒(méi)有提及相關(guān)的文獻(xiàn),我也是自己用代碼實(shí)驗(yàn)了一下,所以也沒(méi)有發(fā)過(guò)相應(yīng)的文章。某些雜志是允許把網(wǎng)址作為參考文獻(xiàn)的,可以按照格式要求直接把這篇文章的鏈接寫(xiě)上。
回復(fù)謝謝。
回復(fù)您好,看了您的方法后,我用C語(yǔ)言試用了一下,但是不知道為什么在HoughLines后,呈現(xiàn)出來(lái)的圖象是全黑的。為了方便,我貼出這部分的代碼,不知道哪里出了問(wèn)題,想向您請(qǐng)教一下。謝謝。
?
CvMemStorage *storage? = cvCreateMemStorage(0);
?CvSeq* lines = NULL;
?float pi180 = (float)CV_PI/180;
?lines =?cvHoughLines2(Image,storage,CV_HOUGH_STANDARD,1,pi180,HOUGH_VOTE,0,0); //Image是之前的二值化圖象
IplImage *lineImg;
?lineImg = cvCreateImage(cvGetSize(src),IPL_DEPTH_8U,1);
?int numLines = lines->total;
?int i=0;
?for(i=0;i<numLines;i++)
? ?{
? ? ?float *line = (float*) cvGetSeqElem(lines,i);
? ? ?float rho = line[0];
? ? ?float theta = line[1];
? ? ?CvPoint pt1,pt2;
? ? ?double a = cos(theta), b = sin(theta);
? ? ?double x0 = a*rho, y0 = b*rho;
? ? ?pt1.x = cvRound(x0+1000*(-b));
? ? ?pt1.y = cvRound(y0+1000*(a));
? ? ?pt2.x = cvRound(x0-1000*(-b));
? ? ?pt2.y = cvRound(y0-1000*(a));
? ? ?cvLine(lineImg,pt1,pt2,CV_RGB(255,0,0),3,8,0);
}
? ? cvNamedWindow("line",0);
? ? cvShowImage("line",lineImg);
? ? cvWaitKey(10000);
抱歉,最近事情比較多,現(xiàn)在問(wèn)題解決了嗎?
回復(fù)已經(jīng)解決了,抱歉,最近都沒(méi)有看信息。
回復(fù)很好用的代碼,十分感謝!
回復(fù)多謝支持!
回復(fù)作者您好,我想做的是一幅圖像經(jīng)過(guò)傅里葉變換之后,分別得到實(shí)部和虛部的數(shù)據(jù),看了您的文章,深受啟發(fā),但是遇到個(gè)問(wèn)題,下面三步算是對(duì)原有圖像填充的尺寸,但是填充值后傅里葉變換得到的虛部和實(shí)部尺寸也已改變,怎么讓實(shí)部虛部數(shù)據(jù)都還原到填充前的尺寸呢?非常感謝!
int opWidth = getOptimalDFTSize(srcImg.rows);
int opHeight = getOptimalDFTSize(srcImg.cols);
copyMakeBorder(srcImg, padded, 0, opWidth-srcImg.rows, 0, opHeight-srcImg.cols, BORDER_CONST
回復(fù)原有圖像的純色填充對(duì)頻域的影響應(yīng)該是可以忽略的。況且這三個(gè)函數(shù)改變的是圖像的空間域,經(jīng)過(guò)FFT之后得到的頻域圖像,其尺寸也失去了與原來(lái)空間域尺寸比較的意義。雖然OpenCV庫(kù)函數(shù)默認(rèn)產(chǎn)生的頻譜圖像本身的大小與輸入圖像相同,但實(shí)際是對(duì)頻域橫縱坐標(biāo)縮放的結(jié)果,其本質(zhì)上仍然是沿坐標(biāo)軸對(duì)稱的正方形區(qū)域。如果直接對(duì)頻譜圖像切割,會(huì)造成原始圖像頻率上信息的丟失(高頻或是低頻,取決于是否將四象限對(duì)調(diào))。
關(guān)于頻域?qū)?yīng)空間域的關(guān)系,可以參考這個(gè)問(wèn)題的第一個(gè)答案,個(gè)人覺(jué)得解釋得很精湛:http://dsp.stackexchange.com/questions/1637/what-does-frequency-domain-denote-in-case-of-images
回復(fù)感謝您的詳細(xì)解答,可能我太笨啦,有個(gè)問(wèn)題還是想不太明白,希望您解惑。問(wèn)題描述:其實(shí)我是在把MATLAB代碼變成c++時(shí)遇到的提取圖像經(jīng)過(guò)fft變換后的實(shí)部和虛部數(shù)據(jù)這個(gè)問(wèn)題,下面是MATLAB代碼:
//tilted_array2,array1是灰度圖像
//PCE是一個(gè)計(jì)算相關(guān)度的函數(shù)
TA = fft2(tilted_array2); ? ? ? ? ? clear tilted_array2
FA = fft2(array1); ? ? ? ? ? ? ? ? ?clear array1
FF = FA .* TA; ? ? ? ? ? ? ? ? ? ? ?clear FA TA
ret = real(ifft2(FF));
detection = PCE(ret)
可以看到ret得到的是FF傅里葉逆變換之后的實(shí)部數(shù)據(jù),然后在PCE函數(shù)中得到相關(guān)度的數(shù)值。
我把上面的MATLAB代碼變成如下c++代碼(調(diào)用了opencv庫(kù))(菜鳥(niǎo)代碼可能寫(xiě)的亂七八糟,見(jiàn)諒~):
Mat paddedTA;
??int mTA = getOptimalDFTSize(tilted_array2.rows); ?
? int nTA = getOptimalDFTSize(tilted_array2.cols);
??copyMakeBorder(tilted_array2, paddedTA,0,mTA-tilted_array2.rows,0,nTA-tilted_array2.cols, BORDER_CONSTANT, Scalar::all(0));
Mat planesTA[] ={Mat_<float(paddedTA),Mat::zeros(paddedTA.size(), CV_32F) };
?? ?Mat TA;
?? ?merge(planesTA, 2, TA);
?? ?dft(TA, TA);
?? ?split(TA,planesTA);?
Mat paddedFA;
?? ?int mFA = getOptimalDFTSize(array1.rows);?
?? ?int nFA = getOptimalDFTSize(array1.cols);
?? ?copyMakeBorder(array1, paddedFA, 0, mFA-array1.rows, 0, nFA-array1.cols, BORDER_CONSTANT, Scalar::all(0));
?? ?Mat planesFA[] = {Mat_<float>(paddedFA), Mat::zeros(paddedFA.size(), CV_32F) };
?? ?Mat FA;
?? ?merge(planesFA, 2, FA);
?? ?dft(FA, FA);
?? ?split(FA, planesFA);
?//下面實(shí)現(xiàn)MATLAB中的FF = FA .* TA;? ?
Mat FF1,FF2,FF3,FF4;
?? ?FF1=planesFA[0].mul(planesTA[0]);
?? ?FF2=planesFA[1].mul(planesTA[1]);
?? ?FF3=planesFA[0].mul(planesTA[1]);
?? ?FF4=planesFA[1].mul(planesTA[0]);
?? ?Mat planesFF[] = {Mat::zeros(paddedTA.size(), CV_32F), Mat::zeros(paddedTA.size(), CV_32F) };
?? ?subtract(FF1,FF2,planesFF[0]);
?? ?add(FF3,FF4,planesFF[1]);
?? ?Mat FF;
?? ?merge(planesFF, 2, FF);
?? ?
?? ?idft(FF,FF,cv::DFT_SCALE | cv::DFT_REAL_OUTPUT );
?? ?split(FF,planesFF);
?? ?Mat rel=planesFF[0];
? ??PCE(rel,detection);
因?yàn)間etOptimalDFTSize()函數(shù)是把輸入圖像的尺寸調(diào)節(jié)成2^n或2,3,5乘積這種形式,當(dāng)我的輸入圖像像素滿足是2^n或2,3,5乘積這種形式時(shí),程序不會(huì)對(duì)原圖像填充補(bǔ)0,這時(shí)opencv得到的planesTA、planesFA的實(shí)部和虛部數(shù)據(jù)都與MATLAB中TA、FA值一樣(行列數(shù)也都一樣),PCE函數(shù)得到的最后結(jié)果detection也是一樣的。但是,如果輸入圖像不滿足像素是2^n或2,3,5乘積這種形式時(shí),程序會(huì)對(duì)輸入圖像進(jìn)行填充,這樣得到的
planesTA、planesFA的行列都是填充之后的行列,與輸入圖像行列不同,這時(shí)
planesTA、planesFA的實(shí)部和虛部數(shù)據(jù)都與MATLAB中TA、FA對(duì)應(yīng)數(shù)據(jù)肯定不同,但我想著可能對(duì)最后的PCE函數(shù)結(jié)果沒(méi)有影響,結(jié)果卻是這種情況下opencv的PCE結(jié)果與MATLAB也是不同的。
所以我想請(qǐng)教您的是,輸入圖像像素不滿足條件時(shí)最后PCE結(jié)果不同的這個(gè)問(wèn)題該怎么解決?不勝感謝!
回復(fù)為方便您的理解我貼出函數(shù)PCE的MATLAB代碼:
shift_range = [0,0];
Cinrange = rel(end-shift_range(1):end,end-shift_range(2):end);
[max_cc, imax] = max(Cinrange(:));
[ypeak, xpeak] = ind2sub(size(Cinrange),imax(1));
peakheight = Cinrange(ypeak,xpeak);
C_without_peak = RemoveNeighborhood(C,[ypeak, xpeak],squaresize);//RemoveNeighborhood是一個(gè)函數(shù),下面有代碼
correl = rel(end,end); ? ? ? ?clear rel
PCE_energy = mean(C_without_peak.*C_without_peak);
detection = peakheight.^2/PCE_energy * sign(peakheight);
?
//RemoveNeighborhood函數(shù)
function Y = RemoveNeighborhood(X,x,ssize)
% Remove a 2-D neighborhood around x=[x1,x2] from matrix X and output a 1-D vector Y
% ssize ? ? square neighborhood has size (ssize x ssize) square
[M,N] = size(X);
radius = (ssize-1)/2;
X = circshift(X,[radius-x(1)+1,radius-x(2)+1]);
Y = X(ssize+1:end,1:ssize); ?
Y = Y(:);
Y = [Y;X(M*ssize+1:end)'];
非常感謝!
?
?
?
回復(fù)在fft前不進(jìn)行速度優(yōu)化,直接對(duì)圖像進(jìn)行操作即可
是把高頻部分移到中心位置吧?
回復(fù)象限換位的目的就是把頻域原點(diǎn)移到圖像中心,所以移位后低頻是位于中心區(qū)域的。頻譜圖像中心的小亮點(diǎn)F(0,0)表示圖像的平均灰度,也能說(shuō)明這一點(diǎn)。
感興趣的話,可以試著把一幅圖像作高斯模糊,再比較一下前后的頻譜圖像,坐標(biāo)軸附近的點(diǎn)亮度是會(huì)增加的。
回復(fù)試了一下 英文很好用,中文就不行了,葡萄牙語(yǔ)也不行,西班牙語(yǔ)還能湊合著用。總之,非常感謝博主分享。另有什么改善的方法么?
回復(fù)多謝支持!就方法上的改進(jìn)可以用調(diào)整參數(shù),過(guò)濾散點(diǎn)噪聲等手段,但從理論上我目前還沒(méi)有更好的改進(jìn)思路。
回復(fù)非常感謝如此詳細(xì)的文章!
回復(fù)多謝支持!
回復(fù)能不能判斷出一個(gè)圖片中的三角形傾斜的角度?
回復(fù)抱歉回復(fù)的很晚。本文的方法是不能用來(lái)計(jì)算單個(gè)三角形傾斜的角度的,只能考慮其他方法
回復(fù)總結(jié)
以上是生活随笔為你收集整理的OpenCV实现基于傅里叶变换(FFT)的旋转文本校正(文字方向检测)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 基于OpenCV完成离散傅里叶变换
- 下一篇: 边缘检测的各种微分算子比较(Sobel,