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

}

运行结果

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

总结

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

### 使用 OpenCV 实现图像抠图 #### 准备工作 为了使用 OpenCV 进行图像抠图,需先安装并导入必要的库。Python 中可以利用 `pip` 安装 OpenCV 库。 ```bash pip install opencv-python-headless numpy ``` 接着,在 Python 脚本中引入所需的模块: ```python import cv2 import numpy as np from matplotlib import pyplot as plt ``` #### 加载图片与创建掩模 读取待处理的源图像以及用于定义前景区域的标志图像(如Logo)。根据需求调整路径以匹配本地文件位置[^3]。 ```python img_logo = cv2.imread('path_to_logo_image') # 替换为实际 Logo 文件路径 logo_gray = cv2.cvtColor(img_logo, cv2.COLOR_BGR2GRAY) _, mask = cv2.threshold(logo_gray, thresh=200, maxval=255, type=cv2.THRESH_BINARY_INV) ``` 此部分代码实现了将彩色的 logo 图片转换成灰度版本,并基于设定阈值生成二值化掩码,其中低于指定亮度级别的像素被标记为目标对象的一部分。 #### 提取前景物体 应用上述得到的掩膜到原始输入图像上来获取仅含前景的信息。 ```python foreground = cv2.bitwise_and(img_logo, img_logo, mask=mask) ``` 这段操作会保留原图里对应于非零掩膜值的部分作为前景元素。 #### 合并背景与前景 如果想要把抠出来的图形放置在一个新的背景下,则需要准备一张大小相同的空白画布或者另一张完整的照片充当新场景;之后再执行逻辑运算完成合成过程。 ```python background = cv2.imread('new_background_path', cv2.IMREAD_COLOR) roi_rows, roi_cols = foreground.shape[:2] # 将前景置于背景之上 center_x, center_y = int(background.shape[1]/2), int(background.shape[0]/2) top_left_corner = (center_x-int(roi_cols/2), center_y-int(roi_rows/2)) bottom_right_corner = (center_x+int(roi_cols/2), center_y+int(roi_rows/2)) roi_bg = background[top_left_corner[1]: bottom_right_corner[1], top_left_corner[0]: bottom_right_corner[0]] roi_fg_masked = cv2.bitwise_and(foreground, foreground, mask=mask) final_img = cv2.addWeighted(src1=roi_bg, alpha=1, src2=roi_fg_masked, beta=1, gamma=0) background[top_left_corner[1]: bottom_right_corner[1], top_left_corner[0]: bottom_right_corner[0]] = final_img ``` 这里展示了如何计算前景应该放置的位置,并通过加权求和的方式将其无缝融合进选定的新环境中。 #### 显示结果 最后一步是查看最终的效果。这可以通过调用 Matplotlib 的绘图函数轻松做到。 ```python plt.figure(figsize=(10,8)) plt.imshow(cv2.cvtColor(background, cv2.COLOR_BGR2RGB)), plt.title("Final Image with Cutout") plt.axis('off') plt.show() ``` 这样就完成了整个抠图流程,从加载图像到最后的结果可视化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值