• OpenCV imread读取jpg图像的一个大坑


    TL;DR 长话短说

    别用版本闭区间[3.0.0, 3.4.1]之内的OpenCV读取.jpg图像,如果你care那种肉眼看不出差异的单像素差异。2.4.x和>=3.4.2的则是OK的。

    问题描述

    同一张jpg图片用OpenCV读取,不同版本OpenCV得到像素值不一样;而这些肉眼难以察觉的差异对计算结果有影响时,请注意检查使用的jpg解码器的情况:

    • (1)OpenCV 2.4.x系列版本,编解码库是libjpeg,API/ABI版本是v6b
    • (2)OpenCV 版本区间 [3.0.0, 3.4.1],解码库是libjpeg,API/ABI版本是v9
    • (3)OpenCV 版本>=3.4.2,3rdparty中同时给了libjpeg和libjpeg-turbo解码器,libjpeg用API/ABI版本v9,libjpeg-turbo用API/ABI版本v6;Windows预编译包用的是libjpeg-turbo

    因此,使用OpenCV的Windows预编译包情况下编解码一张jpg图像,OpenCV版本闭区间[3.0.0, 3.4.1]内得到一种结果(jpeg API/ABI v9),OpenCV版本区间(2.4.x] ∪ [3.4.2, -]得到另一种结果(jpeg API/ABI v6b),是可能出现的。说“可能”存在是因为大部分图、大部分像素不存在差别,少量图、少量像素存在问题,也许运气好手头的图片确实没问题。

    对于像素值敏感的计算过程(例如深度学习推理引擎推理结果),这种差异应当避免,一旦碰上要及时避开;排查的过程消磨时间,而官方changelog中也没有找到很细致的提醒,因此记录为此文。

    How to verity 如何检查

    提供3种方法,每一种方法代表了不同的验证思路,相同之处则是“发现问题-不放过问题-进一步研究”。

    方法1:调用cv::getBuildInformation()函数

    OpenCV贴心的帮我们封装好了一个名为getBuildInformation()的函数,打印各种依赖库是否有找到、找到的版本等信息。我们从中找出“Media I/O”开头的一段,“JPEG:”开头的版本信息就是我们要找到。比对不同版本OpenCV的这部分输出,我得到(个人添加了wrong/correct注释)

    //-------------------
    opencv 3.0.0 (wrong)
    
      Media I/O:
        ZLib:                        build (ver 1.2.8)
        JPEG:                        build (ver 90)
        WEBP:                        build (ver 0.3.1)
        PNG:                         build (ver 1.5.12)
        TIFF:                        build (ver 42 - 4.0.2)
        JPEG 2000:                   build (ver 1.900.1)
        OpenEXR:                     build (ver 1.7.1)
        GDAL:                        NO
    
    //------------------
    opencv 3.4.1 (wrong)
    
     Media I/O:
       ZLib:                        build (ver 1.2.11)
       JPEG:                        build (ver 90)
       WEBP:                        build (ver encoder: 0x020e)
       PNG:                         build (ver 1.6.34)
       TIFF:                        build (ver 42 - 4.0.9)
       JPEG 2000:                   build (ver 1.900.1)
       OpenEXR:                     build (ver 1.7.1)
    
    //------------------
    opencv 3.4.2 (correct)
    
      Media I/O:
        ZLib:                        build (ver 1.2.11)
        JPEG:                        build-libjpeg-turbo (ver 1.5.3-62)
        WEBP:                        build (ver encoder: 0x020e)
        PNG:                         build (ver 1.6.34)
        TIFF:                        build (ver 42 - 4.0.9)
        JPEG 2000:                   build (ver 1.900.1)
        OpenEXR:                     build (ver 1.7.1)
        HDR:                         YES
        SUNRASTER:                   YES
        PXM:                         YES
    
    //------------------
    opencv 2.4.9 (correct)
    
      Media I/O:
        ZLib:                        build (ver 1.2.7)
        JPEG:                        build (ver 62)
        PNG:                         build (ver 1.5.12)
        TIFF:                        build (ver 42 - 4.0.2)
        JPEG 2000:                   build (ver 1.900.1)
        OpenEXR:                     build (ver 1.7.1)
    
    
    //------------------
    opencv 2.4.13.6 (correct)
    
     Media I/O:
       ZLib:                        build (ver 1.2.7)
       JPEG:                        build (ver 62)
       PNG:                         build (ver 1.5.27)
       TIFF:                        build (ver 42 - 4.0.2)
       JPEG 2000:                   build (ver 1.900.1)
       OpenEXR:                     build (ver 1.7.1)
    
    

    可以看到ver62和ver90两种。ver62对应libjpeg API/ABI v6b, ver90对应v9。

    此函数的原理略为hack,通过把编译OpenCV时cmake阶段提取出的各种依赖库的版本信息汇总到一个名为opencv_string.inc的文件中,再通过#include opencv_string.inc的形式,作为字符串常量予以返回。(需要自行源码编译OpenCV debug版本进行查看)

    方法2:自行翻看源码

    通过git clone一份opencv源码,在不同版本间切换源码,然后翻看3rdparty目录,发现opencv 3.4.2版本开始有了libjpeg-turbo子目录。

    git checkout -b 3.4.2 3.4.2
    cd 3rdparty
    ls
    

    进一步查看:
    3rdparty/libjpeg/jpeglib.h,查找JPEG_LIB_VERSION,opencv2.4.x是62, opencv3.x是90。
    3rdparty/libjpeg-turbo/CMakeLists.txt,设定了JPEG_LIB_VERSION为62。

    当然,libjpeg-turbo库本身是兼容libjpeg库的,默认兼容v6b,兼容v7和v8的话只要给cmake传递-DWITH_JPEG7=1-DWITH_JPEG8=1就可以了,而至于v9,从libjpeg-turbo主页上可以看到作者们认为“没卵用,并不必现有的标准无损格式多产生什么”,那我们也就不纠结这一点了。

    方法3:比对实际项目中的图像像素

    比对CNN推理引擎输出结果时,发现SSD网络loc层结果,小数点后几位,PC和设备上结果不一致。loc代表了目标检测预测结果,虽然你看它是一个小数,却要乘以缩放系数来得到原图中的bounding box坐标;而如果loc层的结果偏差了一点点,结果就可能是bounding box有明显视觉偏差,或者跑出图像边界。逐层往前排查,发现网络输入就有问题:同一张.jpg图像,读到内存中像素值有些不一样(第一个像素值就不一样T_T)。所以从这里入手,才发现OpenCV的jpg编解码的大坑。

    纠结一番才开始怀疑OpenCV有问题;但预编译的OpenCV并不提供每个VS版本的库,所以需要耐心配置多个版本的VS和多个版本的OpenCV(耐心的重要性;后来发现这种预编译包没法调试进源码,还是自行编译舒服,但需要手动解依赖则是另一个事情了T_T),一通配置测试后,比对imread读取下图(一张ADAS场景的图片)的第一个像素值看是否一致,测试图像和测试结果记录分别如下:

    1.jpg

    我这里打印上面这张jpg图片的第一个像素值,opencv249打印出来是158(认为是正确,因为板子上跑的版本就是这个结果,是正确的标准,DSP优化库要以这个为标准的),而opencv310打印出来是192。对应的测试代码:

    #include <stdio.h>
    #include <iostream>
    #include <string>
    #include <opencv2/opencv.hpp>
    
    using namespace std;
    using namespace cv;
    
    int main() {
    
        std::string im_pth = "F:/xx/work/20190315/1.jpg";
        cv::Mat src_image = cv::imread(im_pth);
        IplImage* shadow_image = cvLoadImage(im_pth.c_str(), CV_LOAD_IMAGE_COLOR);
    
        std::cout << cv::getBuildInformation() << std::endl << std::endl << std::endl;
    
        for (int i = 0; i < shadow_image->height * shadow_image->width * 3; i++){
            fprintf(flog, "%f
    ", (float)(unsigned char)(shadow_image->imageData[i]));
            if (i == 0) {
                // 158 is correct
                // 192 is wrong
                printf("%u
    ", (unsigned int)(unsigned char)(shadow_image->imageData[i]));
            }
        }
        fclose(flog);
        printf("-------------------end-------------------
    ");
    
        return 0;
    }
    

    references

    Opencv3.0.0‘s bug of imread function

    Problem caused by the change from libjpeg to libjpeg-turbo

    关于JPG解码的坑

    Loading jpg files gives different results in 3.4.0 and 3.4.2

  • 相关阅读:
    关于编码的两个小点(摘)
    c#中类和成员的修饰符介绍
    jquery/js不支持ie9以下版本的方法或属性
    一次Linux系统被攻击的分析过程
    运维堡垒机(跳板机)系统 python
    puppet 3+Unicorn+Nginx安装配置
    用memcache来同步session
    php+memcache实现的网站在线人数统计
    CentOS yum 源的配置与使用
    Bind+DLZ构建企业智能DNS/DNS
  • 原文地址:https://www.cnblogs.com/zjutzz/p/10543935.html
Copyright © 2020-2023  润新知