内容简介
本文旨在通过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 <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
/// <summary>
/// 将图像上旋转矩形以外的区域涂成黑色
/// </summary>
/// <param name="src">原图</param>
/// <param name="rotatedRect">旋转矩形</param>
/// <returns>绘制后的图像</returns>
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 endregion
Mat 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
}
运行结果
总结
抠图主要是根据某些特征,如颜色(或灰度值)、大小、形状等来进行判断处理,通过形态学操作来实现粘黏区域的断开以及分离区域的合并。上面的图像是将彩色图转换到灰度图进行处理的,直接在彩色图像上对每个通道进行单独处理也能够实现。从图像上看,从原图开始就有反光干扰,从硬件层面这种情况可以考虑使用偏光片,从算法层面可以考虑使用光影校正,后面有时间搞个更简单粗暴的浓淡补正算法。