#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
//调整图像显示尺寸函数
void Myshow(std::string name, const cv::Mat& cv_src)
{
cv::namedWindow(name, 0);
/*void nameWindow(const string& winname,int flags = WINDOW_AUTOSIZE)
参数1:新建的窗口的名称。自己随便取。
参数2:窗口的标识,一般默认为WINDOW_AUTOSIZE*/
int max_rows = 400;
int max_cols = 400;
cv::resizeWindow(name, cv::Size(max_cols, max_rows));
cv::imshow(name, cv_src);
}
int main()
{
//读入图像
cv::Mat image = cv::imread("../测试图片/气泡.jpg");
//判断是否读取成功
if (image.empty())
{
std::cout << "图片为空" << std::endl;
return -1;
}
Myshow("原图", image);
//克隆彩色图片
cv::Mat dst = image.clone();
//转为灰度图像
if (image.channels() > 1)
{
cv::cvtColor(image, image, cv::COLOR_BGR2GRAY);
}
cv::Mat segmentation;
//自适应阈值二值化
double t1 = (double)cv::getTickCount();//计时
cv::adaptiveThreshold(image, segmentation, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 25, 10);
/*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的计算方法是计算出领域的平均值再减去第七个参数double C的值。
ADAPTIVE_THRESH_GAUSSIAN_C的计算方法是计算出领域的高斯均值再减去第七个参数double C的值。
int thresholdType:这是阈值类型,只有两个取值,分别为 THRESH_BINARY(标准的二值化阈值法)和THRESH_BINARY_INV(反向二值化)
int blockSize:adaptiveThreshold的计算单位是像素的邻域块,这是局部邻域大小,3、5、7等。
double C:这个参数实际上是一个偏移值调整量,用均值和高斯计算阈值后,再减或加这个值就是最终阈值。*/
t1 = (double)cv::getTickCount() - t1;
double time1 = (t1 * 1000.) / ((double)cv::getTickFrequency());
std::cout << "自适应阈值二值化用时 = " << time1 << " ms. " << std::endl << std::endl;
Myshow("自适应阈值二值化", segmentation);
//形态学处理
//获取结构元素
cv::Mat morphologyKernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3), cv::Point(-1, -1));//返回一个矩形
/*Mat getStructuringElement(int shape, Size esize, Point anchor = Point(-1, -1));
这个函数的第一个参数表示内核的形状,有三种形状可以选择。矩形:MORPH_RECT;交叉形:MORPH_CROSS;椭圆形:MORPH_ELLIPSE;
第二和第三个参数分别是内核的尺寸以及锚点的位置。一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得
getStructuringElement函数的返回值 : 对于锚点的位置,有默认值Point( - 1, -1),表示锚点位于中心点。element形状唯一依赖锚点位置,其他情况下,锚点只是影响了形态学运算结果的偏移。*/
cv::Mat form;
//开闭操作
double t2 = (double)cv::getTickCount();//计时
morphologyEx(segmentation, form, cv::MORPH_OPEN, morphologyKernel, cv::Point(-1, -1), 1, cv::BORDER_REPLICATE);
/*void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor = Point(-1, -1), intiterations = 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)
MORPH_GRADIENT - 形态学梯度(Morphological gradient)
MORPH_TOPHAT - “顶帽”(“Top hat”)
MORPH_BLACKHAT - “黑帽”(“Black hat“)
第四个参数,InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。关于getStructuringElement我们上篇文章中讲过了,这里为了大家参阅方便,再写一遍:
其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一 :
矩形: MORPH_RECT
交叉形 : MORPH_CROSS
椭圆形 : MORPH_ELLIPSE
而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。
我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1, -1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。
第五个参数,Point类型的anchor,锚的位置,其有默认值( - 1, - 1),表示锚位于中心。
第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
第八个参数,const Scalar & 类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。*/
t2 = (double)cv::getTickCount() - t2;
double time2 = (t2 * 1000.) / ((double)cv::getTickFrequency());
std::cout << "开闭操作用时 = " << time2 << " ms. " << std::endl << std::endl;
Myshow("形态学", form);
//绘制轮廓
//轮廓检测所需参数,具体看findContours的参数解释
std::vector< std::vector<cv::Point> > contours;//点的容器的容器
std::vector<cv::Vec4i> hierarchy;//Vec4i是一个包含4个int数据类型的结构体
//轮廓检测
double t3 = (double)cv::getTickCount();//计时
findContours(form.clone(), contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE, cv::Point(-3, 0));//CV_RETR_TREE
/*findContours(InputOutputArray image, OutputArrayOfArrays contours,OutputArray hierarchy, int mode,int method, Point offset = Point());
第一个参数:image,单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;
第二个参数:contours,定义为“vector<vector<Point>> contours”,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。
第三个参数:hierarchy,定义为“vector<Vec4i> hierarchy”,先来看一下Vec4i的定义:typedef Vec<int, 4> Vec4i;Vec4i是Vec<int, 4>的别名,定义了一个“向量内每一个元素包含了4个int型变量”的向量。所以从定义上看,hierarchy也是一个向量,向量内每个元素保存了一个包含4个int整型的数组。向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~hierarchy[i][3]的相应位被设置为默认值 - 1。
第四个参数:int型的mode,定义轮廓的检索模式:
取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
取值二:CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为 - 1,具体下文会讲到
取值三:CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
第五个参数:int型的method,定义轮廓的近似方法:
取值一:CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内
取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留
取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh - Chinl chain 近似算法
第六个参数:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且Point还可以是负值!*/
t3 = (double)cv::getTickCount() - t3;
double time3 = (t3 * 1000.) / ((double)cv::getTickFrequency());
std::cout << "轮
评论0