概述
POCT-IR图像识别算法中抛开实验室相关的内容,其图像处理部分都是使用OpenCV来完成。通过学习《OpenCV3编程入门》,可以更好的在POCT-IR中使用OpenCV,同时也可以对项目中可以提升的点提出一些方案。
通过对这本书的阅读,深刻地感受到对图像进行操作归根结底就是对图像中的特定像素点进行处理,最终得到目标图像。在OpenCV3数以百计的API法中,有些API以单个像素点作为特征量的变换单位,来修改图像内容,例如:滤波、灰度值变换等。还有一些API以一定范围内的像素点作为特征量的变化单位,来对图像进行变换,例如边缘检测、仿射变化等。
这篇学习笔记以《学习OpenCV》书籍中的API为基础,结合实践中的思考,对OpenCV3进行介绍,加深记忆的同时也希望能够对之后使用OpenCV3的小伙伴起到一定的指引作用。
全文按照以下三个方面对OpenCV学习的内容进行整理:
Mat结构、基础的数据结构
OpenCV基本API介绍及使用
1. MAT类介绍、基础数据结构
贯穿OpenCV始终的就是Mat类,通过了解Mat的结构及内存分配方式,将极大的推进我们对OpenCV的学习。而一些基础的数据结构有助于提高代码的可读性和实用性(Point)。
1.1 MAT到底是什么?
内存管理
在OpenCV1.0时代,Mat(IplImage*)是存放图像的数据结构、基本的图像容器。在使用的过程中,需要手动管理内存空间,否则会造成内存泄漏。
从OpenCV2.0开始,为了减少手动管理内存空间的操作,使用引用计数的方式将Mat对象进行内存管理。
内部定义结构
Mat类可以将其分为Header部分和Pointer部分,Header部分主要存放矩阵的大小,存储方式,存储地址等信息;而Pointer部分主要存储指向像素值的指针。在复制的过程中产生大量计算开销是复制Pointer造成的,所以在Clone过程中只复制header相关信息,减少内存开销。
Mat核心成员定义介绍
// 指针,指向存放Pointer的内存
uchar* data; int rows, cols;
// 矩阵中每个元素拥有值的个数
int channels() const;
// 每个元素的位数
int depth() const;
// 矩阵的维度
int dims;
// 矩阵中每个元素的大小
size_t elemSize() const;
// 矩阵元素的类型
CV_8UC3、CV_16UC2 int type() const;
// 定义了矩阵的布局
size_t step1(int i=0) const;
...
上述部分介绍了Mat的核心成员变量,通过这些成员变量也大致能够知道Mat存储图像的方式,涉及到如何进行初始化及这些成员变量的运用,在之后的章节中通过案例及初始化MAT对象对成员变量进一步的认识。
注:在刚开始阅读OpenCV相关文献的时候,有些文献会将MAT介绍为结构体,在另一些文章中将Mat介绍为类,一开始对于各文章中的介绍孰是孰非很难有定论,但是因为其需要手动管理内存空间的机制,或者是使用引用计数的方式管理内存,根据OC的值类型和引用类型的相关知识(结构体是值类型存在栈中系统会自动清理内容、类是引用类型需要运用ARC、MRC机制进行内容管理)类比,确信Mat是一个类。如果有更权威的文献能够啪啪打脸,请指正~
1.2 初始化MAT对象
写这篇文章的提纲时,一直在纠结需不需要将这部分内容放进来,只是关于Mat初始化并不是很值得放入到文章中。最后考虑加入这部分内容,首先确实Mat类是OpenCV核心中的核心,其次对笔记之后内容得介绍起到承上启下的作用。
Mat构造函数
创建方法
UMat(int rows, int cols, int type,const Scalar& s,UMatUsageFlags usageFlags = USAGE_DEFAULT);
Mat mat(2, 2, CV_8UC3, Scalar::all(255, 0, 0));
说明介绍
rows = 2, cols = 2;代表创建一个2*2的矩阵;
type = CV_8UC3;描述为使用8位unsigned char型,每个像素由3个元素组成3通道。
注 :每个像素最多的通道数为4,除了BGR外加入alpha,而最少为单通道,即灰度值。
Scalar = Scalar::all(0, 0, 0));描述为每个元素的值为(255, 0, 0),注意不同于常使用的RGB显示,在OpenCV中采用BGR的方式即第一通道是B,第二通道是G,第三通道是R。
Mat mat(2, 2, CV_8UC3, Scalar::all(255, 0, 0));整体描述为创建了一个2*2像素值为蓝色的Mat对象。
更多初始化Mat对象的方法
UMat(int rows, int cols, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT);
UMat(Size size, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT);
//! constucts 2D matrix and fills it with the specified value _s.
UMat(Size size, int type, const Scalar& s, UMatUsageFlags usageFlags = USAGE_DEFAULT);
//! constructs n-dimensional matrix
UMat(int ndims, const int* sizes, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT); UMat(int ndims, const int* sizes, int type, const Scalar& s, UMatUsageFlags usageFlags = USAGE_DEFAULT);
//! copy constructor UMat(const UMat& m);
//! creates a matrix header for a part of the bigger matrix
UMat(const UMat& m, const Range& rowRange, const Range& colRange=Range::all()); UMat(const UMat& m, const Rect& roi);
UMat(const UMat& m, const Range* ranges);
UMat(const UMat& m, const std::vector& ranges);
通过imread返回Mat对象
创建方法
Mat imread(const String& filename,int flags = IMREAD_COLOR);
Mat srcImage = imread("../海贼王2.jpg");
说明介绍
filename指的是文件的绝对路径,imread对文件格式有一定的要求(bmp、jpg、jpeg等)
flags指的是打开文件的方式,通过修改flags的值,直接将图片进行处理。例如图片设置为单通道IMREAD_GRAYSCALE,或者减少图片的色值IMREAD_REDUCED_COLOR_2 (色值减少为之前的1/2)等;
注:将图片色值减少为原来的1/2的原理是将每个通道上的值除以2取整,在2,相当于去除掉奇数值。 伪代码实现: outputImage.at(i, j)[0] = outputImage.at(i, j)[0]/2*2; IMREAD_REDUCED_COLOR_4; IMREAD_REDUCED_COLOR_8同理,在损失一定色值的情况下提高存储效率。
在接下来的笔记中大多数都是以imread函数创建Mat对象。
2. OpenCV基本API的介绍及使用
上一节中对Mat及初始化进行了介绍,相信已经迫不及待的想要将OpenCV的图像处理运用到实例中去,在本节中将会介绍一个色值衰减的案例和画线的案例,作为切入点来感受一下神奇的OpenCV。虽然这些例子都相对简单,但通过这些例子还是可以窥探出在使用OpenCV进行图像处理时的基本操作步骤:
获取Mat对象.
在Mat对象中根据需求寻找特征像素点.
对特征像素点进行修改,使之获取新的Mat对象.
重复2、3操作获取新的Mat对象,直到Mat对象符合最终结果.
输出图像.
2.1 减少图片色值的案例
在1.2节中对减少图像色值有了一个简单的介绍,但imread的flags色值的使用是有些的,并不能任意的色值缩小为原来的1/3,1/5等,所以在本例中会实现将图片的色值缩小为任意倍数。通过本例可以对像素的通道,有更深刻的认识。
int main() {
//【1】初始化Mat对象 Mat srcImage = imread("../1.jpg");
imshow("【原始图片】", srcImage);
// 【2】按原始图的参数规格来创建效果图
Mat dstImage(srcImage.cols, srcImage.rows, srcImage.type());
// 参数1 传入Mat对象,参数2输出的Mat对象,参数3将色值衰减为原来的1/32
colorReduceDymatic(srcImage, dstImage, 32);
// void imshow(const string& winname, InputArray image) winname要显示的窗口名,image需要显示的内容
imshow("【效果图】", dstImage);
// 按esc关闭窗口
waitKey(0);
}
// 获取到所有的像素点,因为该图片是三通道的图片,所以对每个像素的3个通道都需要进行像素值的衰减。通过这个方法将图片的色值衰减为原来的1/32
void colorReduceDymatic(Mat& inputImage, Mat& outputImage, int div) {
outputImage = inputImage.clone(); int rowNumber = outputImage.rows;
// 行
int colNumber = outputImage.cols;
// 列 // 存取彩色图像像素
for(int i = 0; i < rowNumber; i++) {
for(int j = 0; j < colNumber; j++) {
outputImage.at(i, j)[0] = outputImage.at(i, j)[0]/div*div ; //蓝色通道
outputImage.at(i, j)[1] = outputImage.at(i, j)[1]/div*div ; //绿色通道
outputImage.at(i, j)[2] = outputImage.at(i, j)[2]/div*div ; //红色通道
}
}
}
原始图片
色值衰减之后的图片
2.2 使用OpenCV进行简单的绘制操作
在POCT-IR中涉及到需要画线,以提取T线和C线的色值的内容。所以在本节中将介绍一个通过OpenCV在Mat上绘制图像的例子。
OpenCV绘制图像的过程与iOS中绘制图像的过程也是一致的,调用了基本的line和 ellipse函数进行绘制。
#define WINDOW_WIDTH 600
void drawEllipse(Mat img, double angle);
// 画线 Point 基本的数据结构 定义线的起点和终点
void drawLine(Mat img, Point state, Point end);
int main() {
Mat ellipse = Mat::zeros(Size(WINDOW_WIDTH, WINDOW_WIDTH), CV_8UC3);
Mat line = Mat::zeros(Size(WINDOW_WIDTH, WINDOW_WIDTH), CV_8UC3);
drawEllipse(ellipse, 90);
imshow("【椭圆】", ellipse);
drawLine(line, Point(0, 0), Point(100, 200));
drawLine(line, Point(100, 200), Point(300, 200));
drawLine(line, Point(300, 200), Point(300, 0));
drawLine(line, Point(300, 0), Point(0, 0));
imshow("【画线】", line);
waitKey();
return 0;
}
// 使用line函数进行绘制
void drawLine(Mat img, Point start, Point end) {
int thickness = 2;
int lineType = 8;
line(img,
start,
end,
// 色值
Scalar(0, 255, 255),
thickness,
lineType);
}
// 实现绘制不同角度、相同尺寸的椭圆
void drawEllipse(Mat img, double angle) {
int thickness = 2;
int lineType = 8;
ellipse(img,
Point(WINDOW_WIDTH/2, WINDOW_WIDTH/2),
Size(WINDOW_WIDTH/4, WINDOW_WIDTH/16),
angle,
0,
360,
Scalar(255, 129, 9),
thickness,
lineType
);
}
在本篇文章中介绍的API只涵盖了OpenCV3 API极小的一部分内容,通过对本节开始内容的学习,可以对OpenCV3的使用模式有初步了解,更多相关的接口使用事例.(因案例内容都是手写,如若遇到注释解释不清,内容描述有误的部分请及时指正,以便进行及时修改。)