形態學操作——開閉運算、頂帽底(黑)帽變換

膨脹和腐蝕運算的問題:

邊緣形狀發生了變化,膨脹發生了擴張,腐蝕發生了收縮
目標物體變形,對識別時的特徵提取會造成影響

解決方法:

開操作:

gongshi
B對A的開操作就是先B對A腐蝕,緊接着用B對結果進行膨脹
效果
先腐蝕再膨脹的結果並不是恢復原狀,而是會消除黏連部分,同時不影響其他部分的形狀.
平滑物體的輪廓,斷開較窄的狹頸並消除較細的突出。
效果:
效果

閉操作:

公式
B對A的閉操作就是先B對A膨脹,緊接着用B對結果進行腐蝕
效果
先膨脹再腐蝕的結果並不是恢復原狀,而是填充小的裂縫、孔隙,且不影響形狀.
平滑物體的輪廓,彌合較窄的間斷和細小的溝壑,消除小的孔洞,填充輪廓中的斷痕。
效果:
效果
更加詳細的過程如下:
詳細過程
除了開閉運算,黑帽頂帽運算也是形態學操作比較重要的操作。

頂帽變換:原圖-灰度開運算(灰度腐蝕+灰度膨脹)

效果:
1、保留比結構元素小的部分
2、保留比周圍環境亮的像素

底帽變換:灰度閉運算(灰度膨脹+灰度腐蝕)-原圖 效果:

1、保留比結構元素小的部分
2、保留比周圍環境暗的像素

解釋:
頂帽處理使背景變得趨於一致了,前景和背景的對比度加深了
這是因爲,在頂帽處理中當結構元素比前景目標物的大小
大的時候,腐蝕的步驟會選擇周圍比較暗的值代替比較亮的值
所以背景變暗了,同時前景被去掉了。再用原圖和結果相減,就可以把背景去掉,把開運算中去掉的前景給保留下來
1
2

代碼實現

#include <opencv2/opencv.hpp>
#include <iostream>
#include "windows.h"
#include <stdio.h>

using namespace cv;
using namespace std;

//*--------------------------【練習】形態學操作morphology大練習------------------------------------*/
//請調整滾動條觀察圖像效果
//按鍵操作說明 :
//鍵盤按鍵【空格SPACE】- 在矩形、橢圓、十字形結構元素中循環
//鍵盤按鍵【1】- 使用橢圓結構元素
//鍵盤按鍵【2】- 使用矩形結構元素
//鍵盤按鍵【3】- 使用十字形結構元素
Mat g_secImage, g_dstImage;	//原圖和效果圖
int g_nElementShape = MORPH_RECT;	//初始化元素結構形狀

//變量接受的Trackbar值
int  g_nMaxIterationMun = 10;
int g_nOpenCloseNum = 0;
int g_nErodeDilateNum = 0;
int g_nTopBlackHatNum = 0;

//*--------------------------【全局函數聲明】-----------------------------------*/
static void on_OpenClose(int, void*);		//回調函數
static void on_ErodeDilate(int, void*);		//回調函數
static void on_TopBlakHat(int, void*);		//回調函數
void ShowHelpText1();
void ShowHelpText2();
int main()
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN);		//字體爲綠色
	//載入原圖
	g_secImage = imread("D:\\opencv_picture_test\\形態學操作\\黑白.jpg");
	//判斷圖像是否加載成功
	if (g_secImage.empty())
	{
		cout << "圖像加載失敗!" << endl;
		return -1;
	}
	else
		cout << "圖像加載成功!" << endl << endl;
	//顯示原圖像
	namedWindow("原圖像", WINDOW_NORMAL);     //定義窗口顯示屬性
	imshow("原圖像", g_secImage);
	//創建三個窗口
	namedWindow("【開運算/閉運算】", WINDOW_NORMAL);    
	namedWindow("【腐蝕/膨脹】", WINDOW_NORMAL);
	namedWindow("【頂帽/黑帽】", WINDOW_NORMAL);

	//參數賦值
	g_nOpenCloseNum = 9;
	g_nErodeDilateNum =9;
	g_nTopBlackHatNum =2;

	//分別爲三個窗口建立滑動條
	createTrackbar("迭代值", "【開運算/閉運算】", &g_nOpenCloseNum, g_nMaxIterationMun*2+1,on_OpenClose);
	createTrackbar("迭代值", "【腐蝕/膨脹】", &g_nErodeDilateNum, g_nMaxIterationMun * 2 + 1, on_ErodeDilate);
	createTrackbar("迭代值", "【頂帽/黑帽】", &g_nTopBlackHatNum, g_nMaxIterationMun * 2 + 1, on_TopBlakHat);
	//show幫助信息
	ShowHelpText1();
	//輪詢獲取按鍵信息
	while (1)
	{
		int c;
		//執行回調函數
		on_OpenClose(g_nOpenCloseNum,0);
		on_ErodeDilate(g_nErodeDilateNum,0);
		on_TopBlakHat(g_nTopBlackHatNum,0);

		//獲取按鍵
		c = waitKey(0);
		//按下按鍵ESC程序退出
		if ((char)c == 27)
		{
			break;
		}
		//按鍵1,使用橢圓結構元素
		if ((char)c == 49)
		{
			g_nElementShape = MORPH_ELLIPSE;
		}
		//按鍵2,使用矩形結構元素
		else if ((char)c == 50)
		{
			g_nElementShape = MORPH_RECT;
		}
		//按鍵3,使用十字形結構元素
		else if ((char)c == 51)
		{
			g_nElementShape = MORPH_CROSS;
		}
		//按鍵空格,換一種結構元素
		else if ((char)c == ' ')
		{
			g_nElementShape = (g_nElementShape+1)%3;
		}
	}
}
//*--------------------------【回調函數】-----------------------------------*/

static void on_OpenClose(int, void*)
{
	//偏移量定義
	int offset = g_nOpenCloseNum - g_nMaxIterationMun;	//偏移量
	int Abs_offset = offset > 0 ? offset : -offset;	//偏移量的絕對值
	//自定義核
	Mat element = getStructuringElement(g_nElementShape,Size(Abs_offset*2+1, Abs_offset*2+1),Point(Abs_offset, Abs_offset));	//返回的是內核矩陣
	//進行操作
	if (offset < 0)
	{
		morphologyEx(g_secImage, g_dstImage, MORPH_OPEN, element);
	}
	else
	{
		morphologyEx(g_secImage, g_dstImage, MORPH_CLOSE, element);
	}
	//顯示效果圖
	imshow("【開運算/閉運算】", g_dstImage);
}
static void on_ErodeDilate(int, void*)
{
	//偏移量定義
	int offset = g_nErodeDilateNum - g_nMaxIterationMun;	//偏移量
	int Abs_offset = offset > 0 ? offset : -offset;	//偏移量的絕對值
	//自定義核
	Mat element = getStructuringElement(g_nElementShape, Size(Abs_offset * 2 + 1, Abs_offset * 2 + 1), Point(Abs_offset, Abs_offset));	//返回的是內核矩陣
	//進行操作
	if (offset < 0)
	{
		morphologyEx(g_secImage, g_dstImage, MORPH_ERODE, element);
	}
	else
	{
		morphologyEx(g_secImage, g_dstImage, MORPH_DILATE, element);
	}
	//顯示效果圖
	imshow("【腐蝕/膨脹】", g_dstImage);
}
static void on_TopBlakHat(int, void*)
{
	//偏移量定義
	int offset = g_nTopBlackHatNum - g_nMaxIterationMun;	//偏移量
	int Abs_offset = offset > 0 ? offset : -offset;	//偏移量的絕對值
	//自定義核
	Mat element = getStructuringElement(g_nElementShape, Size(Abs_offset * 2 + 1, Abs_offset * 2 + 1), Point(Abs_offset, Abs_offset));	//返回的是內核矩陣
	//進行操作
	if (offset < 0)
	{
		morphologyEx(g_secImage, g_dstImage, MORPH_TOPHAT, element);
	}
	else
	{
		morphologyEx(g_secImage, g_dstImage, MORPH_BLACKHAT, element);
	}
	//顯示效果圖
	imshow("【頂帽/黑帽】", g_dstImage);
}

//-----------------------------------【ShowHelpText( )函數】-----------------------------
//          描述:輸出一些幫助信息
	//關於morphologyEx參數的問題:
	//MORPH_BLACKHAT:黑帽運算
	//MORPH_TOPHAT:頂帽運算
	//MORPH_CLOSE:閉運算
	//MORPH_OPEN:開運算
	//MORPH_GRADIENT:形態學梯度
	//MORPH_ERODE:腐蝕運算
	//MORPH_DILATE:膨脹運算
	//關於getStructuringElement參數的問題:
	//MORPH_RECT:矩形內核
	//MORPH_CROSS:交叉型內核
	//MORPH_ELLIPSE:橢圓形矩陣
//請調整滾動條觀察圖像效果
//按鍵操作說明 :
//鍵盤按鍵【空格SPACE】- 在矩形、橢圓、十字形結構元素中循環
//鍵盤按鍵【1】- 使用橢圓結構元素
//鍵盤按鍵【2】- 使用矩形結構元素
//鍵盤按鍵【3】- 使用十字形結構元素
//----------------------------------------------------------------------------------------------
void ShowHelpText1()
{
	//輸出一些幫助信息
	printf("\n\n\n請調整滾動條觀察圖像效果\n");
	printf("\n\n\t按鍵操作說明\n");
	printf("\n\n\t鍵盤按鍵【空格SPACE】- 在矩形、橢圓、十字形結構元素中循環\n");
	printf("\n\n\t鍵盤按鍵【1】- 使用橢圓結構元素\n");
	printf("\n\n\t鍵盤按鍵【2】- 使用矩形結構元素\n");
	printf("\n\n\t鍵盤按鍵【3】- 使用十字形結構元素\n");
}
void ShowHelpText2()
{
	//輸出一些幫助信息
	printf("\n\n\n\morphologyEx 參數有以下幾種類型\n");
	printf("\n\n\tMORPH_BLACKHAT:黑帽運算\n");
	printf("\n\n\tMORPH_TOPHAT:頂帽運算\n");
	printf("\n\n\tMORPH_CLOSE:閉運算\n");
	printf("\n\n\tMORPH_OPEN:開運算\n");
	printf("\n\n\t//MORPH_GRADIENT:形態學梯度\n");
	printf("\n\n\tMORPH_ERODE:腐蝕運算\n");
	printf("\n\n\tMORPH_DILATE:膨脹運算\n");
	printf("\n\n\n\getStructuringElement參數的問題:\n");
	printf("\n\n\tMORPH_RECT:矩形內核\n");
	printf("\n\n\tMORPH_CROSS:交叉型內核\n");
	printf("\n\n\tMORPH_ELLIPSE:橢圓形矩陣\n");
}

代碼實現效果:
321

代碼解釋

調用opencv庫函數morphologyEx,通過調整參數就可以實現不同的形態學操作。
其中回調函數中要求得變量:偏移量(當前迭代值和預置最大迭代值之差),用它的正負來判斷是進行哪種對偶操作。用它的絕對值來決定矩陣核的大小(邊長爲奇數),以及結構元素錨點的位置。
另外需要注意:十字形的element形狀唯一依賴於錨點的位置。
而在其他情況下,錨點只是影響形態學運算結果的偏移。

相關鏈接:[https://www.cnblogs.com/zsb517/archive/2012/06/08/2541193.html(https://www.cnblogs.com/zsb517/archive/2012/06/08/2541193.html)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章