目录

  • 图像形态学操作:
  • 会用到的相关API:
  • 1.形态学操作中的结构元素(structure element)
  • 2.增强可视化效果的一个API:
  • 3.进行形态学操作的API:
  • 4.转化为二值图像:
  • 图像形态学的基本操作:
  • 1.膨胀:
  • 2.腐蚀:
  • 3.开操作(Open)
  • 4.闭操作(Close)
  • 5.形态学梯度:
  • 6.顶帽:(TopHat)
  • 7.黑帽:(BlackHat)
  • 形态学操作应用1:提取水平或竖直线(背景黑色):
  • 形态学操作应用2:去除干扰背景(想到了验证码):


图像形态学操作:

区别于生物学上的形态学,图像形态学:即数学形态学(mathematical Morphology),是图像处理中应用最为广泛的技术之一,主要用于从图像中提取对表达和描绘区域形状有意义的图像分量,使后续的识别工作能够抓住目标对象最为本质〈最具区分能力-most discriminative)的形状特征,如边界和连通区域等。同时像细化、像素化和修剪毛刺等技术也常应用于图像的预处理和后处理中,成为图像增强技术的有力补充。

形态学基本思想是用具有一定形态的结构元素去度量和提取图像中的对应形状以达到对图像分析和识别的目的形态学图像处理的数学基础和所用语言是集合论。形态学图像处理的应用可以简化图像数据,保持它们基本的形状特性,并除去不相干的结构形态学

图像处理的基本运算有4个:膨胀腐蚀开操作闭操作

形态学的主要应用:边界提取、区域填充、连通分量的提取、凸壳、细化、粗化等

参考1参考2

会用到的相关API:
1.形态学操作中的结构元素(structure element)

设有两幅图像A, S
若A是被处理的对象, 而S是用来处理A的, 则称S为结构元素。结构元素通常都是一些比较小的图像(结构元素的形状可以是任意的)。A与S的关系类似于滤波中图像和模板的关系.

相关API:

定义一个结构元素cv::getStructuringElement():
cv::Mat B=cv::getStructuringElement(int shape, cv::Size ksize, cv::Point anchor = cv::Point(-1, -1))

其中:

shape :参数形状元素形状,可以是cv::MorphShapes之一常用的形状宏定义:(矩形,圆,直线,菱形,十字等)

opencv 异型_二值图像


ksize :结构化元素的尺寸n×m,nm必须为奇数。

anchor :参数元素内的锚定位置。默认值(-1,-1)(一般在中心)
(注意,只有十字对称的形状取决于锚位置。在其他情况下,锚只是调节形态学操作结果的移动量)。

2.增强可视化效果的一个API:

cv::createTrackbar() (在指定窗口上增加滑动条实时调整参数)
int cv::createTrackbar(const cv::String &trackbarname, const cv::String &winname, int *value, int count, cv::TrackbarCallback onChange = (cv::TrackbarCallback)0, void *userdata = (void *)0)

其中:

trackbarname :创建的滑动条的名称。

winname:添加滑动条的指定窗口的名称。(因此在使用这个API之前窗口就必须存在)(使用namedWindow创建)
value:指向整数变量的可选指针,其值反映初始滑块的位置(对应参数大小)

count :计数滑块的最大值。最小值总是0。

onChange:回调函数:指向每次滑块更改位置时要调用的函数的指针。此函数的原型应为void Foo(int,void*),其中第一个参数是传入滑块值,第二个参数是用户数据(请参阅下一个参数)。如果回调是空指针,则不调用回调,只更新传入滑块值。

User data:按原样传递给回调的用户数据。它可以用来处理trackbar事件而不使用全局变量。

3.进行形态学操作的API:

cv::morphologyEx() void cv::morphologyEx(cv::InputArray src, cv::OutputArray dst, int op, cv::InputArray kernel, cv::Point anchor = cv::Point(-1, -1), int iterations = 1, int borderType = 0, const cv::Scalar &borderValue = morphologyDefaultBorderValue())

其中:

src:源图像

dst:目标图像的大小和类型与源图像相同。

op :形态操作类型的参数,请参见cv::MorphTypes

(常见的有腐蚀,膨胀,开,闭,形态学梯度,顶帽,黑帽等)

opencv 异型_二值化_02


kernel:内核结构元素

anchor:锚定位置与内核。

iterations :迭代应用侵蚀和膨胀的次数。

4.转化为二值图像:

自适应阈值的二值化操作:adaptiveThreshold()

函数原型:
void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int bolckSize, double C)

输入和输出必须为单通道图像
maxValue:预设满足条件的最大值,一般为255,(最小值为0)。

adaptiveMethod:指定自适应阈值算法。可选择ADAPTIVE_THRESH_MEAN_CADAPTIVE_THRESH_GAUSSIAN_C(具体见下面的解释)

thresholdType:指定阈值类型。可选择THRESH_BINARY或者THRESH_BINARY_INV两种。(即二进制阈值或反二进制阈值)。

bolckSize:核大小,用来计算区域阈值,为大于1的奇数

C:常数,对阈值进行调整。

其中:
adaptiveMethod:自适应阈值化计算的大概过程是为每一个像素点单独计算阈值,即每个像素点的阈值都是不同的,将该像素点周围B*B区域内的像素加权平均,然后减去一个常数C,从而得到该点的阈值,从而再判断该点的二值取向,B由参数bolckSize 指定,常数C由参数C 指定

根据这一原理,自适应阈值的二值化操作也可以用来很好的提取图像特征(边缘的像素值和邻域差值一般较大,容易大于或小于阈值

ADAPTIVE_THRESH_MEAN_C,为局部邻域块的平均值,该算法是先求出块中的均值,再减去常数C。
ADAPTIVE_THRESH_GAUSSIAN_C,为局部邻域块的高斯加权和。该算法是在区域中(x, y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算,再减去常数C。

举个例子:如果使用平均值方法,平均值mean为190,差值delta(即常数C)为30。那么灰度小于160的像素为0,大于等于160的像素为255
如果是反向二值化,则小于160为255,大于160为0。
参考

二值化效果:

opencv 异型_二值图像_03

代码实现:

void My_adaptiveThreshold(Mat &src, Mat &dst, int Size, int C) //转化为二值图像 
{
	if(src.channels()!=1) 
	cvtColor(src,dst,CV_BGR2GRAY);
	//注:若背景为亮色,对象稍暗。则习惯先将原图像取反再进行二值化,防止出现中心极小值缝隙,直接使用”~“取反
	adaptiveThreshold(~dst,src,255,ADAPTIVE_THRESH_MEAN_C,THRESH_BINARY,Size,-5);
}

注:若背景为亮色,对象稍暗。则习惯先将原图像取反再进行二值化,防止出现中心极小值缝隙,直接使用‘~’取反

图像形态学的基本操作:
1.膨胀:

跟卷积操作类似:假设有图像A和结构元素B,结构元素B在A上面移动,定义B中心点为锚点(待处理的像素),计算B覆盖下A的最大像素值用来替换锚点的像素。(又叫极大值滤波)

公式:

opencv 异型_二值化_04


左:膨胀操作前 右: 膨胀操作后(可理解为白色背景向字母j膨胀)

opencv 异型_API_05


相关API:(也可用morphologyEx )

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

其中:
kernel:用于膨胀的结构元素内核;如果elemenat=Mat(),则默认为3 x 3矩形。可以使用getStructuringElement创建内核。
anchor :元素内锚定的锚定位置;默认值(-1,-1)表示锚定在元件中心。
iterations = 1 :膨胀迭代的次数,默认为1
#int borderType = 0 :像素外推方法,参见cv::BorderTypes
#borderValue :如果是常量边框,则为边框值

2.腐蚀:

腐蚀跟膨胀操作的过程类似,唯一不同的是以最小值替换锚点重叠下图像的像素值 (又叫极小值滤波)

公式:

opencv 异型_opencv 异型_06


左:膨胀操作前 右: 膨胀操作后(可理解为字母j向白色背景腐蚀)

opencv 异型_opencv 异型_07


相关API:(也可用morphologyEx )

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

参数和void dilate类同。效果:

opencv 异型_二值图像_08


左:膨胀,右:腐蚀

根据它们的原理,腐蚀与膨胀还可用于处理椒盐噪声(非线性滤波)

膨胀与腐蚀基本代码实现:

创建滑块条:

void My_Morphology_Opration(int param) //使用滑动条调用形态学基本操作
{
    int ElemSize = 0; //内核初值 
    switch(param){
        case 0:
            namedWindow("dilate",CV_WINDOW_AUTOSIZE);
            dilate(0,0);
            createTrackbar("kernel size:","dilate",&ElemSize,30,dilate);
            break;
        case 1:
            namedWindow("erode",CV_WINDOW_AUTOSIZE);
            erode(0,0);
            createTrackbar("kernel size:","erode",&ElemSize,30,erode);
            break;
    } 
}

膨胀:

void dilate(int ElemSize,void*) //膨胀(回调函数)
{
    int s=ElemSize*2+3;//将内核尺寸限制为奇数
    Mat kernel = getStructuringElement(MORPH_RECT,Size(s,s),Point(-1,-1));
    dilate(SRC, DST, kernel, Point(-1,1),1);
    imshow("dilate",DST);
}

腐蚀:

void erode(int ElemSize,void*) //腐蚀(回调函数)
{
	int s=ElemSize*2+3;//将内核尺寸限制为奇数
	Mat kernel = getStructuringElement(MORPH_RECT,Size(s,s),Point(-1,-1));
	erode(SRC, DST, kernel, Point(-1,1),1);
	imshow("erode",DST);
}
3.开操作(Open)

本质上是先进行一次腐蚀操作,再进行一次膨胀操作。
(即取最小值滤波再取最大值滤波)先进行的腐蚀操作将所有对象的尺度减小,(一些较小的对象即被吞噬)再将对象的尺度增大(将未被吞噬的对象恢复到原来尺度,吞噬的对象即成为了背景)。

作用:若背景是黑色,对象是白色,开操作可以去除较小的对象

代码:

void open(int ElemSize,void*) //开操作(回调函数)
{
    int s=ElemSize*2+3;//将内核尺寸限制为奇数
    Mat kernel = getStructuringElement(MORPH_RECT,Size(s,s),Point(-1,-1));
    morphologyEx(SRC, DST, CV_MOP_OPEN, kernel); //形态学操作API
    imshow("open",DST);
}
4.闭操作(Close)

本质上是先进行一次膨胀操作,再进行一次腐蚀操作。
(即取最大值滤波再取最小值滤波)先进行的膨胀操作将所有对象的尺度增大,(对象中的一些极小值的点即被对象吞噬)再将对象的尺度减小(将对象恢复到原来尺度)。

作用:若背景是黑色,对象是白色,闭操作可以填充小的洞

代码:

void close(int ElemSize,void*) //闭操作(回调函数)
{
    int s=ElemSize*2+3;//将内核尺寸限制为奇数
    Mat kernel = getStructuringElement(MORPH_RECT,Size(s,s),Point(-1,-1));
    morphologyEx(SRC, DST, CV_MOP_CLOSE, kernel); //形态学操作API
    imshow("close",DST);
}

实现效果:

原图:

opencv 异型_二值图像_09


opencv 异型_opencv 异型_10


闭操作: 去除图像中的极小值点,如二值图像的孔洞

开操作: 去除图像中的极大值点

5.形态学梯度:

通常所说形态学梯度(Morphological Gradient)
是膨胀图像与腐蚀图像的之差得到的图像,也是基本梯度。
(个人暂且通俗的理解为图像的一阶导)

梯度用于刻画目标边界或边缘位于图像灰度级剧烈变化的区域,形态学梯度根据膨胀或者腐蚀与原图作差组合来实现增强结构元素领域中像素的强度,突出高亮区域的外围。计算图像的形态学梯度是形态学重要操作,常常将膨胀和腐蚀基础操作组合起来一起使用实现一些复杂的图像形态学梯度

可以计算的梯度常见如下四种:

基本梯度:基本梯度是用膨胀后的图像减去腐蚀后的图像得到差值图像,称为梯度图像。也是OpenCV中仅支持的计算形态学梯度的方法。

内部梯度:是用原图像减去腐蚀之后的图像得到差值图像

外部梯度:是用图像膨胀之后再减去原来的图像得到的差值图像

方向梯度(偏导):方向梯度是使用X方向与Y方向的直线作为结构元素之后得到图像梯度,用X方向直线做结构元素分别膨胀与腐蚀之后得到图像求差值之后称为X方向梯度,用Y方向直线做结构元素分别膨胀与腐蚀之后得到图像求差值之后称为Y方向梯度。

特点
形态学梯度操作的输出图像像素值是在对应结构元素而非局部过渡区域所定义的领域中灰度级强度变化的最大值。
对二值图像进行形态学操作可以将团块(blob)的边缘突出出来,可以用形态学梯度来保留物体的边缘轮廓。

参考博客

基本梯度:(膨胀-腐蚀)的实现效果:(原图在上面)

opencv 异型_opencv 异型_11


形态学梯度提取图像的边缘特征:

opencv 异型_二值化_12


代码:

void gradient(int ElemSize,void*) //形态学梯度(基本梯度)(回调函数)
{
    int s=ElemSize*2+3;//将内核尺寸限制为奇数
    Mat kernel = getStructuringElement(MORPH_RECT,Size(s,s),Point(-1,-1));
    morphologyEx(SRC, DST, CV_MOP_GRADIENT, kernel,Point(-1,1),1); //形态学操作API
    imshow("gradient",DST);}
6.顶帽:(TopHat)

原图像与开操作之间的差值图像

因为开运算到来的结果是放大了裂痕或者局部低亮度的区域,因此,从原图中减去运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。

顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。

7.黑帽:(BlackHat)

闭操作与原图像之间的差值图像

黑帽运算之后的效果图突出了与原图像轮廓周围的区域更暗的区域,且这一操作和选择的核大小相关。所以黑帽运算用来分离比邻近点暗一些的斑块。
参考

实现效果:

opencv 异型_二值化_13


注:形态学操作运用在二值图像上会有比较显著的效果!如果要处理彩色图像,最好先转化为二值图像

代码:

顶帽:

void Top_Hat(int ElemSize,void*) //顶帽(回调函数)
{
    int s=ElemSize*2+3;//将内核尺寸限制为奇数
    Mat kernel = getStructuringElement(MORPH_RECT,Size(s,s),Point(-1,-1));
    morphologyEx(SRC, DST, CV_MOP_TOPHAT, kernel); //形态学操作API
    imshow("Top_Hat",DST);
}

黑帽:

void Black_Hat(int ElemSize,void*) //黑帽(回调函数)
{
    int s=ElemSize*2+3;//将内核尺寸限制为奇数
    Mat kernel = getStructuringElement(MORPH_RECT,Size(s,s),Point(-1,-1));
    morphologyEx(SRC, DST, CV_MOP_BLACKHAT, kernel); //形态学操作API
    imshow("Black_Hat",DST);
}
形态学操作应用1:提取水平或竖直线(背景黑色):

利用结构元素的形状,将结构元素调整为水平或竖直的线型,再利用开操作,这样一来水平的结构元素就可过滤竖直的线,竖直的结构元素就可过滤水平的线。

原图在上面:

opencv 异型_opencv 异型_14


代码实现:

void VH_Open(Mat &src, Mat &dst)  //提取二值图像中水平或竖直的线
{
    //提取水平线(结构元素为一水平线)
    //Mat hkernel = getStructuringElement(MORPH_RECT,Size(src.cols/50,1),Point(-1,-1));
    //提取竖直线(结构元素为一竖直线)
    Mat vkernel = getStructuringElement(MORPH_RECT,Size(1,src.rows/50),Point(-1,-1));

    morphologyEx(src, dst, CV_MOP_OPEN, vkernel);  //形态学操作API
    imshow("VH_OPEN",dst);
}
形态学操作应用2:去除干扰背景(想到了验证码):

原图:

opencv 异型_opencv 异型_15


首先将图片进行二值化处理(背景为黑):

再进行开操作。去除背景中的白线和白点:

opencv 异型_二值图像_16


最后对图像取反:

opencv 异型_二值图像_17

代码实现:

void My_adaptiveThreshold(Mat &src, Mat &dst, int Size, int C) //转化为二值图像 
{
    if(src.channels()!=1) cvtColor(src,dst,CV_BGR2GRAY);
    //注:若背景为亮色,对象稍暗。则习惯先将原图像取反再进行二值化,防止出现中心极小值缝隙,直接使用”~“取反
    adaptiveThreshold(~dst,src,255,ADAPTIVE_THRESH_MEAN_C,THRESH_BINARY,Size,-5);
} 


void opration(Mat src, Mat dst) //去除背景干扰
{
    My_adaptiveThreshold(src,dst, 15, -20); //转化为二值图像 
    Show_Pic(src); //显示图片
    
    Mat kernel1 = getStructuringElement(MORPH_RECT,Size(5,7),Point(-1,-1));
    Mat kernel2 = getStructuringElement(MORPH_RECT,Size(9,9),Point(-1,-1)); 
    
    erode(src, dst, kernel1, Point(-1,1),1);
    dilate(dst, src, kernel2, Point(-1,1),1);
    
    imshow("dilate",~src);}

学习记录博客,欢迎交流,若有理解不到位的地方请多多谅解