OpenCV(C++)学习笔记六:抠图(ROI提取)

该文章已生成可运行项目,

内容简介

本文旨在通过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

}

运行结果

车内手机拍摄原图
抠出的图像

总结

抠图主要是根据某些特征,如颜色(或灰度值)、大小、形状等来进行判断处理,通过形态学操作来实现粘黏区域的断开以及分离区域的合并。上面的图像是将彩色图转换到灰度图进行处理的,直接在彩色图像上对每个通道进行单独处理也能够实现。从图像上看,从原图开始就有反光干扰,从硬件层面这种情况可以考虑使用偏光片,从算法层面可以考虑使用光影校正,后面有时间搞个更简单粗暴的浓淡补正算法。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值