图像像素操作



本文转自:http://blog.csdn.net/songzitea/article/details/8481443 谢谢博主!

上节,我们介绍了OPENCV与VS2008,Python2.7.5配置环境和图像载入,图像显示。本节主要探讨是:访问像素值、使用指针和迭代器遍历图像以及遍历图像和邻域操作。


访问像素值

为了访问 代码中指定元素所在的行和列。程序会返回相应的元素。如果是单通道的图像,返回值是单个数值;如查多通道的图像,返回值则是一组向量。

实现方法

我们创建一个椒盐现象的函数,第一个参数是一张输入图像,第二个参数是我们欲将其替换成白色像素点的像素点个数。

[cpp] view plain copy print ?
  1. void salt(cv::Mat &img, int n){  
  2.     forint k = 0; k < n; k++){  
  3.         int i = rand()%img.cols;  
  4.         int j = rand()%img.rows;  
  5.         if(img.channels( ) == 1){  
  6.             img.at<uchar>(j,i) = 255;  
  7.         }  
  8.         else if(img.channels( ) == 3){  
  9.             img.at<cv::Vec3b>(j,i)[0] =255;  
  10.             img.at<cv::Vec3b>(j,i)[1] =255;  
  11.             img.at<cv::Vec3b>(j,i)[2] =255;  
  12.         }  
  13.     }  
  14. }  
此处我们通过检查图的通道数来区分是GRAY图像和COLOR图像.这时,我们可以用imread()函数来载入一张图像,然后将在调用这个函数时候将些图像传递它。
[cpp] view plain copy print ?
  1. cv::Mat img = cv::imread("../../../waves.jpg");  
  2. salt(img,3000);  
  3. cv::namedWindow("Salt Window");  
  4. cv::imshow("Salt Window",img);  
处理后的结果,如下图所示:

图像像素操作_第1张图片

完整代码:

C++版

[cpp] view plain copy print ?
  1. // salt_image.cpp : Defines the entry point for the console application.  
  2.   
  3. #include "stdafx.h"  
  4. #include <Opencv2\opencv.hpp>  
  5.   
  6. void salt(cv::Mat &img, int n){  
  7.     forint k = 0; k < n; k++){  
  8.         int i = rand()%img.cols;  
  9.         int j = rand()%img.rows;  
  10.         if(img.channels( ) == 1){  
  11.             img.at<uchar>(j,i) = 255;  
  12.         }  
  13.         else if(img.channels( ) == 3){  
  14.             img.at<cv::Vec3b>(j,i)[0] =255;  
  15.             img.at<cv::Vec3b>(j,i)[1] =255;  
  16.             img.at<cv::Vec3b>(j,i)[2] =255;  
  17.         }  
  18.     }  
  19. }  
  20.   
  21. int _tmain(int argc, _TCHAR* argv[]){  
  22.     cv::Mat img = cv::imread("../../../waves.jpg");  
  23.     if(img.data){  
  24.         salt(img,3000);  
  25.         cv::namedWindow("Salt Window");  
  26.         cv::imshow("Salt Window",img);          
  27.                 cv::waitKey(0);  
  28.                 cv::destroyAllWindows()  
  29.         }  
  30.     else  
  31.         printf("Open Image is Error!");  
  32.     return 0;  
  33. }  
Python版
[python] view plain copy print ?
  1. import cv2    
  2. import numpy as np    
  3.     
  4. def salt(img, n):    
  5.     for k in range(n):    
  6.         i = int(np.random.random() * img.shape[1]);  
  7.         j = int(np.random.random() * img.shape[0]);  
  8.         if img.ndim == 2:     
  9.             img[j,i] = 255    
  10.         elif img.ndim == 3:     
  11.             img[j,i,0]= 255    
  12.             img[j,i,1]= 255    
  13.             img[j,i,2]= 255    
  14.     return img    
  15.     
  16. if __name__ == '__main__':    
  17.     img = cv2.imread("../waves.jpg")    
  18.     saltImage = salt(img, 3000)    
  19.     cv2.imshow("Salt", saltImage)  
  20.     cv2.imwrite("../wavessalt.jpg",saltImage)  
  21.     cv2.waitKey(0)    
  22.     cv2.destroyAllWindows()   

使用指针访问

我们在大多数的图像处理中,为了计算,需要遍历图像的所有像素。考虑到将要访问的像素个数非常之多,高效地遍历图像时非常重要的。

实现方法

首先我们定义一个颜色缩减函数原型如下:

[cpp] view plain copy print ?
  1. void colorReduce(cv::Mat &img,int div =64);  
整个处理过程通过一个双重循环来遍历所在的像素值:
[cpp] view plain copy print ?
  1. void colorReduce( cv::Mat &img, int div =32){  
  2.     int nl =img.rows;  
  3.     int nc = img.cols*img.channels();  
  4.   
  5.     for(int j =0; j< nl; j++){  
  6.         uchar* data = img.ptr<uchar>(j);  
  7.         for(int i=0; i<nc;i++){  
  8.             data[i] =data[i]/div*div+div/2;  
  9.         }  
  10.     }  
  11. }  
整个函数可以通过以下的代码片段测试
[cpp] view plain copy print ?
  1. cv::Mat img = cv::imread("../../../waves.jpg");  
  2. colorReduce(img);  
  3. cv::namedWindow("Reduce Window");  
  4. cv::imshow("Reduce Window",img);  
结果如下图所示:

图像像素操作_第2张图片

完整代码

[cpp] view plain copy print ?
  1. // Reduce_image.cpp : Defines the entry point for the console application.  
  2.   
  3. #include "stdafx.h"  
  4. #include <Opencv2\opencv.hpp>  
  5.   
  6. void colorReduce( cv::Mat &img, int div =32){  
  7.     int nl =img.rows;  
  8.     int nc = img.cols*img.channels();  
  9.   
  10.     for(int j =0; j< nl; j++){  
  11.         uchar* data = img.ptr<uchar>(j);  
  12.         for(int i=0; i<nc;i++){  
  13.             data[i] =data[i]/div*div+div/2;  
  14.         }  
  15.     }  
  16. }  
  17. int _tmain(int argc, _TCHAR* argv[]){  
  18.     cv::Mat img = cv::imread("../../../waves.jpg");  
  19.     colorReduce(img);  
  20.     cv::imwrite("Reduce.jpg",img);  
  21.   
  22.     cv::namedWindow("Reduce Window");  
  23.     cv::imshow("Reduce Window",img);  
  24.     cv::waitKey(0);  
  25.     cv::destroyAllWindows();  
  26.       
  27.     return 0;  
  28. }  

扩展知识

本例中提供的中是颜色缩减函数的一种实现方式,不必局限于此,可以使用其它的颜色缩减公式。我们也可以实现一个更通用的版本,它允许用户分别指定输入和输出图像。另外,图像遍历过程还可以通过利用图像数据的连续性,使得整个过程更高效。

1.其他的颜色缩减公式

我们可以选择使用位运算。

[cpp] view plain copy print ?
  1. //mask used to round the pixel value  
  2. uchar maks = 0xFF<<n; // e.g. for dive =16, mask =0xF0  
  3. data[i] =(data[i]&mask) +div/2  
2.高效遍历连续图像

考虑到效率,图像有可能会在行尾扩大若干个像素。但是,值得注意的是当不对行进行填补的时候,图像可以被视为一个长为WxH的一维数组。我们可以通过OpenCV中的CV::Mat的一个成员函数 isContinuous来判断这幅图像是否对行进行了填补。重写颜色缩减函数为

[cpp] view plain copy print ?
  1. void colorReduce( cv::Mat &img, int div =32){  
  2.     if(img.isContinuous()){  
  3.         img.reshape(1,img.cols*img.rows);  
  4.     }  
  5.     int nl =img.rows;  
  6.     int nc = img.cols*img.channels();  
  7.   
  8.     for(int j =0; j< nl; j++){  
  9.         uchar* data = img.ptr<uchar>(j);  
  10.         for(int i=0; i<nc;i++){  
  11.             data[i] =data[i]/div*div+div/2;  
  12.         }  
  13.     }  
  14. }  
这个方法在同时处理若干小图像时会很有优势。

3.底层的指针运算

在类 CV::Mat中,图像数据以unsigned char形成保存在一块内存中。即:

[cpp] view plain copy print ?
  1. uchar *data= img.data;  
  2. data =+img.step;  
  3. data =img.data +j*img.step+i*img.elemSize();  

但是,即使这种方式确实行之有效,但容易出错。

迭代器遍历

在面对象的编程中,遍历数据集合通常是通过迭代器完成的。所以,我们将重写颜色缩减函数为:

[cpp] view plain copy print ?
  1. void colorReduce( cv::Mat &img, int div =32){  
  2.     cv::Mat_<cv::Vec3b>::iterator it = img.begin<cv::Vec3b>();  
  3.     cv::Mat_<cv::Vec3b>::iterator itend = img.end<cv::Vec3b>();  
  4.   
  5.     for(; it!=itend; ++it){  
  6.         (*it)[0] =(*it)[0]/div*div +div/2;  
  7.         (*it)[1] =(*it)[1]/div*div +div/2;  
  8.         (*it)[2] =(*it)[2]/div*div +div/2;  
  9.     }  
  10. }  

遍历图像和邻域操作

在图像处理中,通过当前位置的相邻像素计算新的像素值是很常见的操作。当邻域包含图像的前几行和下几行时,你就需要同时扫描图像的若干行。本例子可以展示如何做到这一点。

[cpp] view plain copy print ?
  1. void sharpen(const cv::Mat &img,cv::Mat &result){  
  2.     result.create(img.size(),img.type());  
  3.     for(int j =1; j<img.rows-1;j++){  
  4.         const uchar* previous = img.ptr<const uchar>(j-1);  
  5.         const uchar* current = img.ptr<const uchar>(j);  
  6.         const uchar* next = img.ptr<const uchar>(j+1);  
  7.         const uchar* output = result.ptr<const uchar>(j);  
  8.         for(int i =1; i<img.cols-1;i++){  
  9.             *output++= cv::saturate_cast<uchar>(  
  10.                 5*current[i]-current[i-1]  
  11.                 -current[i+1]-previous[i]-next[i]);  
  12.         }  
  13.     }  
  14.     result.row(0).setTo(cv::Scalar(0));  
  15.     result.row(result.rows-1).setTo(cv::Scalar(0));  
  16.     result.col(0).setTo(cv::Scalar(0));  
  17.     result.col(result.rows-1).setTo(cv::Scalar(0));  
  18. }  

你可能感兴趣的:(图像像素操作)