本节导读:
Introduction
Fundamentally, an image is a matrix of numerical values.
Accessing pixel values(存取像素值)
salt-and-pepper noise(椒盐噪声)
Definition
Salt-and-pepper noise is a particular type of noise in which some pixels are replaced by a white or a black pixel.(This type of noise can occur in faulty communication when the value of some pixels is lost during transmission)
Add salt-and-pepper noise to an image
1 | void salt(cv::Mat &image, int n) |
The Difference of gray-level image and color image
In the case of a gray-level image, the number 255 is assigned to the single 8-bit value. For a color image, you need to assign 255 to the three primary color channels in order to obtain a white pixel.
How to access the different attributes of an image.
For element access, cv::Mat has the method at(int y, int x).但是返回类型必须要在编译的时候知道,所以程序员必须给定类型,这也是为什么 at method 用模版来实现,所以调用at method用如下方式,举例说明:1
image.at<uchar>(j, i) = 255;
这里我们需要保证指定的类型是矩阵中的类型,对于上述例子,必须保证uchar是image的元素类型。In color images, each pixel is associated with three components: the red, green, and blue channels.Therefore, a cv::Mat containing a color image will return a vector of three 8-bit values.
opencv定义了short vectors :cv::Vec3b.It is a vector of 3 unsigned chars.1
image.at<cv::Vec3b>(j,i)[channel]= value;
2-element and 4-element vectors(cv::Vec2b and cv::Vec4b)s:short, i:int, f:float, d:double, 定义成模版类cv::Vec<T,N> where T is the type and N is the number of vector element.1
typedef Vec<double, 3> Vt;
Use cv::Mat_ to access pixels
Using at method is cumbersome(笨重的;累赘的;难处理的)because the returned type must be specified as a template argument for each call. So it is possible to use the cv::Mat_ class which is a template subclass of cv::Mat.
Among the extra methods, there is the operator () allowing direct access to matrix elements. Therefore, if image is a reference to a uchar matrix, then one can write:1
2cv::Mat_<uchar> im2= image; // im2 refers to image
im2(50,100)= 0; // access to row 50 and column 100
cv::Mat_的元素类型在创建实例的时候已经声明,操作符()在编译期就知道需要返回的数据类型。他和at返回值也是一样的,所以使用()看起来更加简洁方便一点。
Scanning an image with pointers(使用指针遍历图像)
In-place transformation
Definition: The transformation is directly applied to the input image, which is called an in-place transformation.
双重循环遍历像素
代码
1 | int nl = image.rows; //number of lines |
这里颜色降低的原理就是降低维度,一般彩色图像是0~255,现在变成32个维度,即某通道的强度值是0~7时,用4来表示,为8~15时,用12来表示,大概就是这么个意思,我觉得没必要去细究。
作用原理
Opencv中默认使用BGR的通道顺序,因此第一个通道通常是蓝色的。一个W*H的彩色图像,需要W*H*3个uchar构成的内存块。但是处于效率的考虑,每行可能会填补一些额外像素,因为行的长度是4或8的倍数,一些多媒体处理芯片(如Intel的MMX架构)可以更高校的处理图像。这些额外的像素不会被显示或者保存,填补的值会被忽略。opencv将填补后的一行的长度指定为关键字。如果没有对图像进行填补,图像的有效长度就等于图像的真实宽度。step代表以字节为单位的图像的有效宽度。即使图像类型元素不是uchar,step依然代表着一行的字节数。
像素的大小可以有elemSize method得到:对于一个三通道的short型矩阵(CV_16SC3),elemSize返回6。
通道数可以由channels method得到:例如:int nc = image.cols * image.channels();
为了简化指针运算,cv::Mat提供了ptr函数可以得到图像任意行的首地址。如下:1
uchar *data = image.ptr<uchar>(j); //返回第j行的首地址
此外,之前提到的颜色缩减(降低图像颜色数目),还可以通过位运算实现,如下:(当作一个编程技巧)1
2uchar mask = 0xFF << n; //e.g. for div = 16, mask = 0xF0
data[i] = (data[i] & mask) + div/2; //这种实现方法就要比上面的data[i] = data[i]/div*div + div/2;高明了
高效遍历连续图像
考虑到效率,图像有可能在行尾补充若干个像素,但是当不填补的时候,图像可以被视为一个常委W*H的一位数组。高效遍历连续图像的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void colorReduce(cv::Mat &image, int div = 64)
{
int nl = image.rows;
int nc = image.cols * image.channels();
if(image.isContinus()) //判断是否为连续图像,即若不包含填充像素,则返回true
{
nc = nc * nl;
nl = 1;
}
for(int j = 0; j < nl; ++j)
{
uchar *data = image.ptr<uchar>(j);
for(int i = 0; i < nc; ++i)
{
data[i] = data[i]/2;
}
}
}
或者使用reshape来完成上述功能1
2
3
4
5
6if(image.isContinus())
{
image.reshape(1, image.cols * image.rows);
}
int nl = image.rows;
int nc = image.cols * image.channels();
reshape不需要内存拷贝或者重新分配就能改变矩阵的维度。上述方法在处理小图像的时候很有优势。(书上说的,至于为啥,我猜是调用uchar *data = image.ptr<uchar>(j);每个图像只用一次?)
底层指针运算
在类cv::Mat中,图像数据以unsigned char 形式保存在一块内存中。(那其他类型的也是这样?感觉有点问题)这块内存的首地址可以通过data成员变量得到,如下方式:1
2uchar *data = image.data; //图像的首地址指针
data += image.step; //到下一行
还可以通过如下方式或许第j行,第i列的像素地址:1
2//(j,i)处的像素地址为&image.at(j, i);
data = image.data + j * image.step + i * image.elemSize();
但是不建议用这种方式(书上说不建议的),不适用regions of interest
Scanning an image with iterators(使用迭代器遍历图像)
OpenCV为cv::Mat提供了与STL迭代器兼容的迭代器。
实现方法
一个cv::Mat实例的迭代器可以通过创建一个cv::MatIterator_的实例来得到。类似于子类cv::Mat_,下划线意味着cv::MatIterator_是一个模板类,用迭代器来存取图像的元素,必须在编译期就要知道图像元素的数据类型。1
cv::MatIterator_<cv::Vec3b> it;
或者使用定义在Mat_内部的迭代器类型:1
cv::Mat_<cv::Vec3b>::iterator it;
重写颜色缩减函数如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void colorReduce(cv::Mat &imgCopy, int div = 64)
{
//下面这两种方法都可以
// Mat_<Vec3b>::iterator it = imgCopy.begin<Vec3b>();
// Mat_<Vec3b>::iterator itend = imgCopy.end<Vec3b>();
MatIterator_<Vec3b> it = imgCopy.begin<Vec3b>();
MatIterator_<Vec3b> itend = imgCopy.end<Vec3b>();
for(; it != itend; ++it)
{
(*it)[0] = (*it)[0] / 2; //上面代码除以div啥的,我这里只是为了示范迭代器使用,就没有写那么麻烦
(*it)[1] = (*it)[1] / 2;
(*it)[2] = (*it)[2] / 2;
//处理的是彩色图像,所以这里迭代器返回的是cv::Vec3b,每个颜色分量可以通过操作符[]得到。
}
//可以不用for循环,用while
/*
while(it != itend)
{
····
++it;
}
*/
}
有时候不放心,可以用cv::MatConstIterator_,防止后面自己手贱。此外我们在用迭代器访问的时候,可能会觉得使用模板函数begin每次需要指定类型,很麻烦,这里有种更简洁的方法,可以通过cv::Mat_的实例来获得。1
2
3cv::Mat_<cv::Vec3b> cimage = image;
cv::Mat_<cv::Vec3b>::iterator it = cimage.begin();
cv::Mat_<cv::Vec3b>::iterator itend = cimage.end();