2020年8月31日 星期一

OpenCV erode、dilate(形態學處理)

當使用完二值化或邊緣偵測後,畫面總會殘留一些白點雜訊,或特徵會有破洞等狀況,為了不影響後續的辨識,這時可用型態學的方式,如侵蝕或膨脹將不想要的特徵給去除。

侵蝕

void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue())

InputArray src:輸入影像
OutputArray dst:輸出影像
InputArray kernel:輸入核心
Point anchor:錨點位置
int iterations:疊代次數
nt borderType:邊界模式

膨脹

void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue())

InputArray src:輸入影像
OutputArray dst:輸出影像
InputArray kernel:輸入核心
Point anchor:錨點位置
int iterations:疊代次數
nt borderType:邊界模式

但不論是侵蝕或膨脹,其預設的kernel都是3x3,如果想定義自己的kernel,可利用下列函示

Mat getStructuringElement(int shape, Size esize, Point anchor = Point(-1, -1));
int shape:元素形狀
Size esize:建構元素大小(kernal size)
Point anchor:錨點位置


程式碼如下:
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(){

	Mat SrcImg = imread("dog.jpg", CV_LOAD_IMAGE_COLOR);
	Mat GrayImg, BinaryImg;
	Mat ErodeImg, DilateImg, UserErodeImg, UserDilateImg;

	cvtColor(SrcImg, GrayImg, CV_BGR2GRAY);

	Mat Element = getStructuringElement(MORPH_ELLIPSE, Size(7, 7));
	erode(GrayImg, ErodeImg, Mat(), Point(-1, -1), 1);
	erode(GrayImg, UserErodeImg, Element, Point(-1, -1), 1);
	dilate(GrayImg, DilateImg, Mat(), Point(-1, -1), 1);
	dilate(GrayImg, UserDilateImg, Element, Point(-1, -1), 1);

	imshow("GrayImg", GrayImg);
	imshow("ErodeImg", ErodeImg);
	imshow("UserErodeImg", UserErodeImg);
	imshow("DilateImg", DilateImg);
	imshow("UserDilateImg", UserDilateImg);

	waitKey(0);
	return 0;
}

GrayImg

ErodeImg

UserErodeImg

DilateImg

UserDilateImg

2020年8月1日 星期六

OpenCV Threshold(二值化)

為了從影像找出我們想要的特徵,一般會使用二值化(Threshold)先將前景(foreground)與背景(background)初步分離出來,以減少不必要的訊息量(雜訊),底下就來介紹一下OpenCV常用的threshold函式,我自己初分成三種類型。

第一種:基本款的二值化(依據閥值進行分類的二值化)

double cv::threshold(InputArray src,OutputArray dst,double thresh,double maxval,int type)
InputArray src:輸入的影像來源
OutputArray dst:輸出的影像來源
double thresh:判定閥值
double maxval:最大值(當像素值大於或小於判定閥值時,用此參數取代)
int type:設定二值化模式,常見有THRESH_BINARY、THRESH_BINARY_INV、THRESH_TRUNC、THRESH_TOZERO、THRESH_TOZERO_INV五種。
THRESH_BINARY:將大於閥值的灰度值設為最大值,反之設為0
THRESH_BINARY_INV:將大於閥值的灰度值設為0,反之設為最大值
THRESH_TRUNC:將大於閥值的灰度值設為閥值,反之則不變
THRESH_TOZERO:將大於閥值的灰度值不變,反之則設為0
THRESH_TOZERO_INV:將大於閥值的灰度值設為0,反之則不變

第二種:進階版的二值化(依據亮度分佈,自動定義閥值進行分類的二值化)

跟第一種幾乎一樣,差別在於設定的二值化模式不同,可選擇為THRESH_OTSU跟THRESH_TRIANGLE兩種,THRESH_OTSU比較適用在有兩個分峰值情況下使用,而THRESH_TRIANGLE則用在單一峰值比較合適。


第三種:強大版的二值化(依據區域不同,適應不同閥值進行分類的二值化)

void adaptiveThreshold(InputArray src, OutputArray dst,double maxValue,int adaptiveMethod, int thresholdType, int blockSize, double C)
InputArray src:輸入的影像來源
OutputArray dst:輸出的影像來源
double maxval:最大值(當像素值大於或小於判定閥值時,用此參數取代)
int adaptiveMethod:設定自適應模式,有ADAPTIVE_THRESH_MEAN_C(平均法)、ADAPTIVE_THRESH_GAUSSIAN_C(高斯法)兩種。
nt thresholdType:設定二值化模式,即前面提到的二值化五種模式
int blockSize:區塊大小
double C:常數

總結:

上述三種方式,我覺得第一種使用單純但無法用於環境光源變化大的地方、第二種雖然可以自動定義閥值,但使用時若影像亮度分佈沒有明顯的兩個或一個峰值,則效果可能有限,第三種則適應能力較強,處理後的效果還有點像邊緣偵測,也許可用在其他地方上。


現在就來實作一下吧,為了比較好理解基本款二值化操作,我設計了一張圖片,當中分成了五個區域,由左至右亮度值分別為(50、100、150、200、250),讓我們更容易看出不同的差異性。

程式碼如下:
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(){

	Mat SrcImg = imread("GradientImg.bmp", CV_LOAD_IMAGE_COLOR);
	Mat GrayImg, BinaryImg, BinaryInvImg, BinaryTruncImg, BinaryToZeroImg, BinaryToZeroInvImg;
	Mat OtsuImg, TriangleImg, AdaptiveThImg;

	cvtColor(SrcImg, GrayImg, CV_BGR2GRAY);
	threshold(GrayImg, BinaryImg, 150, 255, THRESH_BINARY);//將大於閥值的灰度值設為最大值,反之設為0
	threshold(GrayImg, BinaryInvImg, 150, 255, THRESH_BINARY_INV);//將大於閥值的灰度值設為0,反之設為最大值
	threshold(GrayImg, BinaryTruncImg, 190, 255, THRESH_TRUNC);//將大於閥值的灰度值設為閥值,反之則不變
	threshold(GrayImg, BinaryToZeroImg, 150, 255, THRESH_TOZERO);//將大於閥值的灰度值不變,反之則設為0
	threshold(GrayImg, BinaryToZeroInvImg, 150, 255, THRESH_TOZERO_INV);//將大於閥值的灰度值設為0,反之則不變
	threshold(GrayImg, OtsuImg, 0, 255, THRESH_OTSU);//自動設定最佳閥值,適用於有兩個峰值的情況
	threshold(GrayImg, TriangleImg, 250, 255, THRESH_TRIANGLE);//自動設定最佳閥值,適用只有一個峰值的情況
	adaptiveThreshold(GrayImg, AdaptiveThImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 5, 1);

	imshow("GradientImg", SrcImg);
	imshow("BinaryImg", BinaryImg);
	imshow("BinaryInvImg", BinaryInvImg);
	imshow("BinaryTruncImg", BinaryTruncImg);
	imshow("BinaryToZeroImg", BinaryToZeroImg);
	imshow("BinaryToZeroInvImg", BinaryToZeroInvImg);
	imshow("OtsuImg", OtsuImg);
	imshow("TriangleImg", TriangleImg);
	imshow("AdaptiveThImg", AdaptiveThImg);

	waitKey(0);
	return 0;
}


GradientImg

BinaryImg

BinaryInvImg

BinaryTruncImg

BinaryToZeroImg

BinaryToZeroInvImg

OtsuImg


TriangleImg

AdaptiveImg