1圖像分割原理
圖像分割的研究多年來一直受到人們的高度重視,至今提出了各種類型的分割算法。Pal把圖像分割算法分成了6類:閾值分割,像素分割、深度圖像分割、彩色圖像分割,邊緣檢測和基於模糊集的方法。但是,該方法中,各個類別的內容是有重疊的。為了涵蓋不斷涌現的新方法,有的研究者將圖像分割算法分為以下六類:並行邊界分割技術、串行邊界分割技術、並行區域分割技術、串行區域分割技術、結合特定理論工具的分割技術和特殊圖像分割技術。而在較近的一篇綜述中,更有學者將圖像分割簡單的分割數據驅動的分割和模型驅動的分割兩類。下面將圖像分割方法主要分以下幾類:基於閾值的分割方法、基於區域的分割方法、基於邊緣的分割方法、基於數學形態的分割方法以及基於特定理論的分割方法等,對其中主要的分別進行簡要介紹。
1.1灰度閾值分割法
是一種最常用的並行區域技術,它是圖像分割中應用數量最多的一類。閾值分割方法實際上是輸入圖像f到輸出圖像g的如下變換:
其中,T為閾值,對於物體的圖像元素g(i,j)=l,對於背景的圖像元素g(i,j)=0。
由此可見,閾值分割算法的關鍵是確定閾值,如果能確定一個合適的閾值就可准確地將圖像分割開來。如果閾值選取過高,則過多的目標區域將被划分為背景,相反如果閾值選取過低,則過多的背景將被划分到目標區[7]。閾值確定后,將閾值與像素點的灰度值比較和像素分割可對各像素並行地進行,分割的結果直接給出圖像區域。
閾值分割必須滿足一個假設條件:圖像的直方圖具有較明顯的雙峰或多峰,並在谷底選擇閉值。因此這種方法對目標和背景反差較大的圖像進行分割的效果十分明顯,而且總能用封閉、連通的邊界定義不交疊的區域。
閾值分割法主要分為全局和局部兩種,目前應用的閉值分割方法都是在此基礎上發展起來的,比如最小誤差法、最大相關法、最大嫡法、矩量保持法、Otsu最大類間方差法等,而應用最廣泛的是Otsu最大類間方差法。
人們發展了各種各樣的閾值處理技術,包括全局閾值、自適應閾值、最佳閾值等等。 全局閾值是指整幅圖像使用同一個閾值做分割處理,適用於背景和前景有明顯對比的圖像。它是根據整幅圖像確定的:T=T(f)。但是這種方法只考慮像素本身的灰度值,一般不考慮空間特征,因而對噪聲很敏感。常用的全局閾值選取方法有利用圖像灰度直方圖的峰谷法、最小誤差法、最大類間方差法、最大熵自動閾值法以及其它一些方法。在許多情況下,物體和背景的對比度在圖像中的各處不是一樣的,這時很難用一個統一的閾值將物體與背景分開。這時可以根據圖像的局部特征分別采用不同的閾值進行分割。實際處理時,需要按照具體問題將圖像分成若干子區域分別選擇閾值,或者動態地根據一定的鄰域范圍選擇每點處的閾值,進行圖像分割。這時的閾值為自適應閾值。閾值的選擇需要根據具體問題來確定,一般通過實驗來確定。對於給定的圖像,可以通過分析直方圖的方法確定最佳的閾值,例如當直方圖明顯呈現雙峰情況時,可以選擇兩個峰值的中點作為最佳閾值。
閾值分割的優點是計算簡單、運算效率較高、速度快,在算法上容易實現,在重視運算效率的應用場合(如用於硬件實現),它得到了廣泛應用。它對目標和背景對比度反差較大圖像這種分割很有效, 而且總能用封閉、連通的邊界定義不交疊的區域。但它不適用於多通道圖像和特征值相關不大的圖像,對圖像中不存在明顯灰度差異或各物體的灰度值范圍有較大重疊的圖像分割問題難以得到准確結果。另外由於閾值確定主要依賴於灰度直方圖, 而很少考慮圖像中像素的空間位置關系,因此當背景復雜,特別在是同一背景上重疊出現若干個研究目標時,或圖像中噪聲信號較多時,目標的灰度值與背景相差無幾等情形下,容易喪失部分邊界信息,按照固定的閾值進行分割所得到的結果就不准確,造成分割不完整,需要進一步的精確定位。
1.2基於區域的分割方法
區域生長和分裂合並法是兩種典型的串行區域技術,其分割過程后續步驟的處理要根據前面步驟的結果進行判斷而確定。
(1)區域生長
區域生長的基本思想是將具有相似性質的像素集合起來構成區域。具體先對每個需要分割的區域找一個種子像素作為生長的起點,然后將種子像素周圍鄰域中與種子像素有相同或相似性質的像素(根據某種事先確定的生長或相似准則來判定)合並到種子像素所在的區域中。將這些新像素當作新的種子像素繼續進行上面的過程,直到再沒有滿足條件的像素可被包括進來。
區域生長需要選擇一組能正確代表所需區域的種子像素,確定在生長過程中的相似性准則,制定讓生長停止的條件或准則。相似性准則可以是灰度級、彩色、紋理、梯度等特性。選取的種子像素可以是單個像素,也可以是包含若干個像素的小區域。大部分區域生長准則使用圖像的局部性質。生長准則可根據不同原則制定,而使用不同的生長准則會影響區域生長的過程。
區域生長法的優點是計算簡單,對於較均勻的連通目標有較好的分割效果,對有復雜物體定義的復雜場景的分割或者對自然景物的分割等類似先驗知識不足的圖像分割, 效果均較理想,Wu H S等提出利用肺部癌細胞圖像的均值、標准偏差構成的矢量作為細胞分割的特征, 提出的區域增長分割算法分割肺部癌細胞紋理圖像, 取得較好結果[10]。它的缺點是需要人為確定種子點,雖然其抗噪性能優於邊緣分割和直方圖分割,但仍對噪聲敏感,可能導致區域內有空洞;另外,它是一種串行算法,當目標較大時,分割速度較慢,因此在設計算法時,要盡量提高效率;而且在計算過程中引入的預定誤差值選取不當時,還會引入誤判,易受分析目標內部組織之間的重疊干擾影響。因此,基於區域生長的分割方法一般適合於邊緣光滑、無重疊的細胞圖象的分割。
(2)區域分裂合並
區域生長是從某個或者某些像素點出發,最后得到整個區域,進而實現目標提取。分裂合並差不多是區域生長的逆過程:從整個圖像出發,不斷分裂得到各個子區域,然后再把前景區域合並,實現目標提取。分裂合並的假設是對於一幅圖像,前景區域由一些相互連通的像素組成的,因此,如果把一幅圖像分裂到像素級,那么就可以判定該像素是否為前景像素。當所有像素點或者子區域完成判斷以后,把前景區域或者像素合並就可得到前景目標。在這類方法中,最常用的方法是四叉樹分解法。設R代表整個正方形圖像區域,P代表邏輯謂詞。基本分裂合並算法步驟如下:
① 對任一個區域,如果H(Ri)=FALSE就將其分裂成不重疊的四等份;
② 對相鄰的兩個區域Ri和Rj,它們也可以大小不同(即不在同一層),如果條件H(Ri∪Rj)=TRUE滿足,就將它們合並起來。
③ 如果進一步的分裂或合並都不可能,則結束。
分裂合並法的關鍵是分裂合並准則的設計。這種方法對復雜圖像的分割效果較好,但算法較復雜,計算量大,分裂還可能破壞區域的邊界。
1.3基於邊緣的分割方法
圖像分割的一種重要途徑是通過邊緣檢測,即檢測灰度級或者結構具有突變的地方,表明一個區域的終結,也是另一個區域開始的地方。這種不連續性稱為邊緣。不同的圖像灰度不同,邊界處一般有明顯的邊緣,利用此特征可以分割圖像。圖像中邊緣處像素的灰度值不連續,這種不連續性可通過求導數來檢測到。對於階躍狀邊緣,其位置對應一階導數的極值點,對應二階導數的過零點(零交叉點)。因此常用微分算子進行邊緣檢測[11]。
常用的一階微分算子有Roberts算子、Prewitt算子和Sobel算子,二階微分算子有Laplace算子和Kirsh算子等。在實際中各種微分算子常用小區域模板來表示,微分運算是利用模板和圖像卷積來實現。這些算子對噪聲敏感,只適合於噪聲較小不太復雜的圖像。由於邊緣和噪聲都是灰度不連續點,在頻域均為高頻分量,直接采用微分運算難以克服噪聲的影響。因此用微分算子檢測邊緣前要對圖像進行平滑濾波。
Roberts算子有利於對具有陡峭邊緣的低噪聲圖像的分割;laplacian算子具有各向同性的特點;Roberts算子和laplacian算子在實施過程中大大增強了噪聲,惡化了信噪比。Prewitt算子、Sobel算子等有利於對具有較多噪聲且灰度漸變圖像的分割。Log算子和Canny算子是具有平滑功能的二階和一階微分算子,邊緣檢測效果較好。其中Log算子是采用Laplacian算子求高斯函數的二階導數,Canny算子是高斯函數的一階導數,它在噪聲抑制和邊緣檢測之間取得了較好的平衡。Marr算法對有較多噪聲的圖像具有平滑作用,且其邊緣檢測效果優於以上幾種算子,但Marr算法在平滑的同時導致圖像對比度下降[7]。Kirch算法利用對梯度圖像適當的閾值進行二值化, 使得目標和背景像素點低於閾值, 而大多數邊緣點高於閥值, 同時為了提高性能, 在該類算法中可引入分水嶺算法以進行准確分割[1]。
Hough變換法利用圖像全局特性而直接檢測目標輪廓,將邊緣像素連接起來組成區域封閉邊界的一種常見方法。在預先知道區域形狀的條件下,利用哈夫變換可以方便地得到邊界曲線而將不連續的邊界像素點連接起來。它的主要優點是受噪聲和曲線間斷的影響較小。
對於灰度變化復雜和細節較豐富圖象,邊緣檢測算子均很難完全檢測出邊緣,而且一旦有噪聲干擾時,上述算子直接處理效果更不理想。這一方法用來分割顯微圖象的例子不多,因為顯微圖象中的許多紋理或顆粒會掩蓋真正的邊緣,雖然可以通過有關算法改進,但效果並不太好。
擬合算子( 即參數模型匹配算法)原理:用邊緣的參數模型對圖像的局部灰度值進行擬合, 再在擬合的參數模型上進行邊緣檢測。優缺點:此類算子在檢測邊緣的同時, 還平滑了噪聲, 對有較大噪聲和高紋理細胞圖像處理效果較好, 但由於參數模型記錄着更多的邊緣結構信息, 計算開銷很大, 算法復雜, 而且對邊緣類型要求較高。
以上三種方法中,基於邊緣分割的最常見的問題是在沒有邊界的地方出現了邊緣以及在實際存在邊界的地方沒有出現邊界, 這是由圖像噪聲或圖像中的不適合的信息造成的[24]。基於區域增長方法分割后的圖像, 時常是由於參數的設置非最優性造成, 不是含有太多的區域就是含有過少的區域。閾值化是最簡單的分割處理, 計算代價小, 速度快, 用一個亮度常量即閾值來分割物體和背景。
2. 圖像分割的實現
我用了Roberts算子、Sobel算子和Kirsh算子邊緣檢測的方法但都由於亮度不均等因素對圖像分割的效果不太好:
Sobel:
void sobel (unsignedchar* des, constunsignedchar* src, int width, int height) { for (int y=0; y<height; y++) for (int x=0; x<width; x++) des[y * width + x]=255; /* Now compute the convolution, scaling */ for (int y=1; y<height-1; y++) for (int x=1; x<width-1; x++) { double n = (src[(y+1)*width+x-1]+2*src[(y+1)*width+x]+src[(y+1)*width+x+1]) - (src[(y-1)*width+x-1]+2*src[(y-1)*width+x]+src[(y-1)*width+x+1]); double m = (src[(y-1)*width+x+1]+2*src[y*width+x+1]+src[(y+1)*width+x+1])- (src[(y-1)*width+x-1]+2*src[y*width+x-1]+src[(y+1)*width+x-1]); double k = (int)( sqrt( (double)(n*n + m*m) )/4.0 ); des[y * width + x] = k; } thresh (des, width,height); } Roberts算子: void roberts(unsignedchar* des, constunsignedchar* src, int width, int height) { for (int y=0; y<height; y++) for (int x=0; x<width; x++) des[y * width + x]=255; /* Now compute the convolution, scaling */ for (int y=1; y<height-1; y++) for (int x=1; x<width-1; x++) { double n = src[y*width+x] - src[(y+1)*width+x+1]; double m = src[(y+1)*width+x] - src[y*width+x+1]; double k = abs(m)+abs(n); des[y * width + x] = k; } thresh (des, width,height); } Kirsch算子: void kirsch(unsigned char* des, const unsigned char* src, int width, int height) { // TODO: Add your command handler code here //顯示數值 long int i,j,Ns; static int nWeight[8][3][3];//對一個靜態整型數組賦初值,模板 double dGrad[8]; int nTmp[3][3],xx,yy;//每像素點的鄰域值 nWeight[0][0][0] = -1 ; nWeight[0][0][1] = 0 ; nWeight[0][0][2] = 1 ; nWeight[0][1][0] = -2 ; nWeight[0][1][1] = 0 ; nWeight[0][1][2] = 2 ; nWeight[0][2][0] = -1 ; nWeight[0][2][1] = 0 ; nWeight[0][2][2] = 1 ; nWeight[1][0][0] = -1 ; nWeight[1][0][1] = -2 ; nWeight[1][0][2] = -1 ; nWeight[1][1][0] = 0 ; nWeight[1][1][1] = 0 ; nWeight[1][1][2] = 0 ; nWeight[1][2][0] = 1 ; nWeight[1][2][1] = 2 ; nWeight[1][2][2] = 1 ;//負號上下??? 已改成8個方向模板的值 nWeight[2][0][0] = 0 ; nWeight[2][0][1] = -1 ; nWeight[2][0][2] = -2 ; nWeight[2][1][0] = 1 ; nWeight[2][1][1] = 0 ; nWeight[2][1][2] = -1 ; nWeight[2][2][0] = 2 ; nWeight[2][2][1] = 1 ; nWeight[2][2][2] = 0 ; nWeight[3][0][0] = 1 ; nWeight[3][0][1] = 0 ; nWeight[3][0][2] = -1 ; nWeight[3][1][0] = 2 ; nWeight[3][1][1] = 0 ; nWeight[3][1][2] = -2 ; nWeight[3][2][0] = 1 ; nWeight[3][2][1] = 0 ; nWeight[3][2][2] = -1 ; nWeight[4][0][0] = 2 ; nWeight[4][0][1] = 1 ; nWeight[4][0][2] = 0 ; nWeight[4][1][0] = 1 ; nWeight[4][1][1] = 0 ; nWeight[4][1][2] = -1 ; nWeight[4][2][0] = 0 ; nWeight[4][2][1] = -1 ; nWeight[4][2][2] = -2 ; nWeight[5][0][0] = 1 ; nWeight[5][0][1] = 2 ; nWeight[5][0][2] = 1 ; nWeight[5][1][0] = 0 ; nWeight[5][1][1] = 0 ; nWeight[5][1][2] = 0 ; nWeight[5][2][0] = -1 ; nWeight[5][2][1] = -2 ; nWeight[5][2][2] = -1 ; nWeight[6][0][0] = 0 ; nWeight[6][0][1] = 1 ; nWeight[6][0][2] = 2 ; nWeight[6][1][0] = -1 ; nWeight[6][1][1] = 0 ; nWeight[6][1][2] = 1 ; nWeight[6][2][0] = -2 ; nWeight[6][2][1] = -1 ; nWeight[6][2][2] = 0 ; nWeight[7][0][0] = -2 ; nWeight[7][0][1] = -1 ; nWeight[7][0][2] = 0 ; nWeight[7][1][0] = -1 ; nWeight[7][1][1] = 0 ; nWeight[7][1][2] = 1 ; nWeight[7][2][0] = 0 ; nWeight[7][2][1] = -1 ; nWeight[7][2][2] = 2 ; //注意:每行的字節數必須是4的整數倍!!!先不考慮 Ns=height*width; unsigned char* kk = new unsigned char[width * height]; //開始變換 initiion for(i=0; i<height ; i++ ) //if(i==0)//tt change at 05.05.16 for(j=0 ; j<width ; j++ ) { des[i*width + j]=0;//*(pdGrad+y*nWidth+x) } for(i=1; i<height-1 ; i++ ) { for(j=1 ; j<width-1 ; j++ ) { dGrad[0] = 0 ; dGrad[1] = 0 ; dGrad[2] = 0 ; dGrad[3] = 0 ; dGrad[4] = 0 ; dGrad[5] = 0 ; dGrad[6] = 0 ; dGrad[7] = 0 ; // sobel算子需要的各點象素值 // 模板第一行 nTmp[0][0] = src[(i-1)*width + j - 1 ]; nTmp[0][1] = src[(i-1)*width + j ] ; nTmp[0][2] = src[(i-1)*width + j + 1 ] ; // 模板第二行 nTmp[1][0] = src[i*width + j - 1 ] ; nTmp[1][1] = src[i*width + j ] ; nTmp[1][2] = src[i*width + j + 1 ] ; // 模板第三行 nTmp[2][0] = src[(i+1)*width + j - 1 ] ; nTmp[2][1] = src[(i+1)*width + j ] ; nTmp[2][2] = src[(i+1)*width + j + 1 ] ; // 計算梯度 for(yy=0; yy<3; yy++) for(xx=0; xx<3; xx++) { dGrad[0] += nTmp[yy][xx] * nWeight[0][yy][xx] ; dGrad[1] += nTmp[yy][xx] * nWeight[1][yy][xx] ; dGrad[2] += nTmp[yy][xx] * nWeight[2][yy][xx] ; dGrad[3] += nTmp[yy][xx] * nWeight[3][yy][xx] ; dGrad[4] += nTmp[yy][xx] * nWeight[4][yy][xx] ; dGrad[5] += nTmp[yy][xx] * nWeight[5][yy][xx] ; dGrad[6] += nTmp[yy][xx] * nWeight[6][yy][xx] ; dGrad[7] += nTmp[yy][xx] * nWeight[7][yy][xx] ; } for (xx=1;xx<8;xx++) { if (dGrad[xx]>dGrad[0]) dGrad[0]=dGrad[xx]; } des[i*width + j]=dGrad[0];// 梯度值寫入src[i] } } //設定閾值 int th[5120],newth[5120],shuN,newN,flagyuzhi;//winframe=32,ii,jj,initpos; double thk,kmin,mvalue[8]; shuN=0; thk=0.5; for (i=0;i<Ns;i++)//每層的每個點 { if ((i>=width) && (i<(Ns-width)))//若是非邊界點,則…… { if ((i%width!=0) && ((i+1)%width!=0)) { //每點做變換,首先求kirs(c)h算子 mvalue[0]=fabs(double(des[i+1]+des[i+width+1]+des[i+width]+\ des[i+width-1]+des[i-1]-des[i-width-1]-\ des[i-width]-des[i-width+1])); mvalue[1]=fabs(double(des[i+width+1]+des[i+width]+\ des[i+width-1]+des[i-1]+des[i-width-1]-\ des[i-width]-des[i-width+1]-des[i+1])); mvalue[2]=fabs(double(des[i+width]+des[i+width-1]+des[i-1]+\ des[i-width-1]+des[i-width]-\ des[i-width+1]-des[i+1]-des[i+width+1])); mvalue[3]=fabs(double(des[i+width-1]+des[i-1]+\ des[i-width-1]+des[i-width]+\ des[i-width+1]-des[i+1]-des[i+width+1]-\ des[i+width])); mvalue[4]=fabs(double(des[i-1]+des[i-width-1]+\ des[i-width]+des[i-width+1]+des[i+1]-\ des[i+width+1]-des[i+width]-\ des[i+width-1])); mvalue[5]=fabs(double(des[i-width-1]+des[i-width]+\ des[i-width+1]+des[i+1]+des[i+width+1]-\ des[i+width]-des[i+width-1]-des[i-1])); mvalue[6]=fabs(double(des[i-width]+des[i-width+1]+des[i+1]+\ des[i+width+1]+des[i+width]-\ des[i+width-1]-des[i-1]-des[i-width-1])); mvalue[7]=fabs(double(des[i-width+1]+des[i+1]+des[i+width+1]+\ des[i+width]+des[i+width-1]-\ des[i-1]-des[i-width-1]-des[i-width])); for (j=1;j<8;j++) //比較得出算子,mvalue[0]為最大 { if (mvalue[0]<mvalue[j]) mvalue[0]=mvalue[j]; } kk[i]=max(1,mvalue[0]/15); if (shuN==0) kmin=kk[i]; if (kk[i]>thk) { th[shuN]=i; kmin=min(kmin,kk[i]); shuN++; if (shuN>=5*height)//若大於5*H個點,則重新確定 { //AfxMessageBox("lll"); thk=kmin; newN=0; for (j=0;j<shuN;j++) { if (kk[th[j]]>thk) { if (newN==0) kmin=kk[th[j]]; newth[newN]=th[j]; kmin=min(kmin,kk[th[j]]); newN++; } //else des[th[j]]=0; } for (j=0;j<5120;j++) { th[j]=newth[j]; } shuN=newN; }//重新確定完 } //非邊界的每點變換結束 } } }//一層結束 for (i=0;i<Ns;i++)//每層的每個點 { if (des[i]<thk) des[i]=0; } thresh (des, width,height); //菜單函數結束 }
下面三圖分別為sobel、Roberts、kerish邊緣檢測的結果:
之后打算用霍夫變換檢測直線找矩形框,但是由於光照形成的噪點效果並不是很好,因此最后用自適應直方圖均衡去除光照影響加自適應中值濾波再用投影法實現矩形框和數字的檢測。具體如下:
int main() { IplImage* src = cvLoadImage("dm5.bmp"); IplImage* gray = cvCreateImage(cvGetSize(src), src->depth, 1); cvCvtColor(src,gray,CV_BGR2GRAY); //灰度化 int width = src->width; int height = src->height; IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, gray->nChannels); IplImage* scr = cvCreateImage(cvGetSize(gray), gray->depth, gray->nChannels); cvSmooth(gray, gray, CV_MEDIAN, 3, 0, 0, 0); //中值濾波,消除小的噪聲; cvSmooth(gray, gray, CV_GAUSSIAN, 9, gray->nChannels);//高斯濾波 cvCvtColor(src,scr,CV_BGR2GRAY); cvThreshold( gray, gray, 190, 255, CV_THRESH_BINARY);//二值化 int nChannels =gray->nChannels; cvNamedWindow("origin",0); cvResizeWindow("origin",int(width/2),int(height/2)); cvShowImage("origin", src); unsigned char* img = new unsigned char[width * height ]; unsigned char* des = new unsigned char[width * height ]; unsigned char* gra = new unsigned char[width * height]; unsigned char* grt = new unsigned char[width * height]; img_data(gray, gra,width,height, nChannels); img_data(scr, img,width,height,nChannels); AHE(des, img, width, height,nChannels,10);//自適應直方圖均衡 Projection( grt,gra,width,height); //投影檢測表盤區域 img_extract(des,grt,width,height,1); //表盤區域還原 //kirsch(des,gra, width,height); data_img( scr, des, width, height, nChannels); cvNamedWindow("表盤",0); cvResizeWindow("表盤",int(width/2),int(height/2)); cvShowImage("表盤", scr); cvThreshold(scr, scr, 100, 255, CV_THRESH_BINARY); //表盤區域二值化以查找數字 img_data(scr, img,width,height,nChannels); Adaptivemedianfilter(des, img, width, height, nChannels); //自適應中值濾波去噪 ImageDilation( img, des, width, height,nChannels,1); ImageErosion( des,img,width, height,nChannels,1); //經過一次膨脹腐蝕去噪 location(img, des, width, height); //找出數字所在區域 data_img( scr, img, width, height, nChannels); cvNamedWindow("數字",0); cvResizeWindow("數字",int(width/2),int(height/2)); cvSaveImage("123.bmp",scr); cvShowImage("數字", scr); data_img( gray,des, width, height, nChannels); cvNamedWindow("erzhi",0); cvResizeWindow("erzhi",int(width/2),int(height/2)); cvShowImage("erzhi", gray); cvWaitKey(0); } /************************************************************************** 函數名:Projection 功 能:投影法找出矩形區域 輸 入:目標圖像des, 原圖像 src,圖像寬width, 高height 返回值:no *************************************************************************/ void Projection(unsigned char* des, const unsigned char* src,int width, int height) { int* h_sum = new int[height]; int* w_sum = new int[width]; int up=0; int below=height; int left=0; int right=width; for(int y=0;y<height;y++) { for(int x=0;x<width;x++) { des[y*width+x]=255; } } for(int y=0;y<height;y++) { h_sum[y]=0; for(int x=0;x<width;x++) { //printf("src %d",src[y*width+x]); h_sum[y]=h_sum[y]+src[y*width+x]; } //printf("%d行%d ",y,h_sum[y]); } for(int y=height/2;y<height;y++) { if((h_sum[y]-h_sum[height/2])>255*60) { below=y; break; } } for(int y=height/2;y>0;y--) { if((h_sum[y]-h_sum[height/2])>255*60) { up=y; break; } } for(int x=0;x<width;x++) { w_sum[x]=0; for(int y=up;y<below;y++) { w_sum[x]=w_sum[x]+src[y*width+x]; } //printf("%d列%d ",x,w_sum[x]); } int max_r=0; int max_l=0; for(int x=width/2+100;x<width;x++) { if(w_sum[x]>max_r) { right=x; max_r=w_sum[x]; } } for(int x=width/2-100;x>0;x--) { if(w_sum[x]>max_l) { left=x; max_l=w_sum[x]; } } for(int y=up;y<below;y++) { for(int x=left;x<right;x++) { des[y*width+x]=0; } } printf("up%d below%d left%d right%d",up, below,left, right); } void img_extract(unsigned char* des, const unsigned char* src,int width, int height, int nChannels) { for (int y=0;y<height;y++) for(int x=0;x<width;x++) if(src[y*width+x]!=0) { for(int n = 0; n < nChannels; n++) { des[y * width * nChannels + x * nChannels + n ] = 255; } } } /************************************************************************ 函數名:location 功 能:投影法找出數字 輸 入:目標圖像des, 原圖像 src,圖像寬width, 高height 返回值:no **********************************************************************/ void location(unsigned char* des, const unsigned char* src,int width, int height) { int* h_sum = new int[height]; int* w_sum = new int[width]; int up=0; int below=height; int left=0; int right=width; for(int y=0;y<height;y++) { for(int x=0;x<width;x++) { des[y*width+x]=255; } } for(int y=0;y<height;y++) { h_sum[y]=0; for(int x=0;x<width;x++) { //printf("src %d",src[y*width+x]); h_sum[y]=h_sum[y]+src[y*width+x]; } //printf("%d行%d ",y,h_sum[y]); } int h_mid=(h_sum[height/2]+h_sum[height/2-10]+h_sum[height/2-20]+h_sum[height/2-30]+h_sum[height/2-40]); h_mid=h_mid/5; for(int y=height/2;y<height;y++) { if((h_sum[y]-h_mid)>255*35) { below=y; break; } } for(int y=height/2;y>0;y--) { if((h_sum[y]-h_mid)>255*37) { up=y; break; } } for(int x=0;x<width;x++) { w_sum[x]=0; for(int y=up;y<below;y++) { w_sum[x]=w_sum[x]+src[y*width+x]; } //printf("%d列%d ",x,w_sum[x]); } int right_start=width-10; for(int x=width-10;x>width/2;x--) { if(w_sum[x]!=(below-up)*255) { right_start=x; break; } } for(int x=right_start-45;x>width/2;x--) { if(w_sum[x]<255*(below-up-40)) { right=x; break; } } int left_start=10; for(int x=10;x<width;x++) { if(w_sum[x]!=(below-up)*255) { left_start=x; break; } } for(int x=left_start+100;x<width;x++) { if(w_sum[x]<255*(below-up-20)) { left=x; break; } } for(int y=up;y<below;y++) { for(int x=left-5;x<right+5;x++) { des[y*width+x]=src[y*width+x]; } } printf("up%d below%d left%d right%d left_start%d h_mid%d height/2%d width%d",up, below,left, right,left_start,h_mid,height/2,width); } 1.3結果展示