OpenCV源码剖析之ImageDecoder 中,ImageCodecInitializer注册了众多图像类型,这一节我们将讲解PNG的编解码。由于这部分比较简单,只将简单看下代码。PNG 解码是从BaseImageDecoder继承而来,PNG 编码则是从BaseImageEncoder继承而来。在grfmt_png.hpp中可以查看他们的定义:

namespace cv
class PngDecoder CV_FINAL : public BaseImageDecoder
public:
    PngDecoder();
    virtual ~PngDecoder();
    bool  readData( Mat& img ) CV_OVERRIDE;
    bool  readHeader() CV_OVERRIDE;
    void  close();
    ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
    static void readDataFromBuf(void* png_ptr, uchar* dst, size_t size);
    int   m_bit_depth;
    void* m_png_ptr;  // pointer to decompression structure
    void* m_info_ptr; // pointer to image information structure
    void* m_end_info; // pointer to one more image information structure
    FILE* m_f;
    int   m_color_type;
    size_t m_buf_pos;
class PngEncoder CV_FINAL : public BaseImageEncoder
public:
    PngEncoder();
    virtual ~PngEncoder();
    bool  isFormatSupported( int depth ) const CV_OVERRIDE;
    bool  write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
    ImageEncoder newEncoder() const CV_OVERRIDE;
protected:
    static void writeDataToBuf(void* png_ptr, uchar* src, size_t size);
    static void flushBuf(void* png_ptr);

        对于解码主要就是readHeader和readData这两个函数了,其中readHeader实现如下:

bool PngDecoder::readHeader()
    volatile bool result = false;
    close();
    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
    if (png_ptr)
        png_infop info_ptr = png_create_info_struct(png_ptr);
        png_infop end_info = png_create_info_struct(png_ptr);
        m_png_ptr  = png_ptr;
        m_info_ptr = info_ptr;
        m_end_info = end_info;
        m_buf_pos  = 0;
        if (info_ptr && end_info)
            if (setjmp(png_jmpbuf(png_ptr)) == 0)
                if (!m_buf.empty())
                    png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf);
                    m_f = fopen(m_filename.c_str(), "rb");
                    if (m_f)
                        png_init_io(png_ptr, m_f);
                if (!m_buf.empty() || m_f)
                    png_uint_32   wdth, hght;
                    int           bit_depth, color_type, num_trans = 0;
                    png_bytep     trans;
                    png_color_16p trans_values;
                    png_read_info(png_ptr, info_ptr);
                    png_get_IHDR(png_ptr, info_ptr, &wdth, &hght,
                                 &bit_depth, &color_type, 0, 0, 0);
                    m_width      = (int)wdth;
                    m_height     = (int)hght;
                    m_color_type = color_type;
                    m_bit_depth  = bit_depth;
                    if ((bit_depth <= 8) || (bit_depth == 16))
                        switch (color_type)
                        case PNG_COLOR_TYPE_RGB:
                        case PNG_COLOR_TYPE_PALETTE:
                            png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values);
                            if (num_trans > 0)
                                m_type = CV_8UC4;
                                m_type = CV_8UC3;
                            break;
                        case PNG_COLOR_TYPE_GRAY_ALPHA:
                        case PNG_COLOR_TYPE_RGB_ALPHA:
                            m_type = CV_8UC4;
                            break;
                        default:
                            m_type = CV_8UC1;
                        if (bit_depth == 16)
                            m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
                        result = true;
    if (!result)
        close();
    return result;
}
从代码中可以看出,图片数据有2种来源DataFromBuf和文件,DataFromBuf实现通过png_set_read_fn指定,其处理函数实现如下所示:
void PngDecoder::readDataFromBuf(void *_png_ptr, uchar *dst, size_t size)
    png_structp png_ptr  = (png_structp)_png_ptr;
    PngDecoder  *decoder = (PngDecoder *)(png_get_io_ptr(png_ptr));
    CV_Assert(decoder);
    const Mat& buf = decoder->m_buf;
    if (decoder->m_buf_pos + size > buf.cols * buf.rows * buf.elemSize())
        png_error(png_ptr, "PNG input buffer is incomplete");
        return;
    memcpy(dst, decoder->m_buf.ptr() + decoder->m_buf_pos, size);
    decoder->m_buf_pos += size;

      可以看出这个函数最主要的实现就是memcpy(dst, decoder->m_buf.ptr() + decoder->m_buf_pos, size);它是将数据从decoder->m_buf拷贝到dst buf中去,decoder->m_buf就是基类中setSource 2种实现之一的来自内存了,其类型为Mat。

        在指定数据源后,通过png_read_info接口读取png chunk信息,再调用png_get_IHDR获取png文件头数据块信息,从未获取图片的宽高、颜色类型、位深信息。再根据位深和颜色类型得到对应的Mat 数据类型。

        获取到图片的基本信息后,接下来就是读取数据,其实现为

bool PngDecoder::readData(Mat& img)
    volatile bool result = false;
    AutoBuffer<uchar *> _buffer(m_height);
    uchar               **buffer = _buffer;
    int                 color    = img.channels() > 1;
    png_structp png_ptr  = (png_structp)m_png_ptr;
    png_infop   info_ptr = (png_infop)m_info_ptr;
    png_infop   end_info = (png_infop)m_end_info;
    if (m_png_ptr && m_info_ptr && m_end_info && m_width && m_height)
        if (setjmp(png_jmpbuf(png_ptr)) == 0)
            int y;
            if ((img.depth() == CV_8U) && (m_bit_depth == 16))
                png_set_strip_16(png_ptr);
            else if (!isBigEndian())
                png_set_swap(png_ptr);
            if (img.channels() < 4)
                /* observation: png_read_image() writes 400 bytes beyond
                 * end of data when reading a 400x118 color png
                 * "mpplus_sand.png".  OpenCV crashes even with demo
                 * programs.  Looking at the loaded image I'd say we get 4
                 * bytes per pixel instead of 3 bytes per pixel.  Test
                 * indicate that it is a good idea to always ask for
                 * stripping alpha..  18.11.2004 Axel Walthelm
                png_set_strip_alpha(png_ptr);
                png_set_tRNS_to_alpha(png_ptr);
            if (m_color_type == PNG_COLOR_TYPE_PALETTE)
                png_set_palette_to_rgb(png_ptr);
            if (((m_color_type & PNG_COLOR_MASK_COLOR) == 0) && (m_bit_depth < 8))
#if (PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE >= 10209) || \
            (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR == 0 && PNG_LIBPNG_VER_RELEASE >= 18)
                png_set_expand_gray_1_2_4_to_8(png_ptr);
#else
                png_set_gray_1_2_4_to_8(png_ptr);
#endif
            if ((m_color_type & PNG_COLOR_MASK_COLOR) && color)
                png_set_bgr(png_ptr);   // convert RGB to BGR
            else if (color)
                png_set_gray_to_rgb(png_ptr);   // Gray->RGB
                png_set_rgb_to_gray(png_ptr, 1, 0.299, 0.587);   // RGB->Gray
            png_set_interlace_handling(png_ptr);
            png_read_update_info(png_ptr, info_ptr);
            for (y = 0; y < m_height; y++)
                buffer[y] = img.data + y * img.step;
            png_read_image(png_ptr, buffer);
            png_read_end(png_ptr, end_info);
            result = true;
    close();
    return result;
         这里有点需要注意的是PNG图片存储是按大端顺序存储数据的,因此在处理前需要判断系统测存储模式。
         PNG编码和解码处理差不多,这里将编码代码附上:
/// PngEncoder ///
PngEncoder::PngEncoder()
    m_description   = "Portable Network Graphics files (*.png)";
    m_buf_supported = true;
PngEncoder::~PngEncoder()
bool PngEncoder::isFormatSupported(int depth) const
    return depth == CV_8U || depth == CV_16U;
ImageEncoder PngEncoder::newEncoder() const
    return makePtr<PngEncoder>();
void PngEncoder::writeDataToBuf(void *_png_ptr, uchar *src, size_t size)
    if (size == 0)
        return;
    png_structp png_ptr  = (png_structp)_png_ptr;
    PngEncoder  *encoder = (PngEncoder *)(png_get_io_ptr(png_ptr));
    CV_Assert(encoder && encoder->m_buf);
    size_t cursz = encoder->m_buf->size();
    encoder->m_buf->resize(cursz + size);
    memcpy(&(*encoder->m_buf)[cursz], src, size);
void PngEncoder::flushBuf(void *)
bool PngEncoder::write(const Mat& img, const std::vector<int>& params)
    png_structp    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
    png_infop      info_ptr = 0;
    FILE *volatile f = 0;
    int            y, width = img.cols, height = img.rows;
    int            depth = img.depth(), channels = img.channels();
    volatile bool  result = false;
    AutoBuffer<uchar *> buffer;
    if ((depth != CV_8U) && (depth != CV_16U))
        return false;
    if (png_ptr)
        info_ptr = png_create_info_struct(png_ptr);
        if (info_ptr)
            if (setjmp(png_jmpbuf(png_ptr)) == 0)
                if (m_buf)
                    png_set_write_fn(png_ptr, this,
                                     (png_rw_ptr)writeDataToBuf, (png_flush_ptr)flushBuf);
                    f = fopen(m_filename.c_str(), "wb");
                    if (f)
                        png_init_io(png_ptr, (png_FILE_p)f);
                int  compression_level    = -1;                       // Invalid value to allow setting 0-9 as valid
                int  compression_strategy = IMWRITE_PNG_STRATEGY_RLE; // Default strategy
                bool isBilevel            = false;
                for (size_t i = 0; i < params.size(); i += 2)
                    if (params[i] == IMWRITE_PNG_COMPRESSION)
                        compression_strategy = IMWRITE_PNG_STRATEGY_DEFAULT; // Default strategy
                        compression_level    = params[i + 1];
                        compression_level    = MIN(MAX(compression_level, 0), Z_BEST_COMPRESSION);
                    if (params[i] == IMWRITE_PNG_STRATEGY)
                        compression_strategy = params[i + 1];
                        compression_strategy = MIN(MAX(compression_strategy, 0), Z_FIXED);
                    if (params[i] == IMWRITE_PNG_BILEVEL)
                        isBilevel = params[i + 1] != 0;
                if (m_buf || f)
                    if (compression_level >= 0)
                        png_set_compression_level(png_ptr, compression_level);
                        // tune parameters for speed
                        // (see http://wiki.linuxquestions.org/wiki/Libpng)
                        png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB);
                        png_set_compression_level(png_ptr, Z_BEST_SPEED);
                    png_set_compression_strategy(png_ptr, compression_strategy);
                    png_set_IHDR(png_ptr, info_ptr, width, height,
                                 depth == CV_8U ? isBilevel ? 1 : 8 : 16,
                                 channels == 1 ? PNG_COLOR_TYPE_GRAY :
                                 channels == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA,
                                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
                                 PNG_FILTER_TYPE_DEFAULT);
                    png_write_info(png_ptr, info_ptr);
                    if (isBilevel)
                        png_set_packing(png_ptr);
                    png_set_bgr(png_ptr);
                    if (!isBigEndian())
                        png_set_swap(png_ptr);
                    buffer.allocate(height);
                    for (y = 0; y < height; y++)
                        buffer[y] = img.data + y * img.step;
                    png_write_image(png_ptr, buffer);
                    png_write_end(png_ptr, info_ptr);
                    result = true;
    png_destroy_write_struct(&png_ptr, &info_ptr);
    if (f)
        fclose((FILE *)f);
    return result;

        从上部分代码实现可以看出OpenCV的PNG支持实现还是挺简单的,主要还是调用了libpng接口实现。

学习opencv 最开始一般都是从读取一张图片开始,在opencv 中我们可通过 imread() 来操作。首先我们看下imread 方法: 原型Mat imread( const String& filename, int flags ) 第一个参数 filename: 表示图像的路径。 第二个参数 flags:表示读取图像的方式。 IMREAD_UNCH...... 注意,flag默认为1,表示按照BGR三通道的方式进行读取,并且它的每个像素点都是以BGR三通道的方式进行存储,与flag取cv2.IMREAD_COLOR效果相同;flag也可取0,表示以灰度图形式进行读取,与flag取cv2.IMREAD_GRAYSCALE是效果相同。注意:一般而言第三个参数不用填写,第三个参数涉及到图片压缩质量等,此处不做讨论。一定要注意路径的表示方法!路径内的斜杠一定为/或者。该函数后面应该紧跟cv::waitKey函数。注意:其中路径内的斜杠一定为/或者。 Mat image_source = imread(“D:\program\xie.png”)直接放入图片的绝对路径 只需要把图像文件放在工程文件夹下和.cpp文件放在一起就行了,读取的时候就可以直接用名字读取,如imread(“miao.jpg”); src = imread( argv[1], 1 );方法是: 工程——属性——配置属性——调试——命令行参数,然后设置就行了。 argv[1... CV_LOAD_IMAGE_UNCHANGED (CV_LOAD_IMAGE_GRAYSCALE ( 0) loads the image as an intensity oneCV_LOAD_IMAGE_COLOR (>0) loads the image in the RGB format 如果待读取的png图片 cv2.imread()和matplotlib.image.imread()除了读取出来的rgb的顺序不一样,对于读取图片的类型要求也不一样,example:将.jpg改为.png,后者读取就有问题,而前者就顺利的读取出来了而且cv2.imread()不能有中文路径,否则读取不出来,一般opencv库都不允许中文路径... 我分析了一波,会不会是使用matplotlib.image.imread()读取图片返回的时候它自动对图片进行归一化处理了?为了验证,我先使用cv2.imread()读取图片并且对其进行归一化处理,然后用matplotlib.image.imread()读取图片,判断二者是否相等。所以,说明matplotlib.image.imread()在读取图像的时候顺便归一化了。... 关于cv::imread读取图片类型的初探问题来源环境首先生成单通道和三通道的png图片cv::imread函数及其参数不同参数读取rgb图像不同参数读取单通道图片 在处理深度图的时候,在用 cv::imread 读取深度图像时,本以为得到的是单通道图,但实际是三通道图。所以仔细看了一下 cv::imread 函数。 Ubuntu16 Opencv 4.0.0 首先生成单通道和三通... cv::imread()函数读取图片,cv::imwrite()写图片。 imread()支持的图像文件包括:BMP、DIB、JPEG、JPG、JPE、PNG、PBM、PGM、PPM、SR、RAS、TIFF、TIF、EXR、jp2。 函数原型: Mat imread(const string& filename, int flags = 1); bool imwrite(const string& filename, InputArray image, const vector< 第一个参数是图片路径名称,强烈建议使用/作为目录的分割符号,例如 第二参数具有如下的值,说明了读取过程中是否需要进行像素的操作,例如取值0,表示读取过程中将像素转换为灰度值,默认参数是1, 表示不改变原图像的像素 enum ImreadModes { IMREAD_UNCHA... 本人在图像处理项目过程中,经常需要将一幅jpg图像叠加到另一幅背景jpg图像上,来实现一些特定的需求。例如我们经常在抖音中看到一些视频特效的叠加效果,猫耳朵等等特效在背景人脸图像上的叠加。我们利用Python+OpenCV的方式可以很简单的实现jpg图像之间的叠加,但实际项目中更多需要png透明图像在jpg图像上叠加。这种情况下,仍然适用传统的jpg叠加方式,就会出现原本透明的png图像,叠加后直接变为不透明的jpg图像,达不到我们想要的效果。本篇将主要讲解如何利用Python+OpenCV来实现png透明 在使用变量获取批处理图像的相对路径后,会读取具体图像内容,那么问题是,在读取16位uint类型时,总是展示出一半的图像,此展示图的特点是,在行与列相同的情况下,图本身的内容为一半,不会有报错提示。原因有两个:一是在读取16位unit数据后,自动转化成unit8类型的数据,即,将16位unit数据直接除以255左右;而是在读取后,系统默认是8位变量,所以只存取了一半的数据内容。以下是解决问题: Mat srcImage; srcImage = imread(“路径内容”, CV_LOAD_IMAGE_UN