zoukankan      html  css  js  c++  java
  • 关于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

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

  • 相关阅读:
    Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
    DHCP "No subnet declaration for xxx (no IPv4 addresses)" 报错
    Centos安装前端开发常用软件
    kubernetes学习笔记之十:RBAC(二)
    k8s学习笔记之StorageClass+NFS
    k8s学习笔记之ConfigMap和Secret
    k8s笔记之chartmuseum搭建
    K8S集群集成harbor(1.9.3)服务并配置HTTPS
    Docker镜像仓库Harbor1.7.0搭建及配置
    Nginx自建SSL证书部署HTTPS网站
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6332128.html
Copyright © 2011-2022 走看看