opencv 2 computer vision application programming第二章翻译

  1. 云栖社区>
  2. 博客>
  3. 正文

opencv 2 computer vision application programming第二章翻译

lovedan 2013-04-30 15:17:00 浏览260 评论0

摘要: 第二章 操作像素在本章,我们会讲述:处理像素值用指针扫描图像用迭代器扫描图像写高效的图像扫描循环用相邻的方法扫描图像展示简单的图像计算定义感兴趣的区域【概述】为了建立计算机图像应用,你必须能够接触图像内容,并且最终修改或者创建图像。

第二章 操作像素
在本章,我们会讲述:
处理像素值
用指针扫描图像
用迭代器扫描图像
写高效的图像扫描循环
用相邻的方法扫描图像
展示简单的图像计算
定义感兴趣的区域
【概述】
为了建立计算机图像应用,你必须能够接触图像内容,并且最终修改或者创建图像。这一章中会教你如何操作图像元素,比如像素。你会学
习到如何扫描一幅图像并处理每个像素点。你也会学习到如何高效地做,因为就算是适当的维度的图像也会包含成千上万的像素的。

基本上将,一个图像时一个数值对应的矩阵。这就是OpenCV2用cv::Mat处理图像的原因了。矩阵中的每一个元素代表一个像素。对于一个灰
度图像(黑白图像),像素值是8位的无符号型值(也就是非负数。。),相应地,0代表黑色而255代表白色。对于彩色图像,每个像素的三
个这样的值代表着我们常常说的三原色(红,绿,蓝)。此时一个矩阵元素由三个值生成。

正如前面所讲,OpenCV也允许你用不同类型的像素值创建图像,比如CV_8U或者浮点值CV_32F。这些对于存储很有用的,例如在一些图像处理
过程中的起到媒介作用的图像。大多数操作可以被应用到任何类型的矩阵上,其他的则是需要特定的类型,或者只能和给定数量的通道数量
起作用。所以说,为了避免在编程中犯常见错,对于一个函数或者方法的前提的理解是很重要的。

整个这一章节,我们用如下的一张图片作为输入图片。


【处理像素值】
为了处理矩阵中的每一个元素,你晋级你需要确定其行数和列数。相应的元素(可以是单独的数值也可以是多个通道的图像的向量值),会
被作为返回值返回的。

为了说明对于像素值的直接操作,我们会创建一个简单的函数,它对图像增加了胡椒盐噪声。正如这个名字所暗示的,胡椒盐噪声是噪声中
特定的一种,图像中的一些像素会被黑色或者白色像素替代。这种错误在传输错误的时候会发现,是某些像素值丢失导致的。此情形下,我
们简单地随机选择一些像素并设定为白色。


我们写一个函数用于接收输入的图像。这个图像会被此函数修改。为了这个目的,我们用传递引用的方式。第二个参数是像素的编号,我们
将把这个像素值置为白色:

此函数由一重循环组成,执行了n次,每次把255(对应白色)赋值给一个随机像素点。我们对于灰度图像和彩色图像区别对待,彩图需要分
别赋值给三个channel。

 

 1 void salt(cv::Mat &image, int n){
 2     for(int k=0; k<n; k++){
 3         //rand()是MFC随机函数生成器
 4         //在Qt中请使用qrand()
 5         int i=qrand()%image.cols;
 6         int j=qrand()%image.rows;
 7         
 8         if(image.channels()==1){//对应灰度图像
 9             image.at<uchar>(j, i)=255;
10         }else if(image.channels()==3){//彩色图像
11             image.at<cv::Vec3b>(j, i)[0]=255;
12             image.at<cv::Vec3b>(j, i)[1]=255;
13             image.at<cv::Vec3b>(j, i)[2]=255;
14         }
15     }
16 }

 

你可以通过先前打开过的一张图片作为参数传递给此函数:

 

1 //open the image
2 cv::Mat image = cv::imread("boldt.jpg");
3 
4 //call function to add noise
5 salt(image, 3000);
6 
7 //display image
8 cv::namedWindow("Image");
9 cv::imshow("Image", image);

 

 

发现qrand()不能识别。最后还是用了rand():

 1 #include <cv.h>
 2 #include <highgui.h>
 3 
 4 using namespace std;
 5 using namespace cv;
 6 
 7 void salt(Mat& image, int n){
 8     for(int k=0; k<n; k++){
 9         int i=rand()%image.cols;
10         int j=rand()%image.rows;
11 
12         if(image.channels()==1){
13             image.at<uchar>(j, i)=255;
14         }else if(image.channels()==3){
15             image.at<Vec3b>(j, i)[0]=255;
16             image.at<Vec3b>(j, i)[1]=255;
17             image.at<Vec3b>(j, i)[2]=255;
18         }
19     }
20 }
21 
22 int main(){
23     Mat image=imread("C:/testdir/Koala.jpg");
24     salt(image, 3000);
25     namedWindow("Image");
26     imshow("Image", image);
27     waitKey(0);
28 }

 【更多】
使用cv::Mat的at方法有时候很笨重。因为返回的类型必须被规定为模板参

数。当矩阵类型未知时,使用cv::Mat的子类cv::Mat_类是可以的。这个类

定义了少量额外的方法但没有定义更多的数据属性,因此一个指向类的指针

或者引用可以被直接地转化成另外的类。在其他的额外方法中,operator()

方法允许直接访问矩阵元素。因此,如果图像是指向uchar类型的引用,那

么可以这样写:
    cv::Mat_<uchar>im2=image;//im2 refers to image
    im2(50, 100)=0;//access to row 50 and column 100
由于cv::Mat_类的元素类型在变量创建的时候已经声明过了,operator()方

法在编译的时候知道返回值的类型。不同于写入的时候变shorter,

operator()方法和at方法等效。

【同见】
编写高效扫描图像的循环方法

【用指针扫描图像】
在大多数图像处理过程中,人们为了执行计算需要处理图像的所有像素。考

虑到图像中如此多的像素数量,高效的方式很重要。这一部分,以及下一部

分,会向你展示扫描图像的不同的循环的实现方式。这一部分使用指针来计

算。

【准备阶段】
我们通过完成一个加单的任务来实现图像扫描过程:减少一副图像中的颜色

的数量

彩色图像由3个通道(3-channels)的像素组成.每个像素的每个通道对应着三

原色中的强度值。由于这些值都是8位的无符号字符类型值,颜色数量的总

数是256*256*256,大于1.6亿个颜色。所以为了减少分析时的复杂度,有时

候减少图像的数量是有用的。实现这个目标的一个简答方法是,把RGB空间

分割成大小相等的立方体。例如,你想在每个维度把颜色数量减少8个,那

么你得到的颜色总是就是32*32*32.原图中每个颜色也就变成了颜色衰减后

的图中所属立方体中心位置的颜色值。

基本的颜色减少算法是简单的。若N是减少因子,那么对于图像中每个像素

和每个像素中的通道,都要整除N(余数会丢失)。然后把结果乘以N,所得

到的会比原来输入的图像要小。加上N/2,你会得到两者的中间值。持续着

一过程,你会得到总数为(256/N)^3的可能的颜色数量。

【如何做】
颜色衰减函数这样声明:
     void colorReduce(cv::Mat &image, int div=64);
用户提供一张图片和每个通道的衰减因子。这里,此过程恰到好处的完成了

,即:输入图像的像素值被这个函数修改了。更多的输入输出参数参见【更

多】部分。

这个过程很简单,通过创建一个遍历像素的所有值的一个double循环以实现


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;
            //处理像素过程结束
        }//每一行处理完毕
    }
}
这个函数可以用下面代码测试:
    //读入图像
    image=cv::imread("boldt.jpg"):
    //处理图像
    colorReduce(image);    
    //展示图像
    cv::namedWindow("Image");
    cv::imshow("Image", image);

 1 【更多】
 2 使用cv::Mat的at方法有时候很笨重。因为返回的类型必须被规定为模板参
 3 
 4 数。当矩阵类型未知时,使用cv::Mat的子类cv::Mat_类是可以的。这个类
 5 
 6 定义了少量额外的方法但没有定义更多的数据属性,因此一个指向类的指针
 7 
 8 或者引用可以被直接地转化成另外的类。在其他的额外方法中,operator()
 9 
10 方法允许直接访问矩阵元素。因此,如果图像是指向uchar类型的引用,那
11 
12 么可以这样写:
13     cv::Mat_<uchar>im2=image;//im2 refers to image
14     im2(50, 100)=0;//access to row 50 and column 100
15 由于cv::Mat_类的元素类型在变量创建的时候已经声明过了,operator()方
16 
17 法在编译的时候知道返回值的类型。不同于写入的时候变shorter,
18 
19 operator()方法和at方法等效。
20 
21 【同见】
22 编写高效扫描图像的循环方法
23 
24 【用指针扫描图像】
25 在大多数图像处理过程中,人们为了执行计算需要处理图像的所有像素。考
26 
27 虑到图像中如此多的像素数量,高效的方式很重要。这一部分,以及下一部
28 
29 分,会向你展示扫描图像的不同的循环的实现方式。这一部分使用指针来计
30 
31 算。
32 
33 【准备阶段】
34 我们通过完成一个加单的任务来实现图像扫描过程:减少一副图像中的颜色
35 
36 的数量
37 
38 彩色图像由3个通道(3-channels)的像素组成.每个像素的每个通道对应着三
39 
40 原色中的强度值。由于这些值都是8位的无符号字符类型值,颜色数量的总
41 
42 数是256*256*256,大于1.6亿个颜色。所以为了减少分析时的复杂度,有时
43 
44 候减少图像的数量是有用的。实现这个目标的一个简答方法是,把RGB空间
45 
46 分割成大小相等的立方体。例如,你想在每个维度把颜色数量减少8个,那
47 
48 么你得到的颜色总是就是32*32*32.原图中每个颜色也就变成了颜色衰减后
49 
50 的图中所属立方体中心位置的颜色值。
51 
52 基本的颜色减少算法是简单的。若N是减少因子,那么对于图像中每个像素
53 
54 和每个像素中的通道,都要整除N(余数会丢失)。然后把结果乘以N,所得
55 
56 到的会比原来输入的图像要小。加上N/2,你会得到两者的中间值。持续着
57 
58 一过程,你会得到总数为(256/N)^3的可能的颜色数量。
59 
60 【如何做】
61 颜色衰减函数这样声明:
62      void colorReduce(cv::Mat &image, int div=64);
63 用户提供一张图片和每个通道的衰减因子。这里,此过程恰到好处的完成了
64 
65 ,即:输入图像的像素值被这个函数修改了。更多的输入输出参数参见【更
66 
67 多】部分。
68 
69 这个过程很简单,通过创建一个遍历像素的所有值的一个double循环以实现
70 
71 72 void colorReduce(cv::Mat &image, int div=64){
73     int nl=image.rows;//行数
74     //每一行元素总数
75     int nc=image.cols*image.channels();
76 
77     for(int j=0; j<nl; j++){
78         //获取第j行的地址
79         uchar* data=image.ptr<uchar>(j);
80         for(int i=0; i<nc; i++){
81             //处理每个像素
82             data[i]=data[i]/div*div+div/2;
83             //处理像素过程结束
84         }//每一行处理完毕
85     }
86 }
87 这个函数可以用下面代码测试:
88     //读入图像
89     image=cv::imread("boldt.jpg"):
90     //处理图像
91     colorReduce(image);    
92     //展示图像
93     cv::namedWindow("Image");
94     cv::imshow("Image", image);

 【如何起作用的】
在彩图对应图像数据中最前面3字节是左上角的三个通道的值,接下来是第

一行第二个像素的三个通道的值,如此下去(注意,opencv默认使用BGR的

顺序表示颜色,所以蓝色是第一个颜色通道)。宽度为W,高度为H的图像需

要W*H*3个ucahr的内存大小。然而,为了高效,行的长度会被添加额外的像

素。这是因为一些多媒体过程的碎片)例如intelMMX建筑物)在行数是4或者

8的倍数的时候能高效处理。明显地,若没有增加这些额外的行那么有效的

宽度和实际宽度相等。数据属性cols给定了图像宽度,rows给定了图像高度

,而tep数据属性以字节为单位给定了图像的宽度。即使你的图像不是uchar

类型的,step仍然以字节为单位。像素元素的大小通过elemSize的形式给出

(例如,对与3通道的短整型矩阵(CV_16SC3),elemSize会返回6)。图像

中的通道数量由nchannels方法给出(如果是灰度图像那么就是1,彩图就是

3)。最终,total这个方法返回矩阵中像素总数。


每一行的像素值数量通过以下方式计算:
    int nc=image.cols*image.channels();
为了简化指针计算的过程,cv:Mat类提供了一个方法,能够直接给你图像中

行的地址。这就是ptr方法。这是一个模板方法,例如:
    uchar* data=image.ptr<uchar>(j);
注意,在上述处理过程中,我们在每一行都有相同的指针计算,所以可以写

成:
    *data++=*data/div*div+div/2;        //发现书上写错了。
【其他颜色衰减公式】
data[i]=data[i]-data[i]%div+div/2;

 当然亦可在函数中增加参数,不在原图上做修改:

 1 #include <cv.h>
 2 #include <highgui.h>
 3 
 4 using namespace std;
 5 using namespace cv;
 6 
 7 void colorReduce(const Mat &image, Mat &result, int div=64){
 8     int nl=image.rows;
 9     int nc=image.cols*image.channels();
10     result.create(image.rows, image.cols, image.type());
11     for(int j=0; j<nl; j++){
12         const uchar* data_in=image.ptr<uchar>(j);
13         uchar* data_out=result.ptr<uchar>(j);
14         for(int i=0; i<nc; i++){
15             data_out[i]=data_in[i]/div*div+div/2;
16         }
17     }
18 }
19 
20 int main(){
21     Mat image=imread("C:/testdir/Koala.jpg");
22     Mat result;
23     colorReduce(image, result);
24     namedWindow("Image");
25     imshow("Image", result);
26     waitKey(0);
27 }

 

若图像是连续的(即没有添加过额外的行)那么可以变二位为一维处理

 1 #include <cv.h>
 2 #include <highgui.h>
 3 
 4 using namespace std;
 5 using namespace cv;
 6 
 7 void colorReduce(Mat &image, int div=64){
 8     int nl=image.rows;
 9     int nc=image.cols*image.channels();
10     if(image.isContinuous()){
11         nc=nc*nl;
12         nl=1;
13         //or:image.reshape(1, image.cols*image.rows);
14         //then int nl=image.rows; and int nc=image.cols;
15     }
16     for(int j=0; j<nl; j++){
17         uchar* data=image.ptr<uchar>(j);
18         for(int i=0; i<nc; i++){
19             data[i]=data[i]/div*div+div/2;
20         }
21     }
22 }
23 
24 int main(){
25     Mat image=imread("C:/testdir/Koala.jpg");
26     colorReduce(image);
27     namedWindow("Image");
28     imshow("Image", image);
29     waitKey(0);
30 }

 
【低级指针计算】

1 uchar* data=image.data;
2 data+=image.step; //从当前行跳转到要处理的下一行

这样写看似也可以:data=image.data+j*image.step+i*image.elemSize();
但不建议这么做,因为这在感兴趣区域的处理过程中不奏效

 

【【用迭代器处理图像衰减】】

此部分略。(功能和指针类似,有时间补上。)

 

【图像扫描至邻居法】

用相邻元素扫描,核心公式:

当前值=5*当期值-上方值-下方值-左方值-右方值

代码

 1 #include <cv.h>
 2 #include <highgui.h>
 3 
 4 using namespace std;
 5 using namespace cv;
 6 
 7 void sharpen(const Mat& image, Mat &result){
 8     result.create(image.size(), image.type());
 9     imshow("image", image);
10     for(int j=1; j<image.rows-1; j++){
11         const uchar* previous=image.ptr<const uchar>(j-1);
12         const uchar* current=image.ptr<const uchar>(j);
13         const uchar* next=image.ptr<const uchar>(j+1);
14         uchar* output=result.ptr<uchar>(j);
15         for(int i=1; i<image.cols*3; i++){//此处书上写为i<image.cols-1发现只显示1/3宽度
16             *output=saturate_cast<uchar>(5*current[i]-current[i-1]
17                     -current[i+1]-previous[i]-next[i]);
18             output++;
19         }
20     }
21     result.row(0).setTo(Scalar(0));
22     result.row(result.rows-1).setTo(Scalar(0));
23     result.col(0).setTo(Scalar(0));
24     result.col(result.cols-1).setTo(Scalar(0));
25 }
26 int main(){
27     Mat image=imread("C:/testdir/Koala.jpg");
28     Mat result;
29     sharpen(image, result);
30     namedWindow("Image");
31     imshow("Image", result);
32     waitKey(0);
33     return 0;
34 }

 使用系统函数filter2D:

#include <cv.h>
#include <highgui.h>

using namespace std;
using namespace cv;

void sharpen2D(const Mat& image, Mat &result){
    Mat kernel(3, 3, CV_32F, Scalar(0));
    kernel.at<float>(1,1)=5.0;
    kernel.at<float>(0,1)=-1.0;
    kernel.at<float>(2,1)=-1.0;
    kernel.at<float>(1,0)=-1.0;
    kernel.at<float>(1,2)=-1.0;
    filter2D(image, result, image.depth(), kernel);
}
int main(){
    Mat image=imread("C:/testdir/barcode.bmp");
    if(!image.data) return -1;
    Mat result;
    sharpen2D(image, result);
    namedWindow("Image");
    imshow("image", image);
    imshow("result", result);
    waitKey(0);
    return 0;
}

 

 1 /*
 2  *图像合成
 3  *要求大小一样才可以
 4  */
 5 #include <cv.h>
 6 #include <highgui.h>
 7 
 8 using namespace std;
 9 using namespace cv;
10 
11 int main(){
12     Mat image1=imread("C:/testdir/me.jpg");
13     Mat image2=imread("C:/testdir/airplane.jpg");
14     Mat result;
15     addWeighted(image1, 0.7, image2, 0.9, 0., result);
16 //    imshow("image", image);
17     imshow("result", result);
18     imwrite("C:/testdir/we.jpg", result);
19     waitKey(0);
20     return 0;
21 }

 

 1 /*
 2  *图像合成
 3  *在指定区域合成
 4  */
 5 #include <cv.h>
 6 #include <highgui.h>
 7 
 8 using namespace std;
 9 using namespace cv;
10 
11 int main(){
12     Mat image=imread("C:/testdir/me.jpg");
13     Mat logo=imread("C:/testdir/logo.jpg");
14     Mat imageROI=image(Rect(600, 1300, logo.cols, logo.rows));
15     addWeighted(imageROI, 1.0, logo, 0.3, 0., imageROI);
16     imshow("result", image);
17     imwrite("C:/testdir/me4.jpg", image);
18     waitKey(0);
19     return 0;
20 }

 

 

 

 

 

【云栖快讯】一站式开发者服务,海量学习资源免费学  详情请点击

网友评论