关于opencv模块方面的内容,可以参考菜鸟教程
opencv图像容器:Mat类
opencv中图像的基本数据类型是Mat类,Mat类是一个矩阵类,包含了图像的所有信息,包括图像的宽、高、通道数、数据类型等。是操作图像的基础。
Mat构造函数有很多,这里不一一介绍,常用的有以下几种:
cv::Mat(); //默认构造函数,创建一个空矩阵cv::Mat(int rows, int cols, int type); //创建一个指定大小和类型的矩阵cv::Mat(int rows, int cols, int type, const cv::Scalar& s); //创建一个指定大小和类型的矩阵,并用指定的值初始化cv::Mat(const cv::Mat& m); //拷贝构造函数,创建一个与m相同的矩阵cv::Mat(const cv::Mat& m, const cv::Range& rowRange, const cv::Range& colRange); //创建一个m的子矩阵这里的拷贝构造函数是浅拷贝,即两个Mat对象共享同一块内存空间,修改其中一个对象会影响另一个对象。如果需要深拷贝,可以使用clone()函数。
cv::Mat img2 = img1.clone(); //深拷贝Mat实例化
cv::Mat img1; //空矩阵cv::Mat img2(480,640,CV_8UC3); //创建一个480*640的3通道8位无符号整型矩阵cv::Mat img3=imread("test.jpg"); //从文件中读取图像cv::Mat img4=imread("test.jpg",IMREAD_GRAYSCALE); //读取灰度图像cv::Mat img5(img4); //拷贝构造函数cv::Mat img6(img4,cv::Range(0,240),cv::Range(0,320)); //创建img4的子矩阵CV_8UC3是用来表示图像的数据类型的宏,具体含义如下:
- CV:OpenCV的命名空间
- 8U:表示8位无符号整型(U表示unsigned)
- C3:表示3个通道(C表示channel)
其格式为:
CV_<位数><数据类型><通道数>其中:
- 位数:表示每个通道的数据位数,如8、16、32等
- 数据类型:表示数据的类型,如U(无符号整型)、S(有符号整型)、F(浮点型)等
- 通道数:表示图像的通道数,如1(灰度图)、3(彩色图)等
Mat类的常用成员函数
img.rows; //图像的行数img.cols; //图像的列数img.channels(); //图像的通道数img.type(); //图像的数据类型img.size(); //图像的尺寸img.empty(); //判断图像是否为空img.at<Vec3b>(i,j); //访问图像(i,j)像素的像素值,Vec3b表示3通道8位无符号整型img.ptr<uchar>(i); //访问图像的某一行,返回指向该行的指针//这个uchar是unsigned char的缩写在opencv中,图像的像素值是按行优先存储的,即先存储第一行的所有像素的像素值,再存储第二行的所有像素值,以此类推。
所以,访问图像的像素值时,可以通过行列索引来访问(两次遍历),也可以通过指针来访问。
在使用ptr函数时,需要注意以下几点:
-
ptr函数返回的是指向指定行的指针,也就是一个一维数组,指针类型与图像(如CV_8UC3)的数据类型一致。 -
访问像素值时,需要根据图像的通道数来计算像素值在指针中的位置。指针指向的行是一个一维数组,数组的长度是图像的列数 通道数。
- 例如,对于一张3通道的图像,第j列的第channel通道的像素值在指针中的位置是j*3+channel。
-
使用
ptr函数访问像素值时要确保行索引在图像的范围内,否则会导致访问越界。
WARNING对于每一个像素的像素值,opencv的RGB颜色空间是按BGR顺序存储的,即第一个通道是蓝色,第二个通道是绿色,第三个通道是红色。
int channel = 0;cv::Mat img = cv::imread("apple.jpg");img.at<cv::Vec3b>(i,j)[channel]; //访问第i行第j列第channel通道的像素值,0表示蓝色通道,1表示绿色通道,2表示红色通道
uchar* p = img.ptr<uchar>(i); //获取第i行的指针p[j*3+channel]; //访问第i行第j列第channel通道的像素值
for(int r=0;r<img.rows;r++){ uchar* row_ptr= img.ptr<uchar>(r); //获取第r行的指针 for(int c=0;c<img.cols;c++){ row_ptr[c*3+channel]; //访问第r行第c列第channel通道的像素值 }}opencv数据容器:vec和Scalar类
Vec类
Vec类可以表示任意维度的向量,常用于表示图像的像素值,此时Vec类的每个元素都表示一个通道的值。
template<typename _Tp, int cn> class Vec;
Vec<变量类型,长度> 变量名;用[]访问Vec类的元素,索引从0开始。
cv::Vec3b pixel = img.at<cv::Vec3b>(i,j); //访问(i,j)像素的像素值,Vec3b表示3通道8位无符号整型uchar blue = pixel[0]; //蓝色通道uchar green = pixel[1]; //绿色通道uchar red = pixel[2]; //红色通道Scalar类
Scalar类是一个4维向量类,用于固定长度的数值,常用于表示颜色、像素值等。Scalar类的每个元素都是一个double类型的数值,表示一个通道的值。
cv::Scalar(double v0, double v1 = 0, double v2 = 0, double v3 = 0);对于不同的颜色空间,传入构造函数的参数意义不同;如果传入的参数不满4个,最后的参数会被自动补0。
BGR颜色空间:v0:蓝色,v1:绿色,v2:红色
cv::Scalar blue(255, 0, 0); // 蓝色cv::Scalar green(0, 255, 0); // 绿色cv::Scalar red(0, 0, 255); // 红色HSV颜色空间:v0:色调H,v1:饱和度S,v2:亮度V
cv::Scalar hsv_red(0, 255, 255); // 红色opencv基于颜色空间的图像分割
opencv中常用的颜色空间有BGR、HSV、Lab等,不同的颜色空间适用于不同的图像处理任务。基于颜色空间的图像分割是指通过颜色空间的转换和阈值分割来实现图像的分割。
颜色空间转换
opencv中常用的颜色空间转换函数是cvtColor(),该函数可以实现多种颜色空间之间的转换。常用的转换代码如下:
cv::cvtColor(输入图像, 输出图像, 颜色空间转换方式);转换方式包括:cv::COLOR_BGR2GRAY、cv::COLOR_BGR2HSV、cv::COLOR_BGR2Lab等。格式是cv::COLOR_<源颜色空间>2<目标颜色空间>
cv::Mat img = cv::imread("apple.jpg");cv::Mat img_hsv;cv::cvtColor(img, img_hsv, cv::COLOR_BGR2HSV); //BGR转HSV在HSV颜色空间中,H表示色调(红绿蓝),S表示饱和度(深红浅红),V表示亮度(明暗)。HSV颜色空间更符合人类的视觉感知,适合用于颜色分割。
任务要求识别苹果,首先需要确定苹果在HSV颜色空间中的范围。根据经验,红色的HSV范围大致为:
- H: 0-10 或 160-180
- S: 100-255
- V: 100-255
所以,可以设置两个范围来覆盖红色:
cv::Scalar lowerRed1(0, 100, 100);cv::Scalar upperRed1(10, 255, 255);cv::Scalar lowerRed2(160, 100, 100);cv::Scalar upperRed2(180, 255, 255);阈值分割:掩膜
核心概念:掩膜
掩膜是一个与原图像大小相同的二值图像,用于指示哪些区域是感兴趣的。掩膜中的白色区域(255)表示保留的区域,黑色区域(0)表示被忽略的区域。
掩膜可以用来:
-
提取目标区域,过滤背景
-
统计像素数量
-
做图像运算(如 bitwise_and, bitwise_or)
-
轮廓检测、形态学处理等
opencv中常用的阈值分割函数是inRange(),该函数可以根据指定的范围对图像进行二值化处理。参数列表的最后一个表示输出的掩膜:
cv::Mat mask;void inRange(输入图像, 颜色下界, 颜色上界, mask);//这个mask就是掩膜cv::Mat mask1, mask2;cv::inRange(img_hsv, lowerRed1, upperRed1, mask1); //生成掩膜1cv::inRange(img_hsv, lowerRed2, upperRed2, mask2); //生成掩膜2因为红色在HSV空间中有两个范围,所以需要生成两个掩膜,然后将它们合并,常见的方法是使用bitwise_or()函数,参数列表中最后一个参数表示输出的合并结果:
cv::Mat red_mask;cv::bitwise_or(mask1, mask2, red_mask); //合并掩膜现在,掩膜red_mask中白色区域表示图像中红色的部分,可以用来提取苹果。
二值化的方法
二值化是将图像转换为只有两种像素值(0和255)的过程,常用于图像分割。常用的方法有:
- 基于阈值的二值化:如
inRange()函数,根据指定的范围将像素值分为两类。 - 基于自适应阈值的二值化:如
adaptiveThreshold()函数,根据局部区域的像素值动态计算阈值。 - 基于Otsu算法的二值化:如
threshold()函数,自动计算全局阈值。
cv::Mat binary;cv::threshold(输入图像, binary, 阈值, 最大值, cv::THRESH_BINARY);type参数决定了二值化方式,如cv::THRESH_BINARY表示大于阈值的像素值设为最大值,小于等于阈值的像素值设为0。cv::THRESH_BINARY_INV表示相反的情况。
形态学操作:腐蚀与膨胀
形态学操作是基于图像形状的处理方法,常用于去噪、填充孔洞等。常用的形态学操作有腐蚀(Erosion)和膨胀(Dilation)。形态学操作往往作用于二值图像。
结构元素:kernel
结构元素是一个Mat类的矩阵,用于定义形态学操作的邻域关系。常用的结构元素有矩形、椭圆和十字形。可以使用getStructuringElement()函数来创建结构元素。
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(5, 5));其中cv::MORPH_RECT表示结构元素的形状为矩形,cv::Size(5, 5)表示结构元素的大小为5x5。还可以使用cv::MORPH_ELLIPSE和cv::MORPH_CROSS分别表示椭圆和十字形结构元素。
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(5, 5));腐蚀和膨胀操作
腐蚀操作会使白色区域变小,去除小的噪点,常用的方法是erode()函数。
cv::erode(输入图像, 输出图像, kernel);膨胀操作会使白色区域变大,填充小的孔洞,常用的方法是dilate()函数。
cv::dilate(输入图像, 输出图像, kernel);这两个结合就出现了开运算和闭运算,用cv::MORPH_OPEN和cv::MORPH_CLOSE来区分。
开运算:先腐蚀后膨胀,去除小的噪点,常用的方法是morphologyEx()函数。
cv::morphologyEx(输入图像, 输出图像, cv::MORPH_OPEN, kernel);闭运算:先膨胀后腐蚀,填充小的孔洞,常用的方法是morphologyEx()函数。
cv::morphologyEx(输入图像, 输出图像, cv::MORPH_CLOSE, kernel);在苹果的例子中,经过阈值分割后,掩膜可能会有一些噪点和孔洞,可以使用形态学操作来优化掩膜。
// 开运算去除噪声,闭运算填充空洞cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));cv::morphologyEx(red_mask, red_mask, cv::MORPH_OPEN, kernel);cv::morphologyEx(red_mask, red_mask, cv::MORPH_CLOSE, kernel);轮廓的提取与筛选
轮廓的提取
轮廓是图像中连续的边界线,可以用来表示物体的形状。opencv中常用的轮廓提取函数是findContours(),该函数可以从二值图像中提取轮廓。
cv::findContours(red_mask, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);red_mask:输入的二值图像contours:输出的轮廓集合,是一个vector<vector<Point>>类型的变量。vector<Point>表示一个轮廓,Point表示一个点的坐标,vector<Point>构成一个轮廓,vector<vector<Point>>表示多个轮廓的集合contours.size()表示轮廓的数量,contours[i]表示第i个轮廓,contours[i].size()表示第i个轮廓的点数,contours[i][j]表示第i个轮廓的第j个点
hierarchy:输出的轮廓层级关系,是一个vector<Vec4i>类型的变量cv::RETR_EXTERNAL:轮廓检索模式,这里表示只检测外部轮廓cv::CHAIN_APPROX_SIMPLE:轮廓近似方法,这里表示只保存轮廓的端点。
轮廓的筛选
提取到轮廓后,可以根据轮廓的面积、周长、轮廓点集的长度等属性进行筛选,去除一些不符合要求的轮廓。常用的方法是contourArea()和arcLength()函数。
在苹果的例子中,可以根据轮廓的面积,轮廓的点数(方便拟合)来筛选轮廓,去除一些过小的噪声轮廓。
// 过滤轮廓:根据面积过滤小噪声 double min_area = 500; // 最小面积 for (size_t i = 0; i < contours.size(); ++i) { double area = cv::contourArea(contours[i]); if (area > min_area && contours[i].size() > 5) { cv::Point2f center; float radius; cv::minEnclosingCircle(contours[i], center, radius); cv::circle(img, center, static_cast<int>(radius), cv::Scalar(0, 255, 0), 2); std::string label = "Apple"; cv::putText(img, label, cv::Point(static_cast<int>(center.x - radius), static_cast<int>(center.y - radius - 10)), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 2); } }这里面也用到了opencv的拟合函数minEnclosingCircle(),该函数可以拟合一个最小外接圆,用于表示苹果的位置和大小。
cv::Point2f center;float radius;cv::minEnclosingCircle(contours[i], center, radius);contours[i]:输入的轮廓点集center:输出的圆心坐标,是一个Point2f类型的变量radius:输出的圆的半径,是一个float类型的变量
轮廓的分类
在opencv中,partition()函数可以根据自定义的相似性标准对轮廓进行分类。该函数的参数包括:
int cv::partition(const std::vector<_Tp>& vec, std::vector<int>& labels, _EqPredicate predicate);vec:是一个vector<vector<Point>>类型的变量,表示多个轮廓的集合labels:是一个vector<int>类型的变量,表示每个轮廓所属的类别predicate:是一个函数指针,相当于等价关系语句。定义了两个轮廓是否相似的标准(显然,这个关系必须是自反的,对称的和传递的)
在苹果的例子中,可以根据轮廓的面积和位置来定义相似性标准,进而对轮廓进行分类。
auto predicate = [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) { double areaA = cv::contourArea(a); double areaB = cv::contourArea(b);