图像像素操作

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

访问像素值

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

实现方法

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

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);
处理后的结果,如下图所示:

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

完整代码:

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);
结果如下图所示:

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

完整代码

// 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/2
2.高效遍历连续图像

考虑到效率,图像有可能会在行尾扩大若干个像素。但是,值得注意的是当不对行进行填补的时候,图像可以被视为一个长为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));
}


关于Image Engineering & Computer Vision的更多讨论与交流,敬请关注本博客和新浪微博songzi_tea.

你可能感兴趣的:(opencv,图像像素)