• opencv 2 computer vision application programming第二章翻译


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

    基本上将,一个图像时一个数值对应的矩阵。这就是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 }
    Greatness is never a given, it must be earned.
  • 相关阅读:
    【资料整理】面向对象
    【资料整理】函数(方法)
    【资料整理】数组
    【资料整理】循环、判断、三元表达式
    【资料整理】类型转换、运算符
    【资料整理】c#基础
    线性表的链式表示和实现
    线性表
    PAT 乙级1062最简分数
    HDU 1027(全排列)
  • 原文地址:https://www.cnblogs.com/zjutzz/p/3051938.html
Copyright © 2020-2023  润新知