OpenCV 基本函数
用实现照片底片化的例子介绍一些基本函数
所谓照片底片化,就是将图像每个像素的RGB三个通道取反,如果是256色,就是R’=255-R, G’=255-G, B’=255-B,如果是0.0-1.0的颜色空间,就是R’=1.0-R, G’=1.0-G, B’=1.0-B。通过上述描述,可得出算法:遍历每个像素点RGB值-重新计算-生成图像
memcpy 内存拷贝
原始图像:
/#include<iostream>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
int main()
{
//设置图片路径
string path = "./yy.jpg";
//生成Mat型图像矩阵
Mat I = imread(path);
//转化Mat内部数据类型,此处可以不加 1 / 255.0,后面取反的时候用255-像素值
I.convertTo(I, CV_32FC3, 1 / 255.0);
//构建像素值数组
float* img_data = new float[I.cols * I.rows * 3];
//像素值拷贝
memcpy(img_data, I.data, I.cols * I.rows * 3 * sizeof(float));
//遍历像素并计算
for (int i = 0; i < I.cols * I.rows * 3; i++)
{
img_data[i] = 1 - img_data[i];
}
//通过像素数组重新生成图像
Mat result1(I.rows, I.cols, CV_32FC3, img_data);
//保存图片,由于像素值位0~1,打开后是黑色,显示时需要线性变换到0~255,才能显示图片
imwrite("./yy_result1.jpg", result1);
//显示结果
imshow("result1", result1);
waitKey(0);
return 0;
}
结果图:
函数void *memcpy(void *dest, const void *src, size_t n);
该函数在OpenGL部分文章中有提及,该函数并非OpenCV函数而是C++函数,作用是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中,
参数void *dest
:目标指针,文中为新创建空数组img_data
参数const void *src
:源内存地址起始位置,Mat型图像起始指针表示为Mat.data,因此文中为I.data
参数size_t n
:内存长度,文中为像素个数3通道(图像长宽*3),再乘以数据种类所占空间(sizeof(float)),最终形式为I.cols*I.rows * 3 * sizeof(float)
Mat构造函数
1. Mat(int rows, int cols, int type, const Scalar& s);
2. Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);
Mat类型的构造函数有很多种(20种左右吧),目的是为了满足不同作用矩阵的构造需求。对于图像处理和一般操作来说,主要应用上述两种类型构造函数
参数int rows, int cols
:矩阵长宽,文中构造与原图尺寸相同的矩阵以存储结果图像,因此设为I.rows, I.cols
参数int type:设置矩阵内数据类型,虽说该参数为int类型,但是为了直观,通常不用单一的数字表示。如文中需要创建数据为3通道float类型,所以参数设为CV_32FC3
1中参数const Scalar& s
:设置矩阵每个数据的初始值。实际上,构造函数1通常用在空白图像的初始化,可将图像设为全白Scalar(1.0f,1.0f,1.0f)或全黑Scalar(0.0f,0.0f,0.0f),当然也可将图像设为其他颜色。三通道时该参数用Scalar(x,x,x)表示,四通道用Scalar(x,x,x,x)表示,单通道直接用数字表示即可
2中参数void* data
:设置矩阵内数据,将data数组中的数据作为矩阵元素的值。由于矩阵的长宽通过构造函数设定,数组的长度也是认为设定的,因此只要矩阵尺寸和数组长度相对应即可成功构造矩阵。文中该参数为进行遍历运算之后的像素数组img_data
split多通道分离,merge多通道合并
除了对每个像素进行遍历操作外,OpenCV还提供了分通道对图像进行操作的方法:
//划分BGR三通道
vector<Mat> channels;
split(I, channels);
//分别处理三通道
vector<Mat> newChannels;
newChannels.push_back(1 - channels[0]); //B通道
newChannels.push_back(1 - channels[1]); //G通道
newChannels.push_back(1 - channels[2]); //R通道
//融合BGR三通道
Mat result2;
merge(newChannels, result2);
//显示结果
imshow("result2", result2);
-
函数void split(const Mat& m, vector<Mat>& mv )`
该函数作用是将多通道矩阵m中的每个通道单独提取,分别存入mv中
参数const Mat& m:
多通道Mat型矩阵。文中要将彩色BGR三通道图像I分离通道,因此参数设为I
参数vector<Mat>& mv
: vector <Mat>结构,用来存储分离后的单通道矩阵。文中将彩色图像矩阵I拆分为三个单通道矩阵,这三个矩阵存于未初始化的vector channels中
注意:参数vector& mv不必初始化,参数const Mat& m有几个通道,mv就有几个,可用[n]
来获取某通道的矩阵。如文中Mat I为三通道彩色图像,所以vector channels中共有3个Mat型,其中channels[0]代表I的B通道(蓝色),channels[1]代表I的G通道(绿色),channels[2]代表I的R通道(红色) -
函数void merge(const vector<Mat>& mv, OutputArray dst )
该函数作用和split相反,是将若干个单通道Mat型合成一个多通道矩阵
参数const vector<Mat>& mv
:vector<Mat>型若干个单通道Mat组成的vector。文中将分离的vector channels每个通道进行取反计算,因此该参数设为处理后的channels
参数OutputArray dst
:输出矩阵,OutputArray类型可以用Mat类型表示。文中将分别处理之后的三通道合并回三通道BGR彩色图像,因此该参数设为未初始化的Mat result2以存储结果。
程序运行结果和之前的一样:
针对该问题,还有一种更为简单的处理方法,直接应用矩阵运算:
Mat result3 = Scalar(1,1,1) - I;
imshow("result3", result3);
最终结果和前两种方法相同,并且花费时间最短。第一种方法时间花费在遍历全图像素点;第二种方法时间花费在split和merge两个函数上
完整代码:
#include<iostream>
#include<opencv2\opencv.hpp>
using namespace cv;
using namespace std;
void main()
{
//设置图片路径
string path = "./yy.jpg";
//生成Mat型图像矩阵
Mat I = imread(path);
//转化Mat内部数据类型
I.convertTo(I, CV_32FC3, 1 / 255.0);
//构建像素值数组
float* img_data = new float[I.cols * I.rows * 3];
//像素值拷贝
memcpy(img_data, I.data, I.cols * I.rows * 3 * sizeof(float));
//遍历像素并计算
for (int i = 0; i < I.cols * I.rows * 3; i++)
{
img_data[i] = 1 - img_data[i];
}
//通过像素数组重新生成图像
Mat result1(I.rows, I.cols, CV_32FC3, img_data);
//显示结果
imshow("result1", result1);
//划分BGR三通道
vector<Mat> channels;
split(I, channels);
//分别处理三通道
channels[0] = 1 - channels[0];
channels[1] = 1 - channels[1];
channels[2] = 1 - channels[2];
//融合BGR三通道
Mat result2;
merge(channels, result2);
//显示结果
imshow("result2", result2);
Mat result3 = Scalar(1, 1, 1) - I;
imshow("result3", result3);
//图像显示
imshow("ori", I);
//程序挂起等待
waitKey();
}