OpenCV计算机视觉编程攻略(第二章)部分没看完

我们将创建一个简单的函数,用它在图像中加入椒盐噪声

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) { // 灰度图像
// 单通道 8 位图像
image.at<uchar>(j,i)= 255;
} else if (image.type() == CV_8UC3) { // 彩色图像
// 3 通道图像
image.at<cv::Vec3b>(j,i)[0]= 255;
image.at<cv::Vec3b>(j,i)[1]= 255;
image.at<cv::Vec3b>(j,i)[2]= 255;
}
}
}
操作像素
if (image.type() == CV_8UC1) { // 灰度图像
// 单通道 8 位图像
image.at<uchar>(j,i)= 255;
} else if (image.type() == CV_8UC3) { // 彩色图像
// 3 通道图像
image.at<cv::Vec3b>(j,i)[0]= 255;//蓝
image.at<cv::Vec3b>(j,i)[1]= 255;//绿	
image.at<cv::Vec3b>(j,i)[2]= 255;//红
}

OpenCV 为这样的短向量定义了一种类型,即 cv::Vec3b。这个向量包含三个无符号字符( unsigned character)类型的数据。

有一点需要特别注意,程序员必须保证指定的类型与矩阵内的类型是一致的。 at 方法不会进行任何类型转换
对于三通道彩色图像也可以这样访问像素:

image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255);

还有类似的向量类型用来表示二元素向量和四元素向量( cv::Vec2b 和 cv::Vec4b)。此外还有针对其他元素类型的向量。例如,表示二元素浮点数类型的向量就是把类型名称的最后个字母换成 f,即 cv::Vec2f。对于短整型,最后的字母换成 s;对于整型,最后的字母换成 i;对于双精度浮点数向量,最后的字母换成 d。所有这些类型都用 cv::Vec<T,N>模板类定义,其中 T 是类型, N 是向量元素的数量。

cv::Mat_模板类访问像素值:
// 用 Mat 模板操作图像
cv::Mat_ img(image);
img(50,100)= 0; // 访问第 50 行、第 100 列处那个值

2.3 用指针扫描图像

void colorReduce(cv::Mat image, int div=64) {
int nl= image.rows; // 行数
// 每行的元素数量
int nc= image.cols * image.channels();//三通道图像为每行像素个数的三倍
for (int j=0; j<nl; j++) {
// 取得行 j 的地址
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
// 处理每个像素 ---------------------
data[i]= data[i]/div*div + div/2;
// 像素处理结束 ----------------
} // 一行结束
}
}

在彩色图像中,图像数据缓冲区的前 3 字节表示左上角像素的三个通道的值,接下来的 3字节表示第 1 行的第 2 个像素,以此类推(注意 OpenCV 默认的通道次序为 BGR)。一个宽 W高 H 的图像所需的内存块大小为 W×H×3 uchars。不过出于性能上的考虑,我们会用几个额外的像素来填补行的长度。这是因为,如果行数是某个数字(例如 8)的整数倍,图像处理的性
能可能会提高,因此最好根据内存配置情况将数据对齐。当然,这些额外的像素既不会显示也不被保存,它们的额外数据会被忽略。
OpenCV 把经过填充的行的长度指定为有效宽度。
如果图像没有用额外的像素填充,那么有效宽度就等于实际的图像宽度。我们已经学过,用 cols和 rows 属性可得到图像的宽度和高度。
与之类似,用 step 数据属性可得到单位是字节的有效宽度。即使图像的类型不是 uchar, step 仍然能提供行的字节数。

为了简化指针运算的计算过程, cv::Mat 类提供了一个方法,可以直接访问图像中一行的
起始地址。这就是 ptr 方法,它是一个模板方法,返回第 j 行的地址:
uchar* data= image.ptr<uchar>(j);
*data++= *data/div*div + div2;//解引用

构建一个大小和类型都与输入图像相同的矩阵(如果必要):

result.create(image.rows,image.cols,image.type())

3. 对连续图像的高效扫描
//检查图像是否能看成一个一行矩阵
前面解释过,为了提高性能,可以在图像的每行末尾用额外的像素进行填充。有趣的是,在去掉填充后,图像仍可被看作一个包含 W×H 像素的长一维数组。用 cv::Mat 的isContinuous方法可轻松判断图像有没有被填充。如果图像中没有填充像素,它就返回 true。我们还能这样测试矩阵的连续性:

// 检查行的长度(字节数)与“列的个数×单个像素”的字节数是否相等
image.step == image.cols*image.elemSize();
void colorReduce(cv::Mat image, int div=64) {
int nl= image.rows; // 行数
// 每行的元素总数
int nc= image.cols * image.channels();
if (image.isContinuous()) {
// 没有填充的像素
nc= nc*nl;
nl= 1; // 它现在成了一个一维数组
}
int n= staic_cast<int>(log(static_cast<double>(div))/log(2.0) + 0.5);
// 用来截取像素值的掩码
uchar mask= 0xFF<<n; // 如果 div=16, 那么 mask= 0xF0
uchar div2 = div >> 1; // div2 = div/2
// 对于连续图像,这个循环只执行一次
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
*data &= mask;
*data++ += div2;
} // 一行结束
}
}

OpenCV 有一个非常实用的函数可以用来测算函数或代码段的运行时间,它就是 cv::getTickCount(),该函数会返回从最近一次计算机开机到当前的时钟周期数。在代码开始和结束时记录这个时钟周期数,就可以计算代码的运行时间。若想得到以秒为单位的代码运行时间,可使用另一个方法 cv::getTickFrequency(),它返回每秒的时钟周期数,这里假定 CPU的频率是固定的(对于较新的 CPU,频率并不一定是固定的)。为了获得某个函数(或代码段)的运行时间,通常需使用这样的程序模板:

const int64 start = cv::getTickCount();
colorReduce(image); // 调用函数
// 经过的时间(单位:秒)
double duration = (cv::getTickCount()-start)/
cv::getTickFrequency();

2.6 2.7 2.8 组要重新看一遍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值