访问像素值
为了访问 代码中指定元素所在的行和列。程序会返回相应的元素。如果是单通道的图像,返回值是单个数值;如查多通道的图像,返回值则是一组向量。
实现方法
我们创建一个椒盐现象的函数,第一个参数是一张输入图像,第二个参数是我们欲将其替换成白色像素点的像素点个数。
- 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/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));
- }