这几天开始看OpenCV。
基本是跟着官方的tutorial来的,目前已经看到更改图像对比度和亮度。
之前在研究生阶段上过一门机器视觉的课,当时老师为了尽快讲明原理,使用的是Matlab+Simulink。
后来找了一个创业公司实习,创业的主要产品就是一个基于机器视觉的一个图像对比的产品。
这次算是重新系统的学习一下,也当做是练手的东西,提升一下自己实际的变成技巧以及对细节的把控能力。
总结一下这几天看到的和记住的内容。
opencv基础操作:
1. 打开图像
Mat image; image = imread(argv[1], IMREAD_COLOR);
Mat是OpenCV里面应用最广泛的一个数据容器,本质上是一个矩阵。用来存放各种图像。完成图像的运算。
现在用到的构造函数有以下几个:
//Create matrix using explicit construction function. Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255)); cout << "M=" << endl << " " << M << endl; //Create matrix with more than 2 dimensions. int sz[3] = { 2, 2, 2 }; Mat L(3, sz, CV_8UC(1), Scalar::all(0)); //cout << "L=" << endl << L << endl; //Warning: Matrix with more than 2 dimension can not be printed with cout! IplImage *img = cvLoadImage("opencv-logo.png", 1); Mat matx1(img);
Mat C = (Mat_<double>(3,2) << 0, 1, 0, -1, 5, -1);
Mat储存图像时需要用户来设定的包括图像大小,像素通道数,像素深度。
图像大小就是图像各个方向上的像素数量。通道数是指每个像素使用多少个数值来表示,比如灰度图像只需一个数值,RGB彩色图像需要3个数值。值得注意的是,Mat当中每个像素的3个数据代表的含义是BGR,和人们经常说的RGB正好反过来。
图像深度暂时还没搞懂,大概是一个表述每个像素点的数值占据空间大小的这么个东西。
图像深度、图像通道数可以通过几个固定的宏来传递给构造函数。形式是CV_8UC3。8代表图像深度为8个bit,U代表unsigned char。C3代表3通道。
其他还有CV_32FC3等。看其他文章,这个数据在处理图像、储存图像时需要特别注意一下。
最后一种构造方法比较奇特,使用了C++当中stream来获取矩阵元素。
Mat使用方便,有些人也用它来进行矩阵运算。
另外Mat可以不止2个维度,可以任意设定,这个也是他设计比较好的以体现。
imread函数可以直接打开opencv兼容的图像,返回给Mat对象。
namedWindow("Original Picture", WINDOW_AUTOSIZE); namedWindow("Processed Picture", WINDOW_AUTOSIZE); //imshow("Display window", edges); //system("pause"); imshow("Original Picture", image); imshow("Processed Picture", edges);
下面是图像的显示,windows下封装成2个函数,非常简单。
值得注意的是namedWindow的第二个参数,可以设定图像是否随着窗口的大小改变大小。
然后是几个Matlab风格的矩阵。
Mat E = Mat::eye(3, 3, CV_32F); Mat O = Mat::ones(2, 2, CV_64FC1); Mat Z = Mat::zeros(3, 3, CV_8UC3); cout << "E=" << endl << E << endl; cout << "O=" << endl << O << endl; cout << "Z=" << endl << Z << endl;
分别是单位矩阵、全1矩阵、全0矩阵。
然后是引用矩阵的行,或者列。
//Create new matrix using row function an clone function. //Row index begin with 0. Like the C/C++ style arrays. Mat rowmat = C.row(1).clone(); cout << "rowmat = " << endl << rowmat << endl;
然后是randu函数,可以给矩阵当中每个元素一个随机值,随机值的范围可以设定。可以用来生成白噪声?
//randu function. Fill a matrix with random value. The upper limit and lower limit can be set. Mat R(4, 4, CV_32SC3); randu(R, Scalar::all(-100), Scalar::all(100)); cout << "R = " << endl << R << endl;
打印Mat时,可以使用format函数来设定不同的输出格式。
//Different output styles. //Notice the numpy styles. It has a additional element to mark the data type of the matrix. cout << "R(default) = " << endl << R << endl; cout << "R(Python) = " << endl << format(R, "python") << endl; cout << "R(CSV) = " << endl << format(R, "csv") << endl; cout << "R(numpy) = " << endl << format(R, "numpy") << endl; cout << "R(c) = " << endl << format(R, "c") << endl;
有python风格、numpy风格、CSV风格等。值得注意的是numpy风格时会自动输出矩阵当中元素类型。
//isContinuous function. Return if the matrix is stored as a single row in memory. if (R.isContinuous()) { cout << "R is stored in memory as a single big row." << endl; } else { cout << "R is stored in memory not as a single big row." << endl; }
这个isContinuous函数来获取矩阵的储存方式信息,如果矩阵是一一个大一维数据储存在内存里,那么就是true。
这个函数在判断矩阵储存方式之后,可以使得后面的循环便利矩阵变得方便。
//Output other opencv datas. //Point2F p1(3, 4); //cout << "Point2F p1 = " << endl << p1 << endl; //Initialize a matrix with a C++ vector. The matrix will be initialized to a column vector. vector<float> v1; v1.push_back((float)CV_PI); v1.push_back(2); v1.push_back(3); cout << "Mat(v1) = " << endl << Mat(v1) << endl;
在一个是使用C++当中vector来初始化矩阵,初始化之后的矩阵是一个列向量。
LookUpTable用来把原有图像进行单个像素级别的映射,通俗的说就是每个像素的新的值只由它自己决定,和他的邻居无关。因为色彩空间是有限的,所以可以通过查表来加快速度,而不需要使用Filter2D。
LUT(M, lookUpTable, T);
他的3个参数都是Mat对象。第一个是输入,第二个是映射表,第三个是输出。
比如输入图像时一个uchar 3通道彩色图像,那么映射表应该是一个有256元素的矩阵,1行256列。
LUT可以使用OpenCV的GPU加速还是什么东西,比手撸映射函数要快很对。
然后是OpenCV的掩码功能、卷积功能。
首先定义一个掩码或者核,然后遍历整个图像,计算新的图像。
和之前映射表不同的是掩码功能输出的结果是和其他像素有关系, 不只是和它自己有关系。
可以手撸一个掩码函数,也可以利用内置的Filte2D。
#include <iostream> #include <opencv2/core.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> using namespace std; using namespace cv; void Sharpen(const Mat& in, Mat& result) { CV_Assert(in.depth() == CV_8U); result.create(in.size(), in.type()); int channels = in.channels(); const uchar *previous, *next, *current; uchar* output; int rows = in.rows; int cols = in.cols; for (int i = 1; i < rows - 1; i++) { previous = in.ptr<uchar>(i - 1); current = in.ptr<uchar>(i); next = in.ptr<uchar>(i + 1); output = result.ptr<uchar>(i); for (int j = channels; j < channels * (cols - 1); j++) { //Conversion of uchar. Be careful! //saturate_cast<uchar>(int n): //if n > 255, then the result is 255. //if n < 0, then the result is 0. //This function is used to prevent uchar overflow. *output++ = saturate_cast<uchar>(5 * current[j] - current[j - channels] - current[j + channels] - previous[j] - next[j]); } } result.row(0) = Scalar(0); result.row(rows - 1) = Scalar(0); result.col(0) = Scalar(0); result.col(cols - 1) = Scalar(0); } int main(int argc, char** argv) { Mat In = imread(argv[1], IMREAD_COLOR); if (!In.data) { cout << "Load image failed!" << endl; return -1; } Mat Result; //Sharpen(In, Result); Mat kern = (Mat_<char>(3,3) << 0, -1 , 0, -1, 5 , -1, 0, -1 , 0); filter2D(In, Result, In.depth(), kern); namedWindow("Original", CV_WINDOW_AUTOSIZE); namedWindow("Sharpened", CV_WINDOW_AUTOSIZE); imshow("Original", In); imshow("Sharpened", Result); waitKey(0); return 0; }
另外一个值得注意的是saturate_cast这个东西,简单来说就是在进行从int到uchar转换时让图像不失真,把负数全算作0,大于255的数算作255。
效果还是很显著的。如果不用这个东西就会出现很多花点,想来是截断时让色彩发生了很大失真。
这些就是这几天的学习内容。
浙公网安备 33010602011771号