關於我自己

我的相片
累計超過15年的工作經驗,包括10年的設備製造業經驗,超過6年的光電零組件製造業經驗;10年的海外工作經驗,其中至今有5年以上的派駐經驗。 1.專注AOI檢測於10年以上(從光機設計、圖像分析、設備挑選、處理速度) 2.機器學習推論-onnx整合傳統演算法從瑕疵抓取、分析、分類 3.遷移式機器學習模式(工業應用、醫療應用) 4.整合:Python推論引擎、C++運算能力、C#友善介面 彈性使用 5.擅長利用專家模型推進專案,並熟悉使用OpenVINO、TensorRT、ONNXRUNTIME框架進行有效的實作與測試。

2011年10月7日 星期五

各種圖像分割方法

http://blog.csdn.net/cay22/article/details/5645425

圖像分割的方法

一. 基於閾值的圖像分割

1. 直方圖雙峰法(mode 法)

Prewitt 等人於六十年代中期提出的直方圖雙峰法(也稱 mode 是典型的全局單閾值分割方法。該方法的基本思想是:假設圖像中有明顯的目標和背景,則其灰度直方圖呈雙峰分布,當灰度級直方圖具有雙峰特性時,選取兩峰之間的谷對應的灰度級作為閾值。如果背景的灰度值在整個圖像中可以合理地看作為恆定,而且所有物體與背景都具有幾乎相同的對比度,那麼,選擇一個正確的、固定的全局閾值會有較好的效果。例如圖4.1所示:
  
4.1原始灰度圖像

4.2灰度直方圖
選定閾值M100

算法實現:找到第一個峰值和第二個峰值, 再找到第一和第二個峰值之間的谷值,谷值就是那個閥值了。


2. 固定閾值分割

就是設定一個固定的值, 像素灰度大於就該像素編程0或者255或者其他的,小於的又等於什麼的。
for(int i = 0; i < nWidth; ++i)
{
       for(int j = 0; j < nHigh; ++j)
       {
              if(Image[i][j] >= 閾值)
              {
                     Image[i][j] = 255;
}
else
{
       Image[i][j] = 0;
}
}
}

這個閾值選什麼值呢, 1中的雙峰法就是一個閾值產生的方法。


3. 半閾值分割

for (j=0;j
{
       for(i=0;i
       {
              lpSrc=p_data+wide*j+i;
              lpDst=temp+wide*j+i;
                    
              if((*lpSrc - 閾值) < 30)
                     *lpDst=*lpSrc;
              else               
                     *lpDst = 255;
       }
}
不知道為什麼這麼做, 為什麼這樣就叫做半閾值?

4. 迭代閾值圖像分割

http://topic.csdn.net/u/20080402/10/d3cb6789-fa60-4758-b232-7a89926f07b9.html
迭代法是基於逼近的思想,其步驟如下: 
1 求出圖象的最大灰度值和最小灰度值,分別記為ZMAXZMIN,令初始閾值T0=(ZMAX+ZMIN)/2 
2 根據閾值TK將圖象分割為前景和背景,分別求出兩者的平均灰度值ZOZB 
3 求出新閾值TK+1=(ZO+ZB)/2 
4 TK==TK+1,則所得即為閾值;否則轉2,迭代計算。 
我想問下,ZOZB怎麼求??

1. 統計圖像灰度直方圖
2. 找到最大灰度值ZMAX和最小灰度值ZMIN,並計算T0 =(ZMAX+ZMIN)/2
3. 計算小於T0的所有灰度的均值ZO和大於T0的所有灰度的均值ZB(用直方圖求就可以)。
例如,你的直方圖從10250有值,則T0 = 260/2 = 130.
ZO = Sum(nHist[i] * i) / Sum(nHist[i]); 10 <= i <= 130
BO = Sum(nHist[i] * i) / Sum(nHist[i]); 131 <= i <= 250
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ZO = .0, ZB = .0;
int nB = 0, nO = 0;
BYTE bytVal = 0;

while( 還有圖像數據沒讀完 )
{
    bytVal = ReadNextPixel();
    if( bytVal > T0 )
    {
        ZB += bytVal;
        ++nB;
    }
    else
    {
        ZO += bytVal;
        ++nO;
    }
}
ZO /= nO;
ZB /= nB;

//////////////////////////////////////////////////////////////////////////////////////////////////////////
偽代碼1
A. 找到灰度圖中最大灰度nZmax和最小灰度nZmin(代碼略)

B. T0
T0 = (nZmax + nZmin) / 2;

C. 迭代了求出閾值
       int i;
       while (true)
       {
              // 計算下一個迭代閥值
              for (i = 0; i < T0 + 1; i++)
              {
                     Temp0 += tongji[i] * i;
                     Temp1 += tongji[i];
              }
              for (i = T0 + 1; i < 256; i++)
              {
                     Temp2 += tongji[i] * i;
                     Temp3 += tongji[i];
              }
              // (大於T0的灰度均值 + 小於T0的灰度均值) / 2
              T2 = (Temp0 / Temp1 + Temp2 / Temp3) / 2;
              // 看迭代結果是否已收斂
              if (T0 == T2)
                     break;
              else
                     T0 = T2;
       }

D. 根據上一步求到的T2閾值進行圖像分割
       // 對各像素進行灰度轉換
       for (j = 0; j < height; j ++)
       {
              for (i = 0; i < wide; i ++)
              {
                     // 讀取像素
                     unsigned char temp = *((unsigned char *)p_data + wide * j + i);
                     // 判斷像素灰度值是否超出范圍
                     if (temp < T0)
                            temp = 0;
                     else
                            temp = 255;
                     // 回寫處理完的像素
                     *((unsigned char *)p_data + wide * j + i) = temp;
              }
       }





//////////////////////////////////////////////////////////////////////////////////////////////////////////
偽代碼2
C. 找到灰度圖中最大灰度iMaxGrayValue和最小灰度iMinGrayValue (代碼略)

D.iNewThreshold
iNewThreshold = (iMaxGrayValue + iMinGrayValue) / 2;

C. 迭代了求出閾值
       //迭代求最佳閾值
       iNewThreshold = (iMinGrayValue + iMaxGrayValue)/2;
       iThreshold = 0;
      
       for(iIterationTimes = 0; iThreshold != iNewThreshold && iIterationTimes < 100;iIterationTimes ++)
       {
              iThreshold = iNewThreshold;
              lP1 =0;
              lP2 =0;
              lS1 = 0;
              lS2 = 0;
              //求兩個區域的灰度平均值
              for (i = iMinGrayValue;i < iThreshold;i++)
              {
                     lP1 += lHistogram[i]*i;
                     lS1 += lHistogram[i];
              }
              iMean1GrayValue = (unsigned char)(lP1 / lS1);
              for (i = iThreshold+1;i < iMaxGrayValue;i++)
              {
                     lP2 += lHistogram[i]*i;
                     lS2 += lHistogram[i];
              }
              iMean2GrayValue = (unsigned char)(lP2 / lS2);
              iNewThreshold =  (iMean1GrayValue + iMean2GrayValue)/2;
       }
// 這裡限制的迭代次數不大於100,考慮到效率吧。

D. 根據上一步求到的iNewThreshold閾值進行圖像分割
       //根據閾值將圖像二值化
       for (i = 0;i < lHeight ;i++)
       {
                     for(j = 0;j < lWidth ;j++)
                     {
                            // 指向源圖像倒數第j行,第i個象素的指針               
                            lpSrc = (char *)lpDIBBits + lLineBytes *i + j;
             
                            // 指向目標圖像倒數第j行,第i個象素的指針                   
                            lpDst = (char *)lpNewDIBBits + lLineBytes *i + j;

                            pixel = (unsigned char)*lpSrc;
                           
                            if(pixel <= iThreshold)
                            {
                                   *lpDst = (unsigned char)0;
                            }
                            else
                            {
                                   *lpDst = (unsigned char)255;
                            }
                     }
       }


5. 自適應閾值圖像分割

     在許多情況下,物體和背景的對比度在圖象中不是各處一樣的,這時很難用統一的一個閾值將物體與背景分開。這時可以根據圖象的局部特征分別采用不同的閾值進行分割。實際處理時,需要按照具體問題將圖象分成若干子區域分別選擇閾值,或者動態地根據一定的鄰域范圍選擇每點處的閾值,進行圖象分割。

1). 大津法(OTSU)
最大類間方差法是由日本學者大津於1979年提出的,是一種自適應的閾值確定的方法,又叫大津
,簡稱OTSU它是按圖像的灰度特性,將圖像分成背景和目標2部分。背景和目標之間的類間方差
越大,說明構成圖像的2部分的差別越大,當部分目標錯分為背景或部分背景錯分為目標都會導致2
分差別變小。因此,使類間方差最大的分割意味著錯分概率最小。

對於圖像I(x,y),前景(即目標)和背景的分割閾值記作T, 屬於前景的像素點數佔整幅圖像的比例記為ω0,其平均灰度μ0;背景像素點數佔整幅圖像的比例為ω1,其平均灰度為μ1。圖像的總平均灰度記為μ,類間方差記為g

假設圖像的背景較暗,並且圖像的大小為M×N,
圖像中像素的灰度值小於閾值T的像素個數記作N0,像素灰度大於閾值T的像素個數記作N1,則有:
      ω0 = N0/ M×N        (1)
      ω1 = N1/ M×N        (2)
      N0 + N1 = M×N       (3)
      ω0 + ω1 = 1          (4)
      μω0 * μ0 + ω1 * μ1   (5)
      g = ω0 (μ0 -μ) ^ 2 + ω1 (μ1 - μ)^2    (6)
將式(5)代入式(6),得到等價公式:
g = ω0 ω1 (μ0 - μ1) ^ 2    (7)
采用遍歷的方法得到使類間方差最大的閾值T,即為所求。

Otus算法使用的是聚類的思想,即把圖像的灰度數按灰度級分成2個部分,使2個部分的之間的灰度值差異最大,每個部分之內的灰度差異最小的,找到這樣的一個灰度級t劃分。通過方差的計算實現,即方差最小的值對應的t即是理想的劃分。

偽代碼1)
     FLOAT result;
    
     int cnt0;
     int cnt1;
     FLOAT max=0.0;
     for (thre = 1; thre < 255; thre++)
     {
         cnt0=0;
         cnt1=0;
         pixeltotalC0=0.0;
         pixeltotalC1=0.0;
         // 計算背景與目標的像素數各是多少
         // 計算背景與目標的像素值總和各是多少
         for (i=0; i
         {
              for (j=0; j
              {
                   if (ImageSrc[i][j] <= thre)
                   {
                       cnt0++;
                       pixeltotalC0 += ImageSrc[i][j];
                   }
                   else
                   {
                       cnt1++;
                       pixeltotalC1 += ImageSrc[i][j];
                      
                   }
              }
         }
         cnt0=cnt0;
          cnt1=cnt1;

         rateC0 = 1.0 * cnt0 / (lHeight * lWidth); // 計算背景的面積比例
         rateC1 = 1 - rateC0;                           // 計算目標的面積比例

         // 計算背景平均灰度
         if (cnt0 != 0)
         {
              pixelaverC0 = pixeltotalC0 / cnt0;       
         }
         else
         {
              pixelaverC0 = 0;
         }

         // 計算目標平均灰度
         if (cnt1 !=0)
         {
              pixelaverC1 = pixeltotalC1 / cnt1;
         }
         else
         {
              pixelaverC1 = 0;
         }

         // 計算類間方差
         result = rateC0 * rateC1 * (pixelaverC0 - pixelaverC1) * (pixelaverC0 - pixelaverC1);

         // 找到最大的類間方差就找到最佳的閾值了
         if(result > max)
         {
              max = result;
              threbest = thre;
         }

     }
    
     // 進行二值化
     for (i=0; i
     {
         for (j=0; j
         {
              if (ImageSrc[i][j] >= threbest)
              {
                   ImageDst[i][j] = (unsigned char)255;
              }
              else
              {
                   ImageDst[i][j] = (unsigned char)0;
              }
         }
      }
明顯這段代碼的效率會低一點,它是怎對每一個灰度值在圖像中的所有點進行計算。
看下面代碼,效率會高一點。

偽代碼2
http://fcwhx007.bokewu.com/blog173376.htm
/*
OTSU 算法可以說是自適應計算單閾值(用來轉換灰度圖像為二值圖像)的簡單高效方法。下面的代碼最早由 Ryan Dibble提供,此後經過多人Joerg.Schulenburg, R.Z.Liu等修改,補正。
算法對輸入的灰度圖像的直方圖進行分析,將直方圖分成兩個部分,使得兩部分之間的距離最大。劃分點就是求得的閾值。
parameter: *image --- buffer for image
rows, cols --- size of image
x0, y0, dx, dy --- region of vector used for computing threshold
vvv --- debug option, is 0, no debug information outputed
*/
/*======================================================================*/
/* OTSU global thresholding routine */
/* takes a 2D unsigned char array pointer, number of rows, and */
/* number of cols in the array. returns the value of the threshold */
/*======================================================================*/
// 這段代碼可以針對圖像的區域
int otsu (unsigned char *image, int rows, int cols, int x0, int y0, int dx, int dy)
{
     unsigned char *np; // 圖像指針
     int thresholdValue=1; // 閾值
     int ihist[256]; // 圖像直方圖,個點

     int i, j, k; // various counters
     int n, n1, n2, gmin, gmax;
     double m1, m2, sum, csum, fmax, sb;

     // 對直方圖置零...
     memset(ihist, 0, sizeof(ihist));

     gmin=255; gmax=0;
     // 生成直方圖
     // 求出最大像素值和最小像素值
     // 求出圖像中各個灰度值的個數存於數組ihist
     for (i = y0 + 1; i < y0 + dy - 1; i++)
     {
         np = &image[i*cols+x0+1];
         for (j = x0 + 1; j < x0 + dx - 1; j++)
          {
              ihist[*np]++;
              if(*np > gmax) gmax=*np;
              if(*np < gmin) gmin=*np;
              np++; /* next pixel */
         }
     }

     // set up everything
     sum = csum = 0.0;
     n = 0;

     // 不知道這個有什麼用?
     for (k = 0; k <= 255; k++)
     {
         // 圖像的總灰度值
         sum += (double) k * (double) ihist[k]; /* x*f(x) 質量矩*/
         // 總像素點數不就是等於寬*高嗎
         n += ihist[k]; /* f(x) 質量*/
     }

     if (!n)
     {
         // if n has no value, there is problems...
         fprintf (stderr, "NOT NORMAL thresholdValue = 160/n";
         return (160);
     }

     // do the otsu global thresholding method
     fmax = -1.0;
     n1 = 0;
     for (k = 0; k < 255; k++)
     {
         n1 += ihist[k];
         if (!n1)
         {
              continue;
         }
         n2 = n - n1;
         if (n2 == 0)
         {
              break;
         }
         csum += (double) k *ihist[k];
         m1 = csum / n1;
         m2 = (sum - csum) / n2;
         sb = (double) n1 *(double) n2 *(m1 - m2) * (m1 - m2);
         /* bbg: note: can be optimized. */
         if (sb > fmax)
         {
              fmax = sb;
              thresholdValue = k;
         }
     }
     // at this point we have our thresholding value
     return(thresholdValue);
}

2). 均值法
思想很簡單,就是把圖像分成m*n塊子圖,求取每一塊子圖的灰度均值(就是所有像素灰度值之和除以像素點的數量),這個均值就是閾值了。
這種方法明顯不比大津法好,因為均值法和大津法都是從圖像整體來考慮閾值的,但是大津法找了一個類間方差最大值來求出最佳閾值的;這兩種方法子圖越多應該分割效果會好一點,但效率可能會變慢。

 
  

6. 最佳閾值

     閾值的選擇需要根據具體問題來確定,一般通過實驗來確定。對於給定的圖象,可以通過分析直方圖的方法確定最佳的閾值,例如當直方圖明顯呈現雙峰情況時,可以選擇兩個峰值的中點作為最佳閾值。
所謂最佳閾值就是根據一定的方法(例如雙峰法),找出圖像中目標與背景的分割最佳閾值就是了。方法多種多樣,對不同的圖片可以有不同的方法(因為不同的圖片有不同的特點)。方法是多種多樣的,答案是豐富多彩的。

沒有留言: