前言
之前都是使用的最基本的代码,不过那些都是招式。我们追求的往往都是内功。以后我们就开始我们的 内功修炼吧。
正文
我们最开始的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;
}
这里吧所有的文件都读取文成,这里也许还有一些疑问,最好可以断点调试下,我就不在详细介绍,其实这里不是重要的就是把关键的数据读入到一个一个二维矩阵,可以直接用来操作。其他编解码的问题,不是很重要,
后记
这仅仅记录下我的思路,没有整理出一套完成的框架,多少有些遗憾,不过这里还是比较简单。通过特定的处理。选择一个解码器,解码器的工作也比较简单,先读取头文件,然后开始解码内容。不过也算整理清楚一点内容。