opencv 2 computer vision application programming第二章翻译

醉酒当歌 提交于 2020-02-18 08:27:46

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

基本上将,一个图像时一个数值对应的矩阵。这就是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 }

 

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!