设置图片ROI(OpenCV学习笔记之二)
ROI相关简介
ROI是什么
ROI是region of interest首字母的简写,翻译为感性趣的区域。其实在写本篇之前应该写一篇关于图片是什么的学习笔记,但又感觉里面没多少东西还不能承载一篇学习笔记,所以这里只简单提一下。
图片是什么
其实大家也应该能猜到图片就是一个二维数组,只不过这个二维数组有点特殊,它有头信息,在头信息里会有描述这个二维数组的大小、图片类型和数组元素的数据类型等。下面是一张从官方教程里获取的一张辅助理解的图片。
图片就是一个二维数组上面的图片只是一张灰色的图片而我们经常用的图片彩色的图片,在灰图片中一个像素我们用一个值就可以表示了,但在彩色的图片中一个像素要用3个值或4个值(有的图片有alpha通道)来表示。下面就是一个由三个值表示一个像素的辅助理解图。
BGR格式的图片这张图片是从官网获取的。是不是觉得颜色的排序有点不对呀,我们想的应该是红在前才对,其实在OpenCV中很多图片都是BGR格式的和我们常见的到RGB格式的存储方式是相反的。具体为什么要用BGR格式还不清楚如有大神知道还请科普。其他图片的格式有很多种如,HVS、CyCbCr、HSI等格式,后续可能会专门写一篇关于图片格式的学习笔记,这里先对图片格式的介绍写这么多。
为什么要设置ROI
上面我已经介绍过图片是一个二维数组,而我们有时处理图片的时候只对其中的一部分图片的区域进行处理,例如我们想在图片某个区域打马赛克,为了性能考虑我们可以只让程序对这一部分信息进行处理而将其他部分忽略,这时我们就要设置图片感性趣的区域。设置完感性趣的区域后其实是指针指到了ROI区域的左上角,好像我们截取了一张小图片一样,我们只对这张小图片进行处理就可以了,因其ROI指向的还是原图只在告诉它图片的起始位置和大小变了,所以在对ROI区操作会影响原图。
设置ROI
在OpenCV中有C和C++的代码,最早OpenCV是用C写的,在开发中C的代码写起来不太方便在版本进入2.0之后后续加入的代码改用C++,所以设置ROI的方法有两种即C和C++的,C的已不常用不过这里还会列出已方便了解。
C++
- (void)setImageROI:(cv::Mat)image{
// 设置ROI
// 方法一
cv::Mat roiImage = image(cv::Rect(100, 100, 200, 100));
// 方法二,第一个range表示起始行和终止行,第二个range是起始列和终止列
//cv::Mat roiImage = testImage(cv::Range(100, 100 + 100), cv::Range(100, 200 + 100));
// 画一个矩形
cv::rectangle(roiImage, cv::Rect(0, 0, 200, 100), cv::Scalar(255, 0, 0), 10);
}
设置ROI其实就是在原来图片上指定一个区域,而这个区域只是新创建了一个图片文件的头信息而已并没有产生新的图片,文件头里的图片区域的起始位置指向了ROI区域的左上角位置,所以在ROI上做的任何操作都会影响原图片。
C
- (void)setImageROI:(IplImage *)image{
// 记录图片的大小和区域
CvRect currentRect = cvGetImageROI(image);
// 设置ROI区域
cvSetImageROI(image, cvRect(100, 100, 200, 100));
// 画一个矩形
cvRectangleR(image, cvRect(0, 0, 200, 100), CvScalar(255, 0, 0), 10);
// 还原ROI区域
cvSetImageROI(image, currentRect);
// 上面的还原ROI区域要一个临时变量,也可通过下面的方法,还原ROI区域而不用创建临时变量
//cvResetImageROI(image);
}
对于C的代码没有生成一个文件头信息而是修改原来的文件头信息,所以要把文件头信息改回去。现在是不是觉得C++比C简单多了。
代码实战
一、设置ROI并画矩形
以下代码是核心代码,其他简单的显示代码将不再列出。首先请导入以下头文件。
#import <opencv2/opencv.hpp>
#import <opencv2/imgproc/types_c.h>
#import <opencv2/imgcodecs/ios.h>
然后下面是具体的ROI代码都有注释,原理上面已说明。
- (UIImage *)getOpenCVImage{
// 获取测试用的图片路径
NSString * path = [[NSBundle mainBundle] pathForResource:@"test" ofType:nil];
// 读取图片
cv::Mat testImage = cv::imread([path cStringUsingEncoding:NSUTF8StringEncoding]);
// 设置ROI
cv::Mat roiImage = testImage(cv::Rect(100, 100, 100, 100));
// 在ROI区域做操作,画一个矩形
cv::rectangle(roiImage, cv::Rect(5, 5, 50, 50), cv::Scalar(255, 255, 255), 10);
// 将图片的格式从BGR转换成RGB,如果不转会造成显示的图片颜色出错
cv::cvtColor(testImage, testImage, cv::COLOR_BGR2RGB);
// 返回UIImage类型的图片
return MatToUIImage(testImage);
}
运行结果如下第一张是原图,第二张是处理后的图片:
原始图可以看到我们在ROI的(0,0)位置开始画矩形,但在大图中实际效果却不在左上角,这就是设置ROI的效果。
运行结果
超出ROI的效果
我们将上面代码改成正以下代码,让画矩形区域的高度大于ROI的高度。
// 放大矩形的高度,让其超出ROI的区域
cv::rectangle(roiImage, cv::Rect(0, 0, 200, 150), cv::Scalar(255, 0, 0), 10);
超出ROI操作的效果
可以看出,超出ROI的操作是被丢弃的。
二、设置ROI实现图片移位
上面说过设置ROI后我们就可以只对该区域进行操作。我们练习一下将一ROI区域的数据放到另一个ROI区域。
- (UIImage *)getOpenCVImage{
// 获取测试用的图片路径
NSString * path = [[NSBundle mainBundle] pathForResource:@"test" ofType:nil];
// 读取图片
cv::Mat testImage = cv::imread([path cStringUsingEncoding:NSUTF8StringEncoding]);
// 设置ROI区域A
cv::Mat roiImageA = testImage(cv::Rect(100, 100, 200, 100));
// 设置ROI区域B
cv::Mat roiImageB = testImage(cv::Rect(300, 30, 200, 100));
// 将roiImageB数据放到roiImageA的区域以实现图片区域移动效果
roiImageB.copyTo(roiImageA);
// 将图片的格式从BGR转换成RGB,如果不转会造成显示的图片颜色出错
cv::cvtColor(testImage, testImage, cv::COLOR_BGR2RGB);
// 将图片转成UIImage并返回
return MatToUIImage(testImage);
}
代码运行后结果:
实现图片区域移动