为了访问 代码中指定元素所在的行和列。程序会返回相应的元素。如果是单通道的图像,返回值是单个数值;如查多通道的图像,返回值则是一组向量。
我们创建一个椒盐现象的函数,第一个参数是一张输入图像,第二个参数是我们欲将其替换成白色像素点的像素点个数。
void salt(cv::Mat &img, int n){ for( int k = 0; k < n; k++){ int i = rand()%img.cols; int j = rand()%img.rows; if(img.channels( ) == 1){ img.at<uchar>(j,i) = 255; } else if(img.channels( ) == 3){ img.at<cv::Vec3b>(j,i)[0] =255; img.at<cv::Vec3b>(j,i)[1] =255; img.at<cv::Vec3b>(j,i)[2] =255; } } }此处我们通过检查图的通道数来区分是GRAY图像和COLOR图像.这时,我们可以用imread()函数来载入一张图像,然后将在调用这个函数时候将些图像传递它。
cv::Mat img = cv::imread("../../../waves.jpg"); salt(img,3000); cv::namedWindow("Salt Window"); cv::imshow("Salt Window",img);处理后的结果,如下图所示:
C++版
// salt_image.cpp : Defines the entry point for the console application. #include "stdafx.h" #include <Opencv2\opencv.hpp> void salt(cv::Mat &img, int n){ for( int k = 0; k < n; k++){ int i = rand()%img.cols; int j = rand()%img.rows; if(img.channels( ) == 1){ img.at<uchar>(j,i) = 255; } else if(img.channels( ) == 3){ img.at<cv::Vec3b>(j,i)[0] =255; img.at<cv::Vec3b>(j,i)[1] =255; img.at<cv::Vec3b>(j,i)[2] =255; } } } int _tmain(int argc, _TCHAR* argv[]){ cv::Mat img = cv::imread("../../../waves.jpg"); if(img.data){ salt(img,3000); cv::namedWindow("Salt Window"); cv::imshow("Salt Window",img); cv::waitKey(0); cv::destroyAllWindows() } else printf("Open Image is Error!"); return 0; }Python版
import cv2 import numpy as np def salt(img, n): for k in range(n): i = int(np.random.random() * img.shape[1]); j = int(np.random.random() * img.shape[0]); if img.ndim == 2: img[j,i] = 255 elif img.ndim == 3: img[j,i,0]= 255 img[j,i,1]= 255 img[j,i,2]= 255 return img if __name__ == '__main__': img = cv2.imread("../waves.jpg") saltImage = salt(img, 3000) cv2.imshow("Salt", saltImage) cv2.imwrite("../wavessalt.jpg",saltImage) cv2.waitKey(0) cv2.destroyAllWindows()
我们在大多数的图像处理中,为了计算,需要遍历图像的所有像素。考虑到将要访问的像素个数非常之多,高效地遍历图像时非常重要的。
首先我们定义一个颜色缩减函数原型如下:
void colorReduce(cv::Mat &img,int div =64);整个处理过程通过一个双重循环来遍历所在的像素值:
void colorReduce( cv::Mat &img, int div =32){ int nl =img.rows; int nc = img.cols*img.channels(); for(int j =0; j< nl; j++){ uchar* data = img.ptr<uchar>(j); for(int i=0; i<nc;i++){ data[i] =data[i]/div*div+div/2; } } }整个函数可以通过以下的代码片段测试
cv::Mat img = cv::imread("../../../waves.jpg"); colorReduce(img); cv::namedWindow("Reduce Window"); cv::imshow("Reduce Window",img);结果如下图所示:
// Reduce_image.cpp : Defines the entry point for the console application. #include "stdafx.h" #include <Opencv2\opencv.hpp> void colorReduce( cv::Mat &img, int div =32){ int nl =img.rows; int nc = img.cols*img.channels(); for(int j =0; j< nl; j++){ uchar* data = img.ptr<uchar>(j); for(int i=0; i<nc;i++){ data[i] =data[i]/div*div+div/2; } } } int _tmain(int argc, _TCHAR* argv[]){ cv::Mat img = cv::imread("../../../waves.jpg"); colorReduce(img); cv::imwrite("Reduce.jpg",img); cv::namedWindow("Reduce Window"); cv::imshow("Reduce Window",img); cv::waitKey(0); cv::destroyAllWindows(); return 0; }
本例中提供的中是颜色缩减函数的一种实现方式,不必局限于此,可以使用其它的颜色缩减公式。我们也可以实现一个更通用的版本,它允许用户分别指定输入和输出图像。另外,图像遍历过程还可以通过利用图像数据的连续性,使得整个过程更高效。
1.其他的颜色缩减公式
我们可以选择使用位运算。
//mask used to round the pixel value uchar maks = 0xFF<<n; // e.g. for dive =16, mask =0xF0 data[i] =(data[i]&mask) +div/22.高效遍历连续图像
考虑到效率,图像有可能会在行尾扩大若干个像素。但是,值得注意的是当不对行进行填补的时候,图像可以被视为一个长为WxH的一维数组。我们可以通过OpenCV中的CV::Mat的一个成员函数 isContinuous来判断这幅图像是否对行进行了填补。重写颜色缩减函数为
void colorReduce( cv::Mat &img, int div =32){ if(img.isContinuous()){ img.reshape(1,img.cols*img.rows); } int nl =img.rows; int nc = img.cols*img.channels(); for(int j =0; j< nl; j++){ uchar* data = img.ptr<uchar>(j); for(int i=0; i<nc;i++){ data[i] =data[i]/div*div+div/2; } } }这个方法在同时处理若干小图像时会很有优势。
3.底层的指针运算
在类 CV::Mat中,图像数据以unsigned char形成保存在一块内存中。即:
uchar *data= img.data; data =+img.step; data =img.data +j*img.step+i*img.elemSize();
但是,即使这种方式确实行之有效,但容易出错。
在面对象的编程中,遍历数据集合通常是通过迭代器完成的。所以,我们将重写颜色缩减函数为:
void colorReduce( cv::Mat &img, int div =32){ cv::Mat_<cv::Vec3b>::iterator it = img.begin<cv::Vec3b>(); cv::Mat_<cv::Vec3b>::iterator itend = img.end<cv::Vec3b>(); for(; it!=itend; ++it){ (*it)[0] =(*it)[0]/div*div +div/2; (*it)[1] =(*it)[1]/div*div +div/2; (*it)[2] =(*it)[2]/div*div +div/2; } }
在图像处理中,通过当前位置的相邻像素计算新的像素值是很常见的操作。当邻域包含图像的前几行和下几行时,你就需要同时扫描图像的若干行。本例子可以展示如何做到这一点。
void sharpen(const cv::Mat &img,cv::Mat &result){ result.create(img.size(),img.type()); for(int j =1; j<img.rows-1;j++){ const uchar* previous = img.ptr<const uchar>(j-1); const uchar* current = img.ptr<const uchar>(j); const uchar* next = img.ptr<const uchar>(j+1); const uchar* output = result.ptr<const uchar>(j); for(int i =1; i<img.cols-1;i++){ *output++= cv::saturate_cast<uchar>( 5*current[i]-current[i-1] -current[i+1]-previous[i]-next[i]); } } result.row(0).setTo(cv::Scalar(0)); result.row(result.rows-1).setTo(cv::Scalar(0)); result.col(0).setTo(cv::Scalar(0)); result.col(result.rows-1).setTo(cv::Scalar(0)); }