膨脹和腐蝕運算的問題:
邊緣形狀發生了變化,膨脹發生了擴張,腐蝕發生了收縮
目標物體變形,對識別時的特徵提取會造成影響
解決方法:
開操作:
B對A的開操作就是先B對A腐蝕,緊接着用B對結果進行膨脹
先腐蝕再膨脹的結果並不是恢復原狀,而是會消除黏連部分,同時不影響其他部分的形狀.
平滑物體的輪廓,斷開較窄的狹頸並消除較細的突出。
效果:
閉操作:
B對A的閉操作就是先B對A膨脹,緊接着用B對結果進行腐蝕
先膨脹再腐蝕的結果並不是恢復原狀,而是填充小的裂縫、孔隙,且不影響形狀.
平滑物體的輪廓,彌合較窄的間斷和細小的溝壑,消除小的孔洞,填充輪廓中的斷痕。
效果:
更加詳細的過程如下:
除了開閉運算,黑帽頂帽運算也是形態學操作比較重要的操作。
頂帽變換:原圖-灰度開運算(灰度腐蝕+灰度膨脹)
效果:
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");
}
代碼實現效果:
代碼解釋
調用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)