目录
Github代码地址:GitHub - Qinong/OpenCV
第3章 像素的访问与扫描
3.1 简介
图像本质上就是一个由数值组成的矩阵。因此,OpenCV 使用了 cv::Mat 结构来操作图像,矩阵中的每个元素表示一个像素。对灰度图像而言,像素是8位无符号数(数据类型为 unsigned char),0表示黑色,255 表示白色。对彩色图像而言,需要用三原色数据来重现不同的可见色。意味着彩色图像的每个像素都要对应三个数值,常用的主颜色通道是红色、绿色和蓝色,因此每三个8位数值组成短阵的一个元素,有些特殊的应用程序需要用16位通道(例如医学图像)。
3.2 访问像素值
cv::Mat 类包含多种方法,可用来访问图像的各种属性,利用公共成员变量 cols 和rows
可得到图像的列数和行数;利用cv::Mat 的at (int y,int x)方法可以访问元素,其中x是列号,y是行号。在编译时必须明确方法返回值的类型,因为 cv::Mat 可以接受任何类型的元素,所以程序员需要指定返回值的预期类型。
在调用at 方法时,你必须指定图像元素的类型,例如:
image.at<uchar>(j,i)=255;
注意:必须保证指定的类型与矩阵内的类型是一致的,at 方法不会进行任何类型转换。
彩色图像的每个像素对应三个部分:红色通道、绿色通道和蓝色通道,因此包含彩色图像的cv::Mat类会返回一个向量,向量中包含三个8位的数值。OpenCV 为这样的短向量定义了一种类型,即cv::Vec3b,向量包含三个无符号宇符( unsigned character) 类型的数据。因此,访问彩色像素中元素的方法如下所示:
image.at<cv::Vec3b>(j,i)[channel]= value;
channel素引用来指明三个颜色的通道。OpenCV 存储通道数据的次序是蓝色、绿色和红色(因此蓝色是通道 0)。也可以直接使用短向量,方法如下所示:
image.at<cv::Vec3b>(j, i)=cv::Vec3b(255,255,255);
还有类似的向量类型用来表示二元素向量和四元素向量(cv::Vec2b 和cv::Vec4b),此外还有针对其他元素类型的向量。
(1)对图像增加白噪声
// 对图像添加白噪声
void salt(cv::Mat image, int n) {
// C++11随机数生成器
std::default_random_engine generator;
std::uniform_int_distribution<int> randomRow(0, image.rows - 1);
std::uniform_int_distribution<int> randomCol(0, image.cols - 1);
int i,j;
for (int k=0; k<n; k++) {
// 随机生成图形位置
i= randomCol(generator);
j= randomRow(generator);
if (image.type() == CV_8UC1) // 灰度图像
{
image.at<uchar>(j,i)= 255;
}
else if (image.type() == CV_8UC3) // 彩色图像
{
image.at<cv::Vec3b>(j,i)[0]= 255;
image.at<cv::Vec3b>(j,i)[1]= 255;
image.at<cv::Vec3b>(j,i)[2]= 255;
// or
// image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);
}
}
}
3.3 用指针扫描图像
为了简化指针运算的计算过程,cv::vat 类提供了一个方法,可以直接访问图像中一行的起始地址。这就是ptr方法,它是一个模板方法,返回第j行的地址:
void colorReduce(cv::Mat image) {
int nl= image.rows; // 图像的行数
int nc= image.cols * image.channels(); // 图像每行的元素数量
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j); // 取第j行的地址
for (int i=0; i<nc/2; i++) // 处理每个像素
{
data[i]= 255
}
}
}
3.4 用迭代器扫描图像
在面向对象编程时,我们通常用迭代器对数据集合进行循环遍历。迭代器是一种类,专门用于遍历集合的每个元素,并能隐藏遍历过程的具体细节。信息隐藏原则的应用,使扫描集合的过程变得更加容易和安全。标淮模板库(Standard Template Library, STL)对每个集合类都定义了对应的迭代器类,OpenCV 也提供了ev::Mat 的迭代器类,并且与C++ STL 中的标准迭代器兼容。
要得到 cv::Mat 实例的迭代器,首先要创建一个cv::MatIterator_对象,下划线表示它是一个模板子类。因为图像迭代器是用来访问图像元素的,所以必须在编译时就明确返回值的类型。可以这样定义彩色图像的迭代器:
cv::MatIterator_<cv::Vec3b> it;
也可以使用在Mat_模板类内部定义的iterator类型:
cv::Mat_<cv::Vec3b>::iterator it;
然后就可以使用常规的选代器方法 begin 和 end 对像素进行循环遍历。
// 使用cv::Mat_<cv::Vec3b>::iterator迭代器
void colorReduce2(cv::Mat image, int div=64) {
cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
uchar div2 = div >> 1; // div2 = div/2
for ( ; it!= itend; ++it) {
(*it)[0]= (*it)[0]/div*div + div2;
(*it)[1]= (*it)[1]/div*div + div2;
(*it)[2]= (*it)[2]/div*div + div2;
}
}
// 使用cv::MatIterator_<cv::Vec3b>迭代器
void colorReduce3(cv::Mat image, int div=64) {
cv::MatIterator_<cv::Vec3b> it= image.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> itend= image.end<cv::Vec3b>();
const cv::Vec3b offset(div/2,div/2,div/2);
for ( ; it!= itend; ++it) {
*it= *it/div*div+offset;
}
}
3.5 计算函数的运行时间
OpenCV 有一个非常实用的丽数 cv::getTickcount()可以用来测算两数或代码段的运行时间。在代码开始和结束时记录时钟周期数,就可以计算代码的运行时间。若想得到以秒为单位的代码运行时间,可使用另一个方法 cv::getTickFrequenoy(),它返回每秒的时钟周期数。
为了获得某个函数(或代码段)的运行时间,通常需使用如下的程序模板:
const int64 start = cv::getTickCount();
...... ; //调用函数
double duration = (cv::getTickcount()-start)/cv:: getTickFrequency();
3.6 完整代码
(1)代码1
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <random>
// 对图像添加白噪声
void salt(cv::Mat image, int n) {
// C++11随机数生成器
std::default_random_engine generator;
std::uniform_int_distribution<int> randomRow(0, image.rows - 1);
std::uniform_int_distribution<int> randomCol(0, image.cols - 1);
int i,j;
for (int k=0; k<n; k++) {
// 随机生成图形位置
i= randomCol(generator);
j= randomRow(generator);
if (image.type() == CV_8UC1) // 灰度图像
{
image.at<uchar>(j,i)= 255;
}
else if (image.type() == CV_8UC3) // 彩色图像
{
image.at<cv::Vec3b>(j,i)[0]= 255;
image.at<cv::Vec3b>(j,i)[1]= 255;
image.at<cv::Vec3b>(j,i)[2]= 255;
// or
// image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);
}
}
}
int main()
{
cv::Mat image1 = cv::imread("Ferrar_F8.png",1);
cv::Mat image2 = image1.clone();
cv::namedWindow("Image1");
cv::imshow("Image1",image1);
salt(image2,6000); // 调用噪声函数
cv::namedWindow("Image2");
cv::imshow("Image2",image2);
cv::waitKey();
return 0;
}
(2)代码2
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
// 更改指定位置的像素值
void colorReduce1(cv::Mat image) {
int nl= image.rows; // 图像的行数
int nc= image.cols * image.channels(); // 图像每行的元素数量
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j); // 取第j行的地址
for (int i=0; i<nc/2; i++) // 处理每个像素
{
data[i]= 255;
}
}
}
// 使用cv::Mat_<cv::Vec3b>::iterator迭代器
void colorReduce2(cv::Mat image, int div=64) {
cv::Mat_<cv::Vec3b>::iterator it= image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itend= image.end<cv::Vec3b>();
uchar div2 = div >> 1; // div2 = div/2
for ( ; it!= itend; ++it) {
(*it)[0]= (*it)[0]/div*div + div2;
(*it)[1]= (*it)[1]/div*div + div2;
(*it)[2]= (*it)[2]/div*div + div2;
}
}
// 使用cv::MatIterator_<cv::Vec3b>迭代器
void colorReduce3(cv::Mat image, int div=64) {
cv::MatIterator_<cv::Vec3b> it= image.begin<cv::Vec3b>();
cv::MatIterator_<cv::Vec3b> itend= image.end<cv::Vec3b>();
const cv::Vec3b offset(div/2,div/2,div/2);
for ( ; it!= itend; ++it) {
*it= *it/div*div+offset;
}
}
int main()
{
cv::Mat image = cv::imread("Ferrar_F8.png");
cv::Mat image1 = image.clone();
cv::Mat image2 = image.clone();
cv::Mat image3 = image.clone();
cv::namedWindow("Image",0);
cv::imshow("Image", image);
cv::waitKey();
// 调用函数并记录处理图像的时间
colorReduce1(image1);
cv::namedWindow("Image1",0);
cv::imshow("Image1", image1);
cv::waitKey();
// 调用函数并记录处理图像的时间
colorReduce2(image2);
cv::namedWindow("Image2",0);
cv::imshow("Image2", image2);
cv::waitKey();
// 调用函数并记录处理图像的时间
colorReduce3(image3);
cv::namedWindow("Image3",0);
cv::imshow("Image3", image3);
cv::waitKey();
return 0;
}