基於OpenCV的圖像分割處理!

圖像閾值化分割是一種傳統的最經常使用的圖像分割方法,因其實現簡單、計算量小、性能較穩定而成爲圖像分割中最基本和應用最普遍的分割技術。它特別適用於目標和背景佔據不一樣灰度級範圍的圖像。它不只能夠極大的壓縮數據量,並且也大大簡化了分析和處理步驟,所以在不少狀況下,是進行圖像分析、特徵提取與模式識別以前的必要的圖像預處理過程。python

閾值處理是指剔除圖像內像素值高於閾值或者低於閾值得像素點。例如,設定閾值爲127,將圖像內全部像素值大於127的像素點的值設爲255;將圖像內全部像素值小於127的像素點的值設爲0。ios

圖像閾值化的目的是要按照灰度級,對像素集合進行一個劃分,獲得的每一個子集造成一個與現實景物相對應的區域,各個區域內部具備一致的屬性,而相鄰區域不具備這種一致屬性。這樣的劃分能夠經過從灰度級出發選取一個或多個閾值來實現。c++

學習目標

  • 瞭解閾值分割基本概念算法

  • 理解最大類間方差法(大津法)、自適應閾值分割的原理app

  • 掌握OpenCV框架下上述閾值分割算法API的使用框架

算法理論介紹

閾值處理

threshold函數

OpenCV使用threshold函數實現閾值化處理。ide


double cv::threshold ( InputArray src,                      OutputArray dst,                      double thresh,                      double maxval,                      int type )

參數函數

  • src — 原圖像,8或32位浮點類型的Mat。性能

  • dst —  輸出圖像,與原始圖像具備相同大小和類型。學習

  • thresh —  要設定的閾值

  • maxval   — 當type爲THRESH_BINARY或者THRESH_BINARY_INV時,設定的最大值

  • type — 閾值分割的類型

    • THRESH_BINARY;二值化閾值處理:灰度值大於閾值的點,將其灰度值設定爲最大值,灰度值小於或等於閾值的點,將其灰度值設定爲0

    • THRESH_BINARY_INV;反二值化閾值處理:灰度值大於閾值的點,將其灰度值設定爲0,灰度值小於或等於閾值的點,將其灰度值設定爲最大值

    • THRESH_TRUNC;截斷閾值化處理:灰度值大於閾值的點,將其灰度值設定爲閾值,灰度值小於或等於閾值的點,其灰度值保持不變

    • THRESH_TOZERO;低閾值零處理:灰度值大於閾值的點,其灰度值保持不變,灰度值小於或等於閾值的點,將其灰度值設定爲0

    • THRESH_TOZERO_INV;高閾值零處理:灰度值大於閾值的點,將其灰度值設定爲0,灰度值小於或等於閾值的點,其灰度值保持不變

以下表:

OTSU(大津法)

使用threshold進行閾值處理時,須要自定義一個閾值,並以此閾值做爲圖像閾值處理的依據 。一般狀況下對於色彩均衡的圖像,直接將閾值設爲127便可,但有時圖像灰度級的分佈是不均衡的,若是此時還將閾值設爲127,那麼閾值處理的結果就是失敗的。因此須要找出圖像的最佳的分割閾值。OTSU就是得到最佳閾值的方法。OTSU(大津法)是一種肯定圖像二值化分割閾值的算法,由日本學者大津於1979年提出。從大津法的原理上來說,該方法又稱做最大類間方差法,由於按照大津法求得的閾值進行圖像二值化分割後,前景與背景圖像的類間方差最大。它被認爲是圖像分割中閾值選取的最佳算法,計算簡單,不受圖像亮度和對比度的影響,所以在數字圖像處理上獲得了普遍的應用。它是按圖像的灰度特性,將圖像分紅背景和前景兩部分。因方差是灰度分佈均勻性的一種度量,背景和前景之間的類間方差越大,說明構成圖像的兩部分的差異越大,當部分前景錯分爲背景或部分背景錯分爲前景都會致使兩部分差異變小。所以,使類間方差最大的分割意味着錯分機率最小。OTSU 是求圖像全局閾值的最佳方法,適用於大部分須要求圖像全局閾值的場合。缺點:對圖像噪聲敏感;只能針對單一目標分割;當圖像中的目標與背景的面積相差很大時,表現爲直方圖沒有明顯的雙峯,或者兩個峯的大小相差很大,分割效果不佳,或者目標與背景的灰度有較大的重疊時也不能準確的將目標與背景分開。致使這種現象出現的緣由是該方法忽略了圖像的空間信息,同時該方法將圖像的灰度分佈做爲分割圖像的依據,於是對噪聲也至關敏感。因此,在實際應用中,老是將其與其餘方法結合起來使用。

圖像直方圖

效果:

圖像直方圖:

效果:


OTSU求閾值過程:

假設圖像的像素個數爲M×N。假設存在閾值T將圖像全部像素分爲兩類C1(像素值小於T)和C2(像素值大於T)。假設圖片背景較暗,則C1類爲背景,C2類爲前景。像素被分爲C1和C2類的機率分別爲p一、p2。圖像中屬於C1類的像素個數記做N1,其平均灰度;屬於C2類的的像素個數記做N2,其平均灰度爲。圖像的總平均灰度記爲,類間方差記爲所以有以下關係式:

帶入類間方差公式,化簡,能夠獲得:

L爲灰度級數,爲灰度級爲的像素點數

小於或等於灰度級K的累加均值爲:

因此,

類間方差公式能夠化爲:

圖片

得使最大的灰度級K,就是OTSU的閾值。OTSU方法會遍歷全部灰度級,找到最佳閾值

自適應閾值處理

前面介紹了OTSU算法,但這算法還屬於全局閾值法,即整張圖片只有一個閾值。因此對於某些光照不均的圖像,這種方法沒法獲得清晰有效的閾值分割結果圖像,以下圖:

圖片

顯然,這樣的閾值處理結果不是咱們想要的,因此須要使用變化的閾值對圖像進行分割,這種技術稱爲自適應閾值處理方式。它的思想不是計算全局圖像的閾值,而是根據圖像不一樣區域亮度分佈,計算其局部閾值,因此對於圖像不一樣區域,可以自適應計算不一樣的閾值,所以被稱爲自適應閾值法。

肯定局部閾值的方法:計算每一個像素點周圍臨近區域的加權平均值得到閾值,並使用該閾值對該像素點進行處理。

adaptiveThreshold函數

OpenCV提供了adaptiveThreshold函數實現自適應閾值處理。


void adaptiveThreshold(InputArray src, OutputArray dst,                      double maxValue,                      int adaptiveMethod,                      int thresholdType,                      int blockSize, double C)

參數:

  • src — 原圖像,8或32位浮點類型的Mat。必須爲單通道灰度圖像。

  • dst — 輸出圖像,與原始圖像具備相同大小和類型。

  • maxValue — 像素最大值

  • adaptiveMethod — 自適應方法,只有兩種:THRESH_BINARY 和THRESH_BINARY_INV。

  • thresholdType — 閾值計算方式,有兩種:

    • ADAPTIVE_THRESH_MEAN_C:計算方法是計算出領域內的像素平均值再減去C的值

    • ADAPTIVE_THRESH_GAUSSIAN_C:計算方法是計算出領域內像素的高斯均值再減去C的值

  • blockSize — 表示一個像素在計算閾值時使用的鄰域尺寸,一般爲三、五、7。

  • C — 常數,用均值或高斯計算閾值後,再減去C就是最終閾值。

基於OpenCV的實現

c++實現

1. 閾值處理


#include <opencv2/opencv.hpp>#include <iostream>using namespace cv;using namespace std;int main(){//載入圖像Mat img = imread("D:\\yt\\picture\\threshold\\s.jpg");if (img.empty()){cout << "Error: Could not load image" << endl;return 0;}Mat gray;cvtColor(img, gray, COLOR_BGR2GRAY);//先轉爲灰度圖Mat dst1,dst2,dst3,dst4,dst5;threshold(gray, dst1, 127, 255, THRESH_BINARY);//二值化閾值處理threshold(gray, dst2, 127, 255, THRESH_BINARY_INV);//反二值化閾值處理threshold(gray, dst3, 127, 255, THRESH_TRUNC);//截斷閾值化處理threshold(gray, dst4, 127, 255, THRESH_TOZERO_INV);//超閾值零處理threshold(gray, dst5, 127, 255, THRESH_TOZERO);//低閾值零處理//顯示圖像imshow("gray_image", gray);imshow("THRESH_BINARY", dst1);imshow("THRESH_BINARY_INV", dst2);imshow("THRESH_TRUNC", dst3);imshow("THRESH_TOZERO_INV", dst4);imshow("THRESH_TOZERO", dst5);waitKey(0);return 0;}

效果

二值化閾值處理:

反二值化閾值處理:

截斷閾值化處理:

超閾值零處理:

低閾值零處理:

2. OTSU處理

在OpenCV中,設定參數type爲「THRESH_OTSU」便可實現OTSU方式的閾值分割。且設定閾值thresh爲0。


#include <opencv2/opencv.hpp>#include <iostream>using namespace cv;using namespace std;int main(){//載入圖像Mat img = imread("D:\\yt\\picture\\threshold\\s.jpg");if (img.empty()){cout << "Error: Could not load image" << endl;return 0;}Mat gray;cvtColor(img, gray, COLOR_BGR2GRAY);//先轉爲灰度圖Mat dst;threshold(gray, dst, 0, 255, THRESH_OTSU);//OTSU
//顯示圖像imshow("gray_image", gray);imshow("THRESH_OTSU", dst);waitKey(0);return 0;}

效果

3. 自適應閾值處理


#include <opencv2/opencv.hpp>#include <iostream>using namespace cv;using namespace std;int main(){//載入圖像Mat img = imread("D:\\yt\\picture\\threshold\\1.jpg");if (img.empty()){cout << "Error: Could not load image" << endl;return 0;}Mat gray;cvtColor(img, gray, COLOR_BGR2GRAY);//先轉爲灰度圖Mat dst;adaptiveThreshold(gray, dst, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 7, 10);//建立窗口,WINDOW_NORMAL使窗口能夠自由調節大小namedWindow("gray_image",WINDOW_NORMAL);namedWindow("adaptiveThreshold", WINDOW_NORMAL);//顯示圖像imshow("gray_image", gray);imshow("adaptiveThreshold", dst);
waitKey(0);return 0;}

效果

進階實現(根據原理本身實現)

實現示例(c++)

1. OTSU處理


#include <iostream>#include <opencv2/core.hpp>#include <opencv2/highgui.hpp>#include <opencv2/imgproc.hpp>
int Otsu(cv::Mat& src, cv::Mat& dst, int thresh){const int Grayscale = 256;int graynum[Grayscale] = { 0 };int r = src.rows;int c = src.cols;for (int i = 0; i < r; ++i){const uchar* ptr = src.ptr<uchar>(i);for (int j = 0; j < c; ++j){        //直方圖統計graynum[ptr[j]]++;}}
  double P[Grayscale] = { 0 };  double PK[Grayscale] = { 0 };double MK[Grayscale] = { 0 };double srcpixnum = r*c, sumtmpPK = 0, sumtmpMK = 0;for (int i = 0; i < Grayscale; ++i){P[i] = graynum[i] / srcpixnum;   //每一個灰度級出現的機率PK[i] = sumtmpPK + P[i];         //機率累計和sumtmpPK = PK[i];MK[i] = sumtmpMK + i*P[i];       //灰度級的累加均值                                                                                                                                                                                                                                                                                                                                                                                                        sumtmpMK = MK[i];}
//計算類間方差double Var=0;for (int k = 0; k < Grayscale; ++k){if ((MK[Grayscale-1] * PK[k] - MK[k])*(MK[Grayscale-1] * PK[k] - MK[k]) / (PK[k] * (1 - PK[k])) > Var){Var = (MK[Grayscale-1] * PK[k] - MK[k])*(MK[Grayscale-1] * PK[k] - MK[k]) / (PK[k] * (1 - PK[k]));thresh = k;}}
//閾值處理src.copyTo(dst);for (int i = 0; i < r; ++i){    uchar* ptr = dst.ptr<uchar>(i);for (int j = 0; j < c; ++j){if (ptr[j]> thresh)ptr[j] = 255;elseptr[j] = 0;}}return thresh;}

int main(){cv::Mat src = cv::imread("D:\\yt\\picture\\threshold\\1.jpg");if (src.empty()){return -1;}if (src.channels() > 1)cv::cvtColor(src, src, CV_RGB2GRAY);
cv::Mat dst;int thresh=0;double t2 = (double)cv::getTickCount();thresh=Otsu(src , dst, thresh); //Otsu
cv::namedWindow("src", CV_WINDOW_NORMAL);cv::imshow("src", src);cv::namedWindow("dst", CV_WINDOW_NORMAL);cv::imshow("dst", dst);cv::waitKey(0);}

2. 自適應閾值處理


#include <iostream>#include <opencv2/core.hpp>#include <opencv2/highgui.hpp>#include <opencv2/imgproc.hpp>
enum adaptiveMethod{meanFilter,gaaussianFilter,medianFilter};
void AdaptiveThreshold(cv::Mat& src, cv::Mat& dst, double Maxval, int Subsize, double c, adaptiveMethod method = meanFilter){
if (src.channels() > 1)cv::cvtColor(src, src, CV_RGB2GRAY);
cv::Mat smooth;switch (method){case  meanFilter:cv::blur(src, smooth, cv::Size(Subsize, Subsize));  //均值濾波break;case gaaussianFilter:cv::GaussianBlur(src, smooth, cv::Size(Subsize, Subsize),0,0); //高斯濾波break;case medianFilter:cv::medianBlur(src, smooth, Subsize);   //中值濾波break;default:break;}
smooth = smooth - c;
//閾值處理src.copyTo(dst);for (int r = 0; r < src.rows;++r){const uchar* srcptr = src.ptr<uchar>(r);const uchar* smoothptr = smooth.ptr<uchar>(r);uchar* dstptr = dst.ptr<uchar>(r);for (int c = 0; c < src.cols; ++c){if (srcptr[c]>smoothptr[c]){dstptr[c] = Maxval;}elsedstptr[c] = 0;}}
}
int main(){cv::Mat src = cv::imread("D:\\yt\\picture\\threshold\\1.jpg");if (src.empty()){return -1;}if (src.channels() > 1)cv::cvtColor(src, src, CV_RGB2GRAY);
cv::Mat dst;AdaptiveThreshold(src, dst, 255, 21, 10, meanFilter);
cv::namedWindow("src", CV_WINDOW_NORMAL);cv::imshow("src", src);cv::namedWindow("dst", CV_WINDOW_NORMAL);cv::imshow("dst", dst);cv::waitKey(0);}

python實現

與c++不一樣,python中函數cv2.threshold的返回值有兩個


retval,dst = cv2.threshold(src,thresh,maxval,type)
  • retval — 返回的閾值

  • dst — 閾值處理的輸出圖像

1. 二值化閾值處理


import cv2if __name__ == "__main__":   img = cv2.imread('D:/yt/picture/threshold/lena.bmp')   t,dst1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)   # 顯示圖像   cv2.imshow("origin image", img)   cv2.imshow("THRESH_BINARY", dst1)
  # 保存圖像   cv2.imwrite("D:/yt/picture/threshold/THRESH_BINARY.bmp", dst1)   cv2.waitKey(0)   cv2.destroyAllWindows()

效果

2. 反二值化閾值處理


import cv2if __name__ == "__main__":   img = cv2.imread('D:/yt/picture/threshold/lena.bmp')   t,dst1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)    # 顯示圖像   cv2.imshow("origin image", img)   cv2.imshow("THRESH_BINARY_INV", dst1)    # 保存圖像   cv2.imwrite("D:/yt/picture/threshold/THRESH_BINARY_INV.bmp", dst1)   cv2.waitKey(0)   cv2.destroyAllWindows()

效果

3. 截斷閾值化處理


import cv2if __name__ == "__main__":   img = cv2.imread('D:/yt/picture/threshold/lena.bmp')   t,dst1 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
  # 顯示圖像   cv2.imshow("origin image", img)   cv2.imshow("THRESH_TRUNC", dst1)
  # 保存圖像   cv2.imwrite("D:/yt/picture/threshold/THRESH_TRUNC.bmp", dst1)   cv2.waitKey(0)   cv2.destroyAllWindows()

效果

4. 超閾值零處理


import cv2if __name__ == "__main__":   img = cv2.imread('D:/yt/picture/threshold/lena.bmp')   t,dst1 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)
  # 顯示圖像   cv2.imshow("origin image", img)   cv2.imshow("THRESH_TOZERO_INV", dst1)
  # 保存圖像   cv2.imwrite("D:/yt/picture/threshold/THRESH_TOZERO_INV.bmp", dst1)   cv2.waitKey(0)   cv2.destroyAllWindows()

效果

5. 低閾值零處理


import cv2if __name__ == "__main__":   img = cv2.imread('D:/yt/picture/threshold/lena.bmp')   t,dst1 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
  # 顯示圖像   cv2.imshow("origin image", img)   cv2.imshow("THRESH_TOZERO", dst1)
  # 保存圖像   cv2.imwrite("D:/yt/picture/threshold/THRESH_TOZERO.bmp", dst1)   cv2.waitKey(0)   cv2.destroyAllWindows()

效果

6. OTSU處理

在OpenCV中,給參數type多傳遞一個參數「THRESH_OTSU」便可實現OTSU方式的閾值分割。且設定閾值thresh爲0。


import cv2import numpy as npif __name__ == "__main__":   img = cv2.imread('D:/yt/picture/threshold/tiffany.bmp')   img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#原圖像不是灰度圖,必須先轉換爲灰度圖   #普通二值化閾值處理   t1, dst1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)   #採用OTSU的處理   t2, dst2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  # 建立窗口   cv2.namedWindow("origin image",cv2.WINDOW_NORMAL)#cv2.WINDOW_NORMAL使窗口大小可調整   cv2.namedWindow("THRESH_TOZERO",cv2.WINDOW_NORMAL)   cv2.namedWindow("THRESH_OTSU",cv2.WINDOW_NORMAL)   # 顯示圖像   cv2.imshow("origin image", img)   cv2.imshow("THRESH_TOZERO", dst1)   cv2.imshow("THRESH_OTSU",dst2)
  cv2.waitKey(0)   cv2.destroyAllWindows()

效果:

7. 自適應閾值處理


import cv2import numpy as npif __name__ == "__main__":   img = cv2.imread('D:/yt/picture/threshold/computer.jpg')   img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#原圖像不是灰度圖,必須先轉換爲灰度圖   #普通二值化閾值處理   t1, dst1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)   #自適應閾值處理,採用均值計算閾值   dst2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,5,3)   #自適應閾值處理,採用高斯均值計算閾值   dst3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,5,3)
  # 建立窗口   cv2.namedWindow("origin image",cv2.WINDOW_NORMAL)#cv2.WINDOW_NORMAL使窗口大小可調整   cv2.namedWindow("THRESH_BINARY",cv2.WINDOW_NORMAL)   cv2.namedWindow("MEAN_C",cv2.WINDOW_NORMAL)   cv2.namedWindow("GAUSSIAN_C", cv2.WINDOW_NORMAL)   # 顯示圖像   cv2.imshow("origin image", img)   cv2.imshow("THRESH_BINARY", dst1)   cv2.imshow("MEAN_C",dst2)   cv2.imshow("GAUSSIAN_C", dst3)
  cv2.waitKey(0)   cv2.destroyAllWindows()

效果

圖片

相關文章
相關標籤/搜索