opencv解码框架源码走读

前言

之前都是使用的最基本的代码,不过那些都是招式。我们追求的往往都是内功。以后我们就开始我们的 内功修炼吧。

正文

我们最开始的helloworld,用来显示一张图片,这里我们就从这里入手。当然我们那一篇的代码第一句是
Mat img = imread("pic.jpg");
我们就从这里开始。
首先是Mat这个类不太很复杂,不过我们这里不打算详细介绍,我们只要知道这个,类可以保存图片的所有内容。比如像素值,长宽高,等等。下面我们直接看函数,这里比较吸引我们

Mat imread( const String& filename, int flags )
{
    ......
    Mat img;

    /// load the data
    imread_( filename, flags, LOAD_MAT, &img );

    ......
    return img;
}

太简单了,就是调用了imread_( filename, flags, LOAD_MAT, &img );然后开始加载图片文件和解码。我们继续开始。

static void*
imread_( const String& filename, int flags, int hdrtype, Mat* mat=0 )
{
    IplImage* image = 0;
    CvMat *matrix = 0;
    Mat temp, *data = &temp;

    ImageDecoder decoder;

    ......
    decoder = findDecoder( filename );   //获取decoder

    decoder->setSource( filename );  //吧名字设置进去,很简单的。不详细介绍

    ......

    if( !decoder->readHeader() )  //相对来说比较关键、主要是读取文件头,得到图像信息,可以用来解码图片。

    ......

    Size size = validateInputImageSize(Size(decoder->width(), decoder->height()));  // //反正都是按照64位存储,现在的无论argb和rgb换个yuv都可以存储,这里暂时不管。

    ......
    mat->create( size.height, size.width, type );
    data = mat;
    ......

    decoder->readData(*data)  //关键

    return (void*)mat;
}

这里才到了我们最关键的地方,其实这里也仅仅是对图像进行解码。我们还是慢慢的看一下,首先是findDecoder( filename )源码也比较简单:

static ImageDecoder findDecoder( const String& filename ) {

    size_t i, maxlen = 0;

    //这是选取最长的那个signature。作为读入的数据的长度,然后确定到底用那个解码器
    for( i = 0; i < codecs.decoders.size(); i++ )
    {
        size_t len = codecs.decoders[i]->signatureLength();
        maxlen = std::max(maxlen, len);
    }

    /// Open the file
    FILE* f= fopen( filename.c_str(), "rb" );

    ......

    String signature(maxlen, ' ');
    maxlen = fread( (void*)signature.c_str(), 1, maxlen, f );  //读取前边的签名,例如btm文件,开头就是bm0;而我们只检查前边是否是有bm即可判断
    ......
    signature = signature.substr(0, maxlen);

    for( i = 0; i < codecs.decoders.size(); i++ )
    {
        if( codecs.decoders[i]->checkSignature(signature) ) //找到一个适合的,返回回去,就ok了
            return codecs.decoders[i]->newDecoder();  
    }

    return ImageDecoder();
}

稍微注意下,codecs这是一个静态的变量,

static ImageCodecInitializer codecs;

ImageCodecInitializer()
    {
        /// BMP Support
        decoders.push_back( makePtr<BmpDecoder>() );
        encoders.push_back( makePtr<BmpEncoder>() );

        decoders.push_back( makePtr<HdrDecoder>() );
        encoders.push_back( makePtr<HdrEncoder>() );
    #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
        decoders.push_back( makePtr<SunRasterDecoder>() );
        encoders.push_back( makePtr<SunRasterEncoder>() );
        decoders.push_back( makePtr<PxMDecoder>() );
        encoders.push_back( makePtr<PxMEncoder>() );
    #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*/
        decoders.push_back( makePtr<PAMDecoder>() );
        encoders.push_back( makePtr<PAMEncoder>() );
    }

    std::vector<ImageDecoder> decoders;
    std::vector<ImageEncoder> encoders;
};

解码有两个关键函数一个是readHeader()一个是readData(*data)。我们接着就这俩关键的中心研究一下。我们只要看到的是bmp的解码信息,其他的可以自己慢慢读源码。

bool  BmpDecoder::readHeader()
{
    bool result = false;
    bool iscolor = false;

    ......

    CV_TRY
    {
        m_strm.skip( 10 );//跳过十个开头的解码器标志位,
        m_offset = m_strm.getDWord();  //读取四位54 = 0x36  我们图片的保存的内容是  36 00 00 00 28 00 00 00

        int  size = m_strm.getDWord();   //对于我们刚才的一个文件读取的是40 = 0x28

        if( size >= 36 )  //对于我们刚才的一个文件读取的是40 = 0x28,我们图片的保存的内容是  36 00 00 00 28 00 00 00   
        {
            //这我们文件的信息   58 02 00 00 52 01 00 00 01 00 18 00 00 00 00 00
            m_width  = m_strm.getDWord();  //接着又读取几位,600 =0x258
            m_height = m_strm.getDWord();   // 338
            m_bpp    = m_strm.getDWord() >> 16;  //24=0x18;
            m_rle_code = (BmpCompression)m_strm.getDWord();  //0
            m_strm.skip(12); 
            //后面是 00 00 00 00 00 00 00 00
            int clrused = m_strm.getDWord(); //0
            m_strm.skip( size - 36 ); //  这就把所有的头文件都读完了。

            if( m_width > 0 && m_height != 0 &&
             (((m_bpp == 1 || m_bpp == 4 || m_bpp == 8 ||
                m_bpp == 24 || m_bpp == 32 ) && m_rle_code == BMP_RGB) ||
               ((m_bpp == 16 || m_bpp == 32) && (m_rle_code == BMP_RGB || m_rle_code == BMP_BITFIELDS)) ||
               (m_bpp == 4 && m_rle_code == BMP_RLE4) ||
               (m_bpp == 8 && m_rle_code == BMP_RLE8)))
            {
                iscolor = true;
                result = true;

                ......
            }
        }
        ......
    }
    CV_CATCH_ALL
    {
        CV_RETHROW();
    }
    // in 32 bit case alpha channel is used - so require CV_8UC4 type
    m_type = iscolor ? (m_bpp == 32 ? CV_8UC4 : CV_8UC3 ) : CV_8UC1;  //取CV_8UC3
    m_origin = m_height > 0 ? IPL_ORIGIN_BL : IPL_ORIGIN_TL;  //IPL_ORIGIN_BL
    m_height = std::abs(m_height);

    ......
    return result;
}

这里的思路还是比较简单,总是就是读取文件的开头几个字节。然后,开始解析。这里大家最好找到一个bmp文件,然后查看16进制文件,会一目了然。关于bmp的编解码,这里不再详细介绍可以阅读这篇博客:bmp编码详情
这里我吧我自己的一个bmp文件的头文件部分数据截屏出来这里写图片描述
然后我们又要开始真正的解码部分了。(记住我们已经把文件读取地址指向了图片信息的地方)

bool  BmpDecoder::readData( Mat& img )
{
    uchar* data = img.ptr();
    int step = validateToInt(img.step);
    bool color = img.channels() > 1;
    uchar  gray_palette[256] = {0};
    bool   result = false;
    int  src_pitch = ((m_width*(m_bpp != 15 ? m_bpp : 16) + 7)/8 + 3) & -4;
    int  nch = color ? 3 : 1;
    int  y, width3 = m_width*nch;

    ......

    if( m_origin == IPL_ORIGIN_BL )
    {
        data += (m_height - 1)*(size_t)step;
        step = -step;
    }

    AutoBuffer<uchar> _src, _bgr;
    _src.allocate(src_pitch + 32);

    uchar *src = _src, *bgr = _bgr;

    CV_TRY
    {
        m_strm.setPos( m_offset );

        switch( m_bpp )
        {

        ......
        case 24:
            for( y = 0; y < m_height; y++, data += step )
            {
                m_strm.getBytes( src, src_pitch );
                ......

                    memcpy( data, src, m_width*3 );  //因为data是一个二维数组,所以step即一个维度,直接复制就ok了。我们这里把余下的数据全部复制完成。
            }
            result = true;
            break;

        default:
            CV_ErrorNoReturn(cv::Error::StsError, "Invalid/unsupported mode");
        }
    }
    CV_CATCH_ALL
    {
        CV_RETHROW();
    }

    return result;
}

这里吧所有的文件都读取文成,这里也许还有一些疑问,最好可以断点调试下,我就不在详细介绍,其实这里不是重要的就是把关键的数据读入到一个一个二维矩阵,可以直接用来操作。其他编解码的问题,不是很重要,

后记

这仅仅记录下我的思路,没有整理出一套完成的框架,多少有些遗憾,不过这里还是比较简单。通过特定的处理。选择一个解码器,解码器的工作也比较简单,先读取头文件,然后开始解码内容。不过也算整理清楚一点内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值