图像感兴趣区域(ROI)提取主要使用掩模来进行。掩模是二值图像,感兴趣区域的掩模值设置为255,非感兴趣区域的掩模值为0
获取掩模的方法主要有两种
方法一 使用opencv中Mat函数方法,调用Mat(Rect).setTo方法设置掩模
Mat Mat::operator()( const Rect& roi ) const
//调用Mat(Rect).setTo方法
mask(rect).setTo(255);
方法二 在全为0的原始掩模中画一个封闭区域,使用漫水填充算法填充封闭区域,将封闭区域的值都设置为255,实现掩模的提取
方法三 将边界转换为轮廓,使用cv::drawContours(mask, contours, -1, cv::Scalar::all(255),CV_FILLED);
函数提取感兴趣区域(ROI)。
下文对矩形、椭圆,有方向的矩形,轮廓进行提取
1.矩形感兴趣区域提取
1.1 调用Mat(Rect).setTo方法设置掩模
使用方法一对矩形感兴趣区域进行提取示例代码如下:
#include<cv.h>
#include<highgui.h>
using namespace cv;
//方法1,假如区域为长方形,使用MAT 构造函数设置区域内的值为255
int main()
{
Mat image=imread("lena.jpg");
//初始化掩模矩阵
Mat mask = Mat::zeros(image.size(), CV_8UC1);
Rect rect;
rect.x = 100;
rect.y = 100;
rect.width = 100;
rect.height = 100;
//设置矩形掩模
mask(rect).setTo(255);
Mat img2;
image.copyTo(img2, mask);
imshow("mask", mask);
imshow("img2", img2);
waitKey();
return 0;
}
1.2 使用漫水填充算法获取矩形ROI
思路:
1)新建一个值全为零的掩模图像(全是黑的,值为0)
2)在掩模图像上用白色画出矩形的边界(边界值为255)
3)选取矩形的中心作为种子点,使用漫水填充算法将矩形的内部填充为白色(255),最后得到掩模图像,使用掩模实现感兴趣区域提取。
#include<cv.h>
#include<highgui.h>
using namespace cv;
int main()
{
Mat image = imread("lena.jpg");
Mat mask = Mat::zeros(image.size(), CV_8UC1);
Rect rect;
rect.x = 100;
rect.y = 100;
rect.width = 100;
rect.height = 100;
//画矩形
rectangle(mask, rect, Scalar(255));
//设置种子点位置
Point seed;
seed.x = 150;
seed.y = 150;
//pi的值表示为 v(pi),if v(seed)-loDiff<v(pi)<v(seed)+upDiff,将pi的值设置为newVal
//使用漫水填充算法填充
floodFill(mask, seed, 255, NULL, cvScalarAll(0), cvScalarAll(0), CV_FLOODFILL_FIXED_RANGE);
//mask(rect).setTo(255);
Mat img2;
image.copyTo(img2, mask);
imshow("mask", mask);
imshow("img2", img2);
waitKey();
return 0;
}
2.任意几何形状ROI提取
任意几何形状感兴趣区域的提取主要使用方法二和方法三。提取的关键是画出几何形状的边界或获得轮廓。
2.1 旋转的矩形(CvBox2D)、椭圆(RotatedRect)、圆的感兴趣区域的提取
示例代码如下:
#include<cv.h>
#include<highgui.h>
using namespace cv;
#define WIDTH 256
#define HEIGHT 256
void DrawBox(CvBox2D box, IplImage* img)
{
CvPoint2D32f point[4];
int i;
for (i = 0; i<4; i++)
{
point[i].x = 0;
point[i].y = 0;
}
cvBoxPoints(box, point); //计算二维盒子顶点
CvPoint pt[4];
for (i = 0; i<4; i++)
{
pt[i].x = (int)point[i].x;
pt[i].y = (int)point[i].y;
}
cvLine(img, pt[0], pt[1], cvScalar(255), 2, 8, 0);
cvLine(img, pt[1], pt[2], cvScalar(255), 2, 8, 0);
cvLine(img, pt[2], pt[3], cvScalar(255), 2, 8, 0);
cvLine(img, pt[3], pt[0], cvScalar(255), 2, 8, 0);
}
//方法3.在掩模图像中画旋转的矩形(CvBox2D)、椭圆(RotatedRect)、圆,使用漫水填充算法将几何图形内部的值设置为255
int main()
{
Mat image = imread("dot_link_11.jpg");
Mat mask = Mat::zeros(image.size(), CV_8UC1);
CvBox2D box;
box.size.width = 100;
box.size.height = 50;
box.angle = 30;
box.center.x = 200;
box.center.y = 200;
情况1.画旋转的矩形(CvBox2D)
//opencv 2.4.9
//IplImage* imask = &IplImage(mask);
//opencv 3.0
//IplImage* imask = new IplImage(mask);
//DrawBox(box,imask);
//Point seed;
//seed.x = box.center.x;
//seed.y = box.center.y;
//情况2.画椭圆
//RotatedRect roRect;
//roRect.angle = 30;
//roRect.center.x = 200;
//roRect.center.y = 200;
//roRect.size.width = 100;
//roRect.size.height = 50;
//ellipse(mask, roRect, cvScalar(255));
//Point seed;
//seed.x = roRect.center.x;
//seed.y = roRect.center.y;
情况3.画圆
Point center;
center.x = 100;
center.y = 100;
float radius = 50;
circle(mask, center, radius, Scalar(255));
Point seed;
seed.x = center.x;
seed.y = center.y;
//漫水填充
//pi的值表示为 v(pi),if v(seed)-loDiff<v(pi)<v(seed)+upDiff,将pi的值设置为newVal
floodFill(mask, seed, 255, NULL, cvScalarAll(0), cvScalarAll(0), CV_FLOODFILL_FIXED_RANGE);
//mask(rect).setTo(255);
Mat maskImage;
image.copyTo(maskImage, mask);
imshow("mask", mask);
imshow("img2", maskImage);
waitKey();
return 0;
}
2.2 感兴趣区域为轮廓的提取
(1)漫水填充算法思路:
1)调用opencv的画图函数cvLine将轮廓中相邻的点连接为区域
2)获取轮廓中心,使用漫水填充算法填充
示例代码如下:
void draw_external_contour_gray(CvSeq *seq, Mat grayImage)
{
if (seq->total < 2)
{
return;
}
Point* prePoint = (Point*)cvGetSeqElem(seq, 0);
Point* lastPoint = (Point*)cvGetSeqElem(seq, seq->total - 1);
cv::line(grayImage, *prePoint, *lastPoint,cvScalar(255), 1, 8, 0);
for (int i = 1; i<seq->total; i++) {
Point* p=(Point*)cvGetSeqElem(seq,i);
cv::line(grayImage, *prePoint, *p, cvScalar(255), 1, 8, 0);
*prePoint = *p;
}
}
//方法4,假如区域边界为轮廓,使用掩模图像中画轮廓,使用漫水填充算法将几何图形内部的值设置为255
int main()
{
Mat image=imread("lena.jpg");
if(image.empty())
{
cout<<"image is empty"<<endl;
return 0;
}
Mat mask = Mat::zeros(image.size(), CV_8UC1);
CvMemStorage* storage = cvCreateMemStorage(0);
// CvSeq* contour = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);
CvSeqWriter writer;
cvStartWriteSeq(CV_32SC2, sizeof(CvSeq), sizeof(CvPoint),storage,&writer);
CvPoint p1 = { 25, 60 }; CvPoint p2 = { 50, 110 }; CvPoint p4 = { 100, 60 }; CvPoint p3 = { 100, 110 }; CvPoint p5 = { 50, 10 };
CV_WRITE_SEQ_ELEM(p1, writer);
CV_WRITE_SEQ_ELEM(p2, writer);
CV_WRITE_SEQ_ELEM(p3, writer);
CV_WRITE_SEQ_ELEM(p4, writer);
CV_WRITE_SEQ_ELEM(p5, writer);
cvFlushSeqWriter(&writer);
CvSeq* contour = cvEndWriteSeq(&writer);
printf("contour.size=%d", contour->total);
draw_external_contour_gray(contour, mask);
Point seed;
seed.x = 35;
seed.y = 60;
//漫水填充
floodFill(mask, seed, 255, NULL, cvScalarAll(0), cvScalarAll(0), CV_FLOODFILL_FIXED_RANGE);
Mat maskImage;
image.copyTo(maskImage, mask);
imshow("mask", maskImage);
waitKey();
return 0;
}
(2)将边界转换为轮廓,使用cv::drawContours(mask, contours, -1, cv::Scalar::all(255),CV_FILLED);
函数提取感兴趣区域(ROI)
代码如下:
#include <opencv2/core/core.hpp>
#include<opencv2/opencv.hpp>
#include<iostream>
#include <string>
using namespace cv;
using namespace std;
int main()
{
Mat image=imread("lena.jpg");
if(image.empty())
{
cout<<"image is empty"<<endl;
return 0;
}
Mat mask = Mat::zeros(image.size(), CV_8UC1);
Point p1 = { 25, 60 }; Point p2 = { 50, 110 }; Point p4 = { 100, 60 }; Point p3 = { 100, 110 }; Point p5 = { 50, 10 };
vector<Point> contour;
contour.push_back(p1);
contour.push_back(p2);
contour.push_back(p3);
contour.push_back(p4);
contour.push_back(p5);
vector<vector<Point> > contours;
contours.push_back(contour);
cv::drawContours(mask, contours, -1, cv::Scalar::all(255),CV_FILLED);
imshow("maskRegion",mask);
waitKey();
Mat maskImage;
image.copyTo(maskImage, mask);
imshow("getMaskImage", maskImage);
waitKey();
return 0;
}
注意:drawContours方法也是通用的提取感兴趣区域的方法