OpenCV基本函数与原理(二)

1形态学操作

图像形态学操作 :基于形状的一系列图像处理操作的合集,主要是基于集合论基础上的形态学数学。
形态学的应用:消除噪声、边界提取、区域填充、连通分量提取、凸壳、细化、粗化等;分割出独立的图像元素,或者图像中相邻的元素;求取图像中明显的极大值区域和极小值区域;求取图像梯度。
形态学图像处理的基本运算有:膨胀、腐蚀、开操作和闭操作,击中与击不中变换,TOP-HAT变换,黑帽变换等 。

在讲各种形态学操作之前,先来看看结构元素:
膨胀和腐蚀操作的核心内容是结构元素。(后面的开闭运算等重要的也是结构元素的设计,一个合适的结构元素的设计可以带来很好的处理效果)一般来说结构元素是由元素为1或者0的矩阵组成。结构元素为1的区域定义了图像的领域,领域内的像素在进行膨胀和腐蚀等形态学操作时要进行考虑。
一般来说,二维或者平面结构的结构元素要比处理的图像小得多。结构元素的中心像素,即结构元素的原点,与输入图像中感兴趣的像素值(即要处理的像素值)相对应。三维的结构元素使用0和1来定义x-y平面中结构元素的范围,使用高度值定义第三维。
相应的API:

Mat kernel = getStructuringElement(int shape,Size ksize,Point anchor);
//结构元素的定义:形状 (MORPH_RECT \MORPH_CROSS(交叉形) \MORPH_ELLIPSE);结构元素大小;锚点 默认是Point(-1, -1)意思就是中心像素,也可以自己指定

1.1膨胀

跟卷积操作类似,假设有图像A和结构元素B,结构元素B在A上面移动,其中B定义其中心为锚点,计算B覆盖下A的最大像素值用来替换锚点的像素,其中B作为结构体可以是任意形状。下图的右边为膨胀之后的图像。
在这里插入图片描述
相应的API:

void dilate( const Mat& src, Mat& dst, const Mat& element,Point anchor=Point(-1,-1), int iterations=1,int borderType=BORDER_CONSTANT,const Scalar& borderValue=morphologyDefaultBorderValue() );

参数解释:
src:原图像。
dst:目标图像。
element:腐蚀操作的内核。 如果不指定,默认为一个简单的矩阵。否则,我们就要明确指定它的形状,可以使用函数getStructuringElement().
anchor:默认为Point(-1,-1),内核中心点。省略时为默认值。
iterations:腐蚀次数。省略时为默认值1。
borderType:推断边缘类型,具体参见borderInterpolate函数。默认为BORDER_DEFAULT,省略时为默认值。
borderValue:边缘值,具体可参createMorphoogyFilter函数。可省略。

1.2 腐蚀

腐蚀跟膨胀操作的过程类似,唯一不同的是以最小值替换锚点重叠下图像的像素值。下图的右边为腐蚀之后的图像。
在这里插入图片描述
相应的API:

void erode( const Mat& src, Mat& dst, const Mat& element,Point anchor=Point(-1,-1), int iterations=1,int borderType=BORDER_CONSTANT,
const Scalar& borderValue=morphologyDefaultBorderValue() );

参数和膨胀一样。

1.3 开运算

先腐蚀后膨胀,等价于:
在这里插入图片描述
作用:用来消除图像中细小对象,在纤细点处分离物体和平滑较大物体的边界而有不明显改变其面积和形状。
由于开操作、闭操作、顶帽和黑帽共用一个API,所以见文后分析。

1.4闭操作

先膨胀后腐蚀,等价于:
在这里插入图片描述
作用:用来填充目标内部的细小孔洞(fill hole),将断开的邻近目标连接,在不明显改变物体面积和形状的情况下平滑其边界。

1.5形态学梯度、顶帽、黑帽

1)形态学梯度(Morphological gradient)
形态学梯度操作能描述图像亮度变化的剧烈程度;当我们想要突出高亮区域的外围时,则可以选用形态学梯度来突出边缘,可以保留物体的边缘轮廓。
在这里插入图片描述
2)顶帽
顶帽是原图与原图的开运算的差值图像。开运算放大了裂缝或者局部低亮度的区域,所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。TopHat运算一般用来分离比邻近点亮一些的斑块,可以使用这个运算提取背景。
3)黑帽
黑帽是闭运算结果与原图的差值图像。黑帽运算的结果突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。

开操作、闭操作、顶帽和黑帽的API:

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

第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
MORPH_OPEN – 开运算(Opening operation):先腐蚀后膨胀,去掉小对象
MORPH_CLOSE – 闭运算(Closing operation):先膨胀后腐蚀,可以填充一些小的空洞(fill hole)
MORPH_GRADIENT - 形态学梯度(Morphological gradient):膨胀后的图减去腐蚀后的图(设置恰当的参数可以得到目标的大致边缘)
MORPH_TOPHAT - “顶帽”(“Top hat”):原图像与开操作之间的差值图像(可以用来观察开运算除去了哪些小目标)
MORPH_BLACKHAT - “黑帽”(“Black hat“):闭操作图像与源图像的差值图像(可以观察闭运算的效果)
第四个参数:结构元素即形态学运算的内核
第五个参数,Point类型的anchor,锚的位置,其有默认值( - 1, - 1),表示锚位于中心。
第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档。

综合示例:

#include <opencv2/opencv.hpp> 
#include <iostream> 
using namespace cv;

Mat src, dst;
char OUTPUT_WIN[] = "output image";
int element_size = 3;
int max_size = 21;
void CallBack_Demo(int, void*);
int main(int argc, char** argv) {
	
	src = imread("D:/vcprojects/images/test1.png");
	if (!src.data) {
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	imshow("input image", src);

	namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
	createTrackbar("Element Size :", OUTPUT_WIN, &element_size, max_size, CallBack_Demo);  //其中最中要的是 callback 函数功能。如果设置为NULL就是说只有值update,但是不会调用callback的函数。
	CallBack_Demo(0, 0);

	waitKey(0);
	return 0;
}

void CallBack_Demo(int, void*) {
	int s = element_size * 2 + 1;
	Mat structureElement = getStructuringElement(MORPH_RECT, Size(s, s), Point(-1, -1));
	
	// dilate(src, dst, structureElement, Point(-1, -1), 1); //腐蚀操作
	
	//erode(src, dst, structureElement); //膨胀操作
	
	morphologyEx(src, dst, CV_MOP_BLACKHAT, structureElement); //黑帽操作
	imshow(output_title, dst);
	imshow(OUTPUT_WIN, dst);
	return;
}

2形态学操作应用–提取水平与垂直线

原理:图像形态学操作时候,可以通过自定义的结构元素实现结构元素对输入图像一些对象敏感、另外一些对象不敏感,这样就会让敏感的对象改变而不敏感的对象保留输出。通过使用两个最基本的形态学操作 – 膨胀与腐蚀,使用不同的结构元素实现对输入图像的操作、得到想要的结果。

  • 膨胀,输出的像素值是结构元素覆盖下输入图像的最大像素值
  • 腐蚀,输出的像素值是结构元素覆盖下输入图像的最小像素值
    例如:图像膨胀时:在二进制图像中,如果落入内核范围内的输入图像的任何像素被设置为值1,则输出图像的相应像素也将被设置为1。后者适用于任何类型的图像(例如灰度,bgr等)。
    在这里插入图片描述
    图像腐蚀时:在二进制图像中,如果落入内核范围内的输入图像的任何像素被设置为值0,则输出图像的相应像素也将被设置为0。后者适用于任何类型的图像(例如灰度,bgr等)。
    在这里插入图片描述
    结构元素:
    如上所述,通常在任何形态学操作中,用于探测输入图像的结构元素是最重要的部分。
    一个结构化元素是一个由只有0和1组成的矩阵,可以有任意的任意形状和大小。通常比正在处理的图像小得多,而值为1的像素定义邻域。称为原点的结构元素的中心像素标识感兴趣的像素 - 正在处理的像素。
    结构元素可以具有许多常见的形状,例如线,菱形,圆盘,周期线以及圆和尺寸。您通常选择与要在输入图像中处理/提取的对象相同的大小和形状的结构元素。例如,要在图像中查找行,请创建一个线性结构元素,您将在后面看到。

提取步骤:
输入图像彩色图像 imread
转换为灰度图像 – cvtColor
转换为二值图像 – adaptiveThreshold
定义结构元素
开操作 (腐蚀+膨胀)提取 水平与垂直线

综合示例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
int main(int argc, char** argv) {
	Mat src, dst;
	src = imread("D:/vcprojects/images/chars.png");
	if (!src.data) {
		printf("could not load image...\n");
		return -1;
	}

	char INPUT_WIN[] = "input image";
	char OUTPUT_WIN[] = "result image";
	namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
	imshow(INPUT_WIN, src);

	Mat gray_src;
	cvtColor(src, gray_src, CV_BGR2GRAY);
	imshow("gray image", gray_src);
	
	Mat binImg;
	adaptiveThreshold(~gray_src, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
	imshow("binary image", binImg);

	// 水平结构元素
	Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
	// 垂直结构元素
	Mat vline = getStructuringElement(MORPH_RECT, Size(1, src.rows / 16), Point(-1, -1));
	// 矩形结构
	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));

	Mat temp;
	erode(binImg, temp, kernel);
	dilate(temp, dst, kernel);
	// morphologyEx(binImg, dst, CV_MOP_OPEN, vline);
	bitwise_not(dst, dst);
	//blur(dst, dst, Size(3, 3), Point(-1, -1));
	imshow("Final Result", dst);

	waitKey(0);
	return 0;
}

3图像上采样与降采样

3.1图像金字塔

我们在图像处理中常常会调整图像大小,最常见的就是放大(zoom in)和缩小(zoom out)操作。一个图像金字塔是一系列图像的组合,最底下图像尺寸最大,最顶端图像尺寸最小,从空间上往下看就像一个金字塔。
图像处理当中最常见的就是利用图像金字塔产生一系列不同分辨率图像,然后在不同尺度空间去寻找图像对应的特征,因为我们输入的图像我们不知道是什么样子,而图像金字塔可以保证图像特征存在。

3.2常见的OpenCV金字塔

高斯金字塔 – 用来对图像进行降采样;
拉普拉斯金字塔 – 用来重建一张图片根据它的上层降采样图片。

3.2.1高斯金字塔

高斯金子塔是从底向上,逐层降采样得到。
降采样之后图像大小是原图像MxN的M/2 x N/2 ,就是对原图像删除偶数行与列,即得到降采样之后上一层的图片。
高斯金子塔的生成过程分为两步:

  • 对当前层进行高斯模糊
  • 删除当前层的偶数行与列
    即可得到上一层的图像,这样上一层跟下一层相比,都只有它的1/4大小。

3.2.2高斯不同(Difference of Gaussian-DOG)

定义:就是把同一张图像在不同的参数下做高斯模糊之后的结果相减,得到的输出图像。称为高斯不同(DOG)
高斯不同是图像的内在特征,在灰度图像增强、角点检测中经常用到。

相应的API:

void pyrUp(InputArray src, OutputArray dst, const Size& dstsize=Size())

src:源图像Mat对象
dst:目标图像Mat对象
dstsize :目标图像的大小,经测试只能输入为源图像的2,如想继续缩小可在缩小的基础上进行。
borderType :边缘类型,默认不填即可。
例子:
pyrUp(Mat src, Mat dst, Size(src.cols2, src.rows2))
生成的图像是原图在宽与高各放大两倍

void pyrDown(InputArray src, OutputArray dst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT ) //降采样

src:源图像Mat对象
dst:目标图像Mat对象
dstsize :目标图像的大小,经测试只能输入为源图像的1/2,如想继续缩小可在缩小的基础上进行。
borderType :边缘类型,默认不填即可。
例子:
pyrDown(Mat src, Mat dst, Size(src.cols/2, src.rows/2))
生成的图像是原图在宽与高各缩小1/2

综合示例:

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

using namespace cv;
int main(int agrc, char** argv) {
	Mat src, dst;
	src = imread("D:/vcprojects/images/cat.jpg");
	if (!src.data) {
		printf("could not load image...");
		return -1;
	}

	char INPUT_WIN[] = "input image";
	char OUTPUT_WIN[] = "sample up";
	namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
	namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
	imshow(INPUT_WIN, src);

	// 上采样
	pyrUp(src, dst, Size(src.cols*2, src.rows * 2));
	imshow(OUTPUT_WIN, dst);

	// 降采样
	Mat s_down;
	pyrDown(src, s_down, Size(src.cols / 2, src.rows / 2));
	imshow("sample down", s_down);

	// DOG
	Mat gray_src, g1, g2, dogImg;
	cvtColor(src, gray_src, CV_BGR2GRAY);
	GaussianBlur(gray_src, g1, Size(5, 5), 0, 0);
	GaussianBlur(g1, g2, Size(5, 5), 0, 0);
	subtract(g1, g2, dogImg, Mat());

	// 归一化显示
	normalize(dogImg, dogImg, 255, 0, NORM_MINMAX);
	imshow("DOG Image", dogImg);

	waitKey(0);
	return 0;
}

4图像阈值

阈值是什么?简单点说是把图像分割的标尺。详细说,阈值是一个转换临界点,不管你的图片是什么样的彩色,它最终都会把图片当黑白图片处理,也就是说你设定了一个阈值之后,它会以此值作标准,凡是比该值大的颜色就会转换成白色,低于该值的颜色就转换成黑色,所以最后的结果是,你得到一张黑白的图片。

4.1简单阈值

当像素值高于阈值时,我们给这个像素赋予一个新值,否则给他赋予另一个值。
1)阈值二值化(threshold binary)
下方的图表示图像像素点Src(x,y)值分布情况,蓝色水平线表示阈值:
在这里插入图片描述
2)阈值反二值化(threshold binary Inverted)
下方的图表示图像像素点Src(x,y)值分布情况,蓝色水平线表示阈值:
在这里插入图片描述
3)截断(truncate)
下方的图表示图像像素点Src(x,y)值分布情况,蓝色水平线表示阈值:
在这里插入图片描述
4)阈值取零(threshold to zero)
下方的图表示图像像素点Src(x,y)值分布情况,蓝色水平线表示阈值:
在这里插入图片描述
5)阈值反取零(threshold to zero inverted)
下方的图表示图像像素点Src(x,y)值分布情况,蓝色水平线表示阈值:
在这里插入图片描述
相应的API:

threshold(InputArray src, outputArray dst,double thresh,double maxval, int type)

参数说明
src:源图像,可以为8位的灰度图,也可以为32位的彩色图像。(两者由区别)
dst:输出图像
thresh:阈值
maxval:dst图像中最大值
type:阈值类型。

4.2自适应阈值

在前面部分我们使用的是全局阈值,整幅图采用同一个数当做阈值。但这种方法并不适用于所有情况,尤其是当同一副图像上的不同部分的具有不同的亮度的。这时我们可以采用自适应阈值。
此时的阈值是根据图像上的每一个小区域计算与其对应的阈值。因此在同一副图像上的不同的区域采用不同阈值,从而我们能在亮度不同时得到更好的结果。
OpenCv 中 自适应阈值函数用到的函数为: adaptiveThreshold(),

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

参数说明
参数1:InputArray类型的src,输入图像,填单通道,单8位浮点类型Mat即可。
参数2:函数运算后的结果存放在这。即为输出图像(与输入图像同样的尺寸和类型)。
参数3:预设满足条件的最大值。
参数4:指定自适应阈值算法。可选择ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C两种。(具体见下面的解释)。
参数5:指定阈值类型。可选择THRESH_BINARY或者THRESH_BINARY_INV两种。(即二进制阈值或反二进制阈值)。
参数6:表示邻域块大小,用来计算区域阈值,一般选择为3、5、7…等。
参数7:参数C表示与算法有关的参数,它是一个从均值或加权均值提取的常数,可以是负数。(具体见下面的解释)。
对参数4与参数7内容的解释:
自适应阈值化计算大概过程是为每一个象素点单独计算的阈值,即每个像素点的阈值都是不同的,就是将该像素点周围B*B区域内的像素加权平均,然后减去一个常数C,从而得到该点的阈值。B由参数6指定,常数C由参数7指定。
ADAPTIVE_THRESH_MEAN_C,为局部邻域块的平均值,该算法是先求出块中的均值,再减去常数C。
ADAPTIVE_THRESH_GAUSSIAN_C,为局部邻域块的高斯加权和。该算法是在区域中(x, y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算,再减去常数C。

4.3Otsu’s 二值化

无论是简单阈值 ,还是自适应阈值 ,我们在处理之前都不可避免地需要考虑 阈值设置问题,设置的好坏对于最后成像起到很大的影响,但是怎样设置一最适合的阈值呢?不断尝试?
而这个就是 Ostu 二值化要做的,通过根据图像的直方图分布计算出一个阈值 ,但是对于 非双峰图像来说,这种方法得到的结果可能不太理想,Ostu 二值化 用到的函数同样是 cv2.threshold(),
在之前的简单阈值 部分中,cv2.threshold() 函数返回了两个值,第二个参数是我们最后要的图像,而第一个 rerval ,其实就是这里我们要计算的的最佳阈值,Ostu二值化 与 简单阈值 之间 一个重要的区别,就是需要多传入一个参数:cv2.THRESH_OSTU ,同时把 threshold() 函数中设定的阈值一定要设为 0;

综合示例:

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace cv;
Mat src, gray_src, dst;
int threshold_value = 127;
int threshold_max = 255;
int type_value = 2;
int type_max = 4;
const char* output_title = "binary image";
void Threshold_Demo(int, void*);
int main(int argc, char** argv) {
	src = imread("D:/vcprojects/images/test.png");
	if (!src.data) {
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	namedWindow(output_title, CV_WINDOW_AUTOSIZE);
	imshow("input image", src);
	
	createTrackbar("Threshold Value:", output_title, &threshold_value, threshold_max, Threshold_Demo);
	createTrackbar("Type Value:", output_title, &type_value, type_max, Threshold_Demo);
	Threshold_Demo(0, 0);

	waitKey(0);
	return 0;
}

void Threshold_Demo(int, void*) {
	cvtColor(src, gray_src, CV_BGR2GRAY);
	threshold(src, dst, 0, 255, THRESH_TRIANGLE | type_value);
	imshow(output_title, dst);
}
发布了29 篇原创文章 · 获赞 1 · 访问量 529

猜你喜欢

转载自blog.csdn.net/qq_45173769/article/details/104729131