• 关于OpenCV图像操作的默认参数问题



    本系列文章由 @yhl_leo 出品,转载请注明出处。
    文章链接: http://blog.csdn.net/yhl_leo/article/details/51559490


    在使用OpenCV以及其他开源库时,往往一个容易忽略的问题就是使用默认参数,尤其是图像处理,会导致内存中的图像数据变换后被不同程度上被修改!

    下面给出几个示例,帮助理解。

    1. warpAffine

    warpAffine是图像仿射变换函数,函数定义为:

    C++: void warpAffine(
        InputArray src, 
        OutputArray dst, 
        InputArray M, 
        Size dsize, 
        int flags=INTER_LINEAR, 
        int borderMode=BORDER_CONSTANT, 
        const Scalar& borderValue=Scalar())

    其中,
    - M是一个2x3的转换矩阵,关于获取方法,可使用getRotationMatrix2D()函数:

    warpAffine

    • flags是一个标识符,结合了内插方法(interpolation methods)和可选项WARP_INVERSE_MAP

      • INTER_LINEAR - a bilinear interpolation (used by default) 双线性插值
      • INTER_NEAREST - a nearest-neighbor interpolation 最邻近插值
      • INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method. 基于区域像素关系重采样
      • INTER_CUBIC - a bicubic interpolation over 4x4 pixel neighborhood 4x4邻域双三次插值
      • INTER_LANCZOS4 - a Lanczos interpolation over 8x8 pixel neighborhood 8x8邻域兰索斯插值
      • WARP_INVERSE_MAP - M is the inverse transformation (warp) 等价于CV_WARP_INVERSE_MAP fills all of the destination image pixels M是warp的逆变换
    • borderMode: pixel extrapolation method 像素外推方法

      • BORDER_CONSTANT - pad the image with a constant value (used by default) 补上定值,如果使用则补上的定值设置为borderValue的值(默认为0)
      • BORDER_TRANSPARENT - the corresponding pixels in the destination image will not be modified at all 不做任何修改
      • BORDER_REPLICATE - the row or column at the very edge of the original is replicated to the extra border 将原始数据的行方向/列方向的边缘像素值作为外推边界

    因为图像是离散的整数格网,一旦对像素值或者其下标进行浮点位运算,得到的结果都是近似值!几种内插方法各有优劣,双线性插值可以视为一个折中选择,即计算量不算很大(比基于区域和邻域块的方法小很多),效果也过得去(一般比最邻近插值更好),源码编写者大概是基于此考虑,将其设置为默认参数,但是针对某些具体的应用,绝不是最佳选择,例如对二值图像进行旋转,我们希望旋转后的图像仍然是二值的,那选择最临近插值可能就更合适。关于边界外推模式,这里贴上OpenCV官方文档:Adding borders to your images

    2. imread & imwrite

    以前写过一篇博客,讲述了OpenCV图像读取与存储的一些细节:Opencv 图像读取与保存问题, 其中有一些非常容易忽视的细节,例如使用imread()读取图像时,参数flags的值默认是1,也就是说默认读取的是3通道彩色图像,如果待读取的图像是单通道或者4通道的,也会被转成3通道图像,这样读取的数据就不是你真正想要的。

    另外,使用imwrite()存储图像时,params参数也至关重要,其中包括特定图像存储编码参数设置,如果调用时缺省,就会使用默认参数,例如存储JPEG图像,图像压缩质量默认设置为95(范围为0~100,数值越大质量越好),存储为PNG时,压缩级别默认为3(0~9 越大压缩越厉害)。

    3. Demo

    生成一个简单的单通道200x200的二值图像127,255,之所以不使用0, 255,是为了使有些参数的使用对结果的影响更加明显:

    demo_testtest1.png

    局部放大图:

    test1-localtest1-local

    以下面这段代码为例,首先使用

        cv::Mat image = cv::imread("test1.png", IMREAD_UNCHANGED);
        const int cols = image.cols;
        const int rows = image.rows;
    
        cv::Mat R = cv::getRotationMatrix2D(
            cv::Point2f(
            static_cast<float>(cols/2), 
            static_cast<float>(rows/2)), 
            30.0, 
            1.0);
    
        cv::Mat r_image/*(rows,cols,CV_8UC1, cv::Scalar(0))*/;
        cv::warpAffine( 
            image, 
            r_image, 
            R, 
            image.size(), 
            INTER_LINEAR,
            BORDER_CONSTANT);
    
    //  std::vector<int> compress_param;
    //  compress_param.push_back(CV_IMWRITE_PNG_COMPRESSION);
    //  compress_param.push_back(0);
    
        cv::imwrite("test1-r-l_c.png", r_image/*, compress_param*/);

    warpAffine()imwrite()函数都先使用默认参数,并且旋转后的矩阵r_image在声明的时候,不进行初始化,即图像旋转后插值方式为双线性插值,边缘外推方式为自动补为0:

    l-cl_c

    让我们放大局部:

    l_c-locall_c-local

    warpAffine()函数中默认参数修改INTER_LINEAR -> INTER_NEAREST, BORDER_CONSTANT -> BORDER_TRANSPARENT ,即插值方法为最临近插值,边界不做任何调整(保持Mat的初始值,若未初始化,则会先进行初始化):

    n-tn_t

    同样放大局部:

    n_t-localn_t-local

    可以看出两者之间的明显区别,后者在边缘部分会保留原始数据的数据数值,但是为什么边界外推的像素颜色值是那样的,前面已经讲过:边界不做任何调整(保持Mat的初始值,若未初始化,则会先进行初始化),其中初始化值并不是0或者黑色。作为验证,我们在声明r_image的时候,对其进行初始化cv::Mat r_image(rows,cols,CV_8UC1, cv::Scalar(0));:

    n_t-2n_t-2

    最后,再把图像存储压缩参数进行设置,即取消掉对compress_param的注释,虽然两个结果视觉上已经看不出差异,但是从文件大小上可以发现,压缩级为默认值(3)的图片大小为1.31KB,而压缩级为0的图片大小为39.3KB~真的差了很多,当然如果存储的JPG文件,分别使用下面的命令:

    // 1
    cv::imwrite("test1-r-n_t.jpg", r_image);
    
    // 2
    std::vector<int> compress_param;
    compress_param.push_back(IMWRITE_JPEG_QUALITY);
    compress_param.push_back(100);
    cv::imwrite("test1-r-n_t-2.jpg", r_input, compress_param);

    让我们对比局部放大图:

    11

    22

    明显可以看出,使用默认参数保存时,图像质量已经出现只管的下降,可以想象,如果不停地循环读取和保存同一幅图像,那么图像的质量将会以0.95的n次幂的速度降低。

    4. Summary

    讲述了那么多,还是回归到主题,很多时候为了方便大家使用,开源库的一些函数都会提供默认参数,而缺省参数的设置主要是基于能够适用大多数用户的基本需求,但是并不一定是性能或效果最佳,为了获得更好的结果,必须了解传入函数的各个参数的意义,针对现实的需求选择适合自己的,不然你的成果很有可能就失败在这些细小的边边角角上。

  • 相关阅读:
    为什么LIKELY和UNLIKELY要用两个叹号
    vuex-persist数据持久化存储插件
    【ejabberd】安装XMPP服务器ejabberd(Ubuntu 12.04)
    Dynamics CRM2013 picklist下拉项行数控制
    jdk1.8新日期时间类(DateTime、LocalDateTime)demo代码
    webpack插件解析:HtmlWebpackPlugin是干什么的以及如何使用它
    marked实现
    Vue组件使用、父子组件传值
    VUE启动报错
    nodejs创建vue项目
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6332128.html
Copyright © 2020-2023  润新知