在上一篇 OpenCV源码剖析之imread JPEG 中讲解了怎么使用OpenCV imread接口读取JPEG图片,其中有一点当时只是说遍历注册了的解码器,找到与传入图片相对应的解码器进行解码,而对于解码器注册等相关内容没有去深入探讨,本次将讲解下这部分的内容---ImageDecoder。
上章中我们提到在findDecoder函数里通过for循环遍历找到对应的解码器:
for (i = 0; i < codecs.decoders.size(); i++)
{
if (codecs.decoders[i]->checkSignature(signature))
{
return codecs.decoders[i]->newDecoder();
}
}
可以看到这里主要就是根据codecs这个全局变量展开处理的了,其定义就在findDecoder上面:static ImageCodecInitializer codecs;ImageCodecInitializer是一个结构体,其定义为
struct ImageCodecInitializer
{
/**
* Default Constructor for the ImageCodeInitializer
*/
ImageCodecInitializer()
{
/// BMP Support
decoders.push_back(makePtr<BmpDecoder>());
encoders.push_back(makePtr<BmpEncoder>());
#ifdef HAVE_IMGCODEC_HDR
decoders.push_back(makePtr<HdrDecoder>());
encoders.push_back(makePtr<HdrEncoder>());
#endif
#ifdef HAVE_JPEG
decoders.push_back(makePtr<JpegDecoder>());
encoders.push_back(makePtr<JpegEncoder>());
#endif
#ifdef HAVE_WEBP
decoders.push_back(makePtr<WebPDecoder>());
encoders.push_back(makePtr<WebPEncoder>());
#endif
#ifdef HAVE_IMGCODEC_SUNRASTER
decoders.push_back(makePtr<SunRasterDecoder>());
encoders.push_back(makePtr<SunRasterEncoder>());
#endif
#ifdef HAVE_IMGCODEC_PXM
decoders.push_back(makePtr<PxMDecoder>());
encoders.push_back(makePtr<PxMEncoder>(PXM_TYPE_AUTO));
encoders.push_back(makePtr<PxMEncoder>(PXM_TYPE_PBM));
encoders.push_back(makePtr<PxMEncoder>(PXM_TYPE_PGM));
encoders.push_back(makePtr<PxMEncoder>(PXM_TYPE_PPM));
decoders.push_back(makePtr<PAMDecoder>());
encoders.push_back(makePtr<PAMEncoder>());
#endif
#ifdef HAVE_TIFF
decoders.push_back(makePtr<TiffDecoder>());
encoders.push_back(makePtr<TiffEncoder>());
#endif
#ifdef HAVE_PNG
decoders.push_back(makePtr<PngDecoder>());
encoders.push_back(makePtr<PngEncoder>());
#endif
#ifdef HAVE_GDCM
decoders.push_back(makePtr<DICOMDecoder>());
#endif
#ifdef HAVE_JASPER
decoders.push_back(makePtr<Jpeg2KDecoder>());
encoders.push_back(makePtr<Jpeg2KEncoder>());
#endif
#ifdef HAVE_OPENEXR
decoders.push_back(makePtr<ExrDecoder>());
encoders.push_back(makePtr<ExrEncoder>());
#endif
#ifdef HAVE_GDAL
/// Attach the GDAL Decoder
decoders.push_back(makePtr<GdalDecoder>());
#endif /*HAVE_GDAL*/
}
std::vector<ImageDecoder> decoders;
std::vector<ImageEncoder> encoders;
};
该结构体定义了2个vector容器变量decoders和encoders,类型分别为ImageDecoder和ImageEncoder,并且在结构体里面实现了构造函数的初始化,主要就是根据宏定义将支持的编码、解码类型加入到decoders和encoders中,可以认为这就实现了对支持的编解码器的注册了。
接下来就该看下 ImageDecoder和ImageEncoder这两个数据类型了。在grfmt_base.hpp中可以找到他们的定义:
namespace cv
{
class BaseImageDecoder;
class BaseImageEncoder;
typedef Ptr<BaseImageEncoder> ImageEncoder;
typedef Ptr<BaseImageDecoder> ImageDecoder;
/ base class for decoders
class BaseImageDecoder
{
public:
BaseImageDecoder();
virtual ~BaseImageDecoder() {}
int width() const { return m_width; }
int height() const { return m_height; }
virtual int type() const { return m_type; }
virtual bool setSource( const String& filename );
virtual bool setSource( const Mat& buf );
virtual int setScale( const int& scale_denom );
virtual bool readHeader() = 0;
virtual bool readData( Mat& img ) = 0;
/// Called after readData to advance to the next page, if any.
virtual bool nextPage() { return false; }
virtual size_t signatureLength() const;
virtual bool checkSignature( const String& signature ) const;
virtual ImageDecoder newDecoder() const;
protected:
int m_width; // width of the image ( filled by readHeader )
int m_height; // height of the image ( filled by readHeader )
int m_type;
int m_scale_denom;
String m_filename;
String m_signature;
Mat m_buf;
bool m_buf_supported;
};
/ base class for encoders
class BaseImageEncoder
{
public:
BaseImageEncoder();
virtual ~BaseImageEncoder() {}
virtual bool isFormatSupported( int depth ) const;
virtual bool setDestination( const String& filename );
virtual bool setDestination( std::vector<uchar>& buf );
virtual bool write( const Mat& img, const std::vector<int>& params ) = 0;
virtual bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params);
virtual String getDescription() const;
virtual ImageEncoder newEncoder() const;
virtual void throwOnEror() const;
protected:
String m_description;
String m_filename;
std::vector<uchar>* m_buf;
bool m_buf_supported;
String m_last_error;
};
}
可以看出在这里是定义了2个图像处理基类,其实现在grfmt_base.cpp中:
namespace cv
{
BaseImageDecoder::BaseImageDecoder()//默认构造函数
{
m_width = m_height = 0;
m_type = -1;
m_buf_supported = false;
m_scale_denom = 1;
}
bool BaseImageDecoder::setSource( const String& filename )//设置解码器的数据源--来自文件
{
m_filename = filename;
m_buf.release();
return true;
}
bool BaseImageDecoder::setSource( const Mat& buf )//设置解码器的数据源--来自内存
{
if( !m_buf_supported )
return false;
m_filename = String();
m_buf = buf;
return true;
}
size_t BaseImageDecoder::signatureLength() const//获取对应格式signature的大小
{
return m_signature.size();
}
bool BaseImageDecoder::checkSignature( const String& signature ) const//传入的signature和解码器对应的signature是否一致
{
size_t len = signatureLength();
return signature.size() >= len && memcmp( signature.c_str(), m_signature.c_str(), len ) == 0;
}
int BaseImageDecoder::setScale( const int& scale_denom )
{
int temp = m_scale_denom;
m_scale_denom = scale_denom;
return temp;
}
ImageDecoder BaseImageDecoder::newDecoder() const
{
return ImageDecoder();
}
BaseImageEncoder::BaseImageEncoder()
{
m_buf = 0;
m_buf_supported = false;
}
bool BaseImageEncoder::isFormatSupported( int depth ) const
{
return depth == CV_8U;
}
String BaseImageEncoder::getDescription() const
{
return m_description;
}
bool BaseImageEncoder::setDestination( const String& filename )//设置编码目的--编码到文件
{
m_filename = filename;
m_buf = 0;
return true;
}
bool BaseImageEncoder::setDestination( std::vector<uchar>& buf )//设置编码目的--编码到内存
{
if( !m_buf_supported )
return false;
m_buf = &buf;
m_buf->clear();
m_filename = String();
return true;
}
bool BaseImageEncoder::writemulti(const std::vector<Mat>&, const std::vector<int>& )
{
return false;
}
ImageEncoder BaseImageEncoder::newEncoder() const
{
return ImageEncoder();
}
void BaseImageEncoder::throwOnEror() const
{
if(!m_last_error.empty())
{
String msg = "Raw image encoder error: " + m_last_error;
CV_Error( CV_BadImageSize, msg.c_str() );
}
}
}
至此,关于OpenCV的ImageDecoders和ImageEncoders就讲解完了。下面在以JPEG为例分析解码过程。这里我们已经知道了有一个图像解码的基类BaseImageDecoder,下面看看OpenCV JPEG解码器JpegDecoder是如何处理的,其定义如下,可在grfmt_jpeg.hpp中找到。
namespace cv
{
class JpegDecoder CV_FINAL : public BaseImageDecoder
{
public:
JpegDecoder();
virtual ~JpegDecoder();
bool readData( Mat& img ) CV_OVERRIDE;
bool readHeader() CV_OVERRIDE;
void close();
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
FILE* m_f;
void* m_state;
private:
JpegDecoder(const JpegDecoder &); // copy disabled
JpegDecoder& operator=(const JpegDecoder &); // assign disabled
};
class JpegEncoder CV_FINAL : public BaseImageEncoder
{
public:
JpegEncoder();
virtual ~JpegEncoder();
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
};
}
可以看到,无论是JPEG encoder是从基类BaseImageEncoder中派生而来的,JpegDecoder是从BaseImageDecoder中派生而来,除了基类的一些实现外,派生类也有新的实现。这部分代码在OpenCV源码剖析之imread JPEG 中有该部分的分析,这里将不在展开讲述。
至此我们对OpenCV的imread也有了一个清晰的认识,JpegDecoder类继承BaseImageDecoder,并且在JpegDecoder类的实现中调用libJpeg的API实现图片的解码,最终将解码的数据返回给Mat的ptr,而Mat ptr又和data相连,最终就是读入图片通过调用底层解码将解码的数据返回到Mat data中了。