OpenCV(C++)学习笔记六:抠图(ROI提取)_opencv 抠图
导航目录
内容简介
本文旨在通过C++OpenCV实现图片中感兴趣区域提取,案例演示经过两次分割处理,实际生产场景下的图片一般来说相对简单。方法类似,可供参考。
感兴趣区域提取步骤
在生产生活中:我们遇到的感兴趣区域提取一般来说相对简单,流程也相对固定,主要有以下几个步骤:
1.读取图片(工业应用中多处理单通道灰度图像,文中案例处理为单通道图像);
2. 阈值分割,以前只知道用Threshold方法,现在可以通过inRange方法实现灰度值精准分割(习惯使用halcon的朋友应该更习惯这个方法,同halcon中的threshold算子),下文中将详细介绍该方法;
3. 分割后根据一定条件找到想要的区域(可能不完整,大致的区域,至少包含感兴趣区域),根据条件(能够区分开感兴趣区域和其他干扰区域的特征,常用面积,如最大面积区域)筛选你想要的区域;
4. 根据找到的区域进行形态学操作,割掉一些“小尾巴”,如果分割情况比较好,这一步可以省去;
5. 选定裁切方案,如在原图中屏蔽其他区域,或直接将感兴趣区域裁切下来;
6. 裁切后再添加相关修饰步骤,如图像增强、仿射变换等,本文仅讨论前面常规步骤。
关键函数方法
获取特定灰度值区间的区域:
cv::inRange(src, thresh1, thresh2, dst);
- src:原图,输入图像
- thresh1:阈值下限
- thresh2:阈值上限
- dst:结果图,输出图像
形态学操作
cv::morphologyEx(src, dst, MORPH_OPEN, kernel);
- MORPH_OPEN:开运算标记,可选MORPH_ERODE、MORPH_DILATE、MORPH_OPEN、MORPH_CLOSE、MORPH_GRADIENT、MORPH_TOPHAT、MORPH_BLACKHAT、MORPH_HITMISS
- kernel:卷积核,可通过opencv自带的函数进行构建,例如:Mat kernel = getStructuringElement(MorphShapes::MORPH_ELLIPSE, Size(9, 9));//构造大小为9*9的椭圆卷积核
凸包计算
convexHull(contours[i], hull);
- contours[i]:轮廓点集,可由findContours()函数计算出来
- hull:凸包点集,类型为Point向量(vector)
填充凸包区域
fillConvexPoly(mask, hull, Scalar(255));
- mask:待填充的图像
- hull:凸包点集
- Scalar(255):填充色(白色)
代码实现
#include #include using namespace std;using namespace cv;/// /// 将图像上旋转矩形以外的区域涂成黑色/// /// 原图/// 旋转矩形/// 绘制后的图像Mat CropWithMask(const Mat& src, const RotatedRect& rotatedRect){// 1. 获取旋转矩形的四个顶点坐标 (Point2f格式)cv::Point2f vertices[4];rotatedRect.points(vertices);// 2. 创建单通道黑色掩膜 (尺寸=原图, 类型=8位无符号)cv::Mat mask = Mat::zeros(src.size(), CV_8UC1);// 3. 将顶点转换为整数坐标 (fillPoly要求整数点)cv::Point intVertices[4];for (int i = 0; i < 4; ++i){intVertices[i] = static_cast<cv::Point>(vertices[i]);}// 4. 在掩膜上填充旋转矩形 (白色)const cv::Point* pts[] = { intVertices }; // 顶点指针数组int npts[] = { 4 }; // 顶点数量fillPoly(mask, pts, npts, 1, cv::Scalar(255));// 5. 应用掩膜提取区域Mat result;src.copyTo(result, mask); // 仅复制掩膜标记区域 //imwrite(\"C:/Users/Administrator/Desktop/00.jpg\", result);return result;}int main(){Mat src = imread(\"C:/Users/Administrator/Desktop/各景区指路牌.jpg\", IMREAD_GRAYSCALE);#pragma region 根据阈值区间找到大致的目标位置Mat dst;inRange(src, 50, 160, dst);Mat kernel = getStructuringElement(MorphShapes::MORPH_ELLIPSE, Size(9, 9));morphologyEx(dst, dst, MORPH_OPEN, kernel);vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(dst, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);double minArea = 8e4;double maxArea = 2.5e5;vector<int> indexs;for (size_t i = 0; i < contours.size(); i++){double area = contourArea(contours[i]);if (area < maxArea && area > minArea){indexs.push_back(i);}}#pragma endregionMat blackImg = Mat::zeros(src.size(), src.type());vector<RotatedRect> minRects(indexs.size());vector<Mat> resImg(indexs.size());if (indexs.size() > 0){for (size_t i = 0; i < indexs.size(); i++){Mat mask = blackImg.clone();//drawContours(mask, contours, indexs[i], Scalar(255), 2);minRects[i] = minAreaRect(contours[indexs[i]]);resImg[i] = CropWithMask(src, minRects[i]);//imwrite(\"C:/Users/Administrator/Desktop/temp1.jpg\", resImg[i]);inRange(resImg[i], 1, 180, resImg[i]);//imwrite(\"C:/Users/Administrator/Desktop/temp2.jpg\", resImg[i]);#pragma region 找出轮廓凸包contours.clear();hierarchy.clear();findContours(resImg[i], contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);int maxIndex = 0;for (size_t j = 0; j < contours.size(); j++){if (contours[j].size() < 20){continue;}double area = contourArea(contours[j]);if (area < maxArea && area > minArea){maxIndex = j;break;}}#pragma endregion#pragma region 制作凸包蒙板抠原图vector<Point> hull;convexHull(contours[maxIndex], hull);fillConvexPoly(mask, hull, Scalar(255));src.copyTo(blackImg, mask);imwrite(\"C:/Users/Administrator/Desktop/result.jpg\", blackImg);return -1;#pragma endregion}}#pragma region 调试代码Mat showImg = blackImg;namedWindow(\"img\");imshow(\"img\", showImg);imwrite(\"C:/Users/Administrator/Desktop/temp.jpg\", showImg);waitKey();#pragma endregion}
运行结果
总结
抠图主要是根据某些特征,如颜色(或灰度值)、大小、形状等来进行判断处理,通过形态学操作来实现粘黏区域的断开以及分离区域的合并。上面的图像是将彩色图转换到灰度图进行处理的,直接在彩色图像上对每个通道进行单独处理也能够实现。从图像上看,从原图开始就有反光干扰,从硬件层面这种情况可以考虑使用偏光片,从算法层面可以考虑使用光影校正,后面有时间搞个更简单粗暴的浓淡补正算法。