源码解析: Imread函数

时间:2022-04-30 15:00:03

引言

imread()函数在opencv使用比较。

imread()函数

声明:

Mat imread(const string& filename, int flags);

这很标准的写法,传入一个string类型的常量引用。

定义:

Mat imread(const string& filename, int flags)
{
Mat img; //创建一个变量
imread_(filename,flags,LOAD_MAT,&img);
return img;
}

其中imread_()中&img用的是地址符号,为什么呢?当然是为了改变其里面的数据了。imread( )函数是就这么几行么?这么几行能干什么呢?其实它把所有的事情交给了imread_()函数。所以,我们进一步分析imread_()函数。

imread_()函数

声明:

static void* imread_(const string& filename, int flags, int hdrtype, Mat* mat=0 );

其中这个函数返回的是一个空指针,其实在上面,这个返回值时没有用到的。 filename:文件地址 flags:标志,读取什么样(灰度,彩色)图像hdrtype:传入的为载入什么类型(enum {LOAD_CVMAT=0,LOAD_IMAGE=1, LOAD_MAT=2 };这三个中的一个。) Mat :保存图像的Mat对象了。

定义:

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 = findDecoder(filename);//这个是解析图像的后缀名的,用来决定读取特定图像的数据,所有的事情都是它干了。
if( decoder.empty() )
return 0;
decoder->setSource(filename);
if( !decoder->readHeader() )//读取图像的头部信息
return 0;

CvSize size; //读取图像的大小
size.width = decoder->width();
size.height = decoder->height();

int type = decoder->type();//读取类型?
if( flags != -1 )
{ //决定什么样的类型
if( (flags & CV_LOAD_IMAGE_ANYDEPTH) == 0 )
type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));

if( (flags & CV_LOAD_IMAGE_COLOR) != 0 ||
((flags & CV_LOAD_IMAGE_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1) )
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);//彩色
else
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1);//灰度
}

if( hdrtype == LOAD_CVMAT || hdrtype == LOAD_MAT )
{
if( hdrtype == LOAD_CVMAT )
{
matrix = cvCreateMat( size.height, size.width, type );
temp = cvarrToMat(matrix); //创建一个空的,即还没有图像数据的对象。
}
else
{
mat->create( size.height, size.width, type );
data = mat;
}
}
else//就是传入的类型都为IplImage*类型的
{
image = cvCreateImage( size, cvIplDepth(type), CV_MAT_CN(type) );
temp = cvarrToMat(image);
}

if(!decoder->readData(*data))//读取数据,这里应该是复制数据,想一想这个是要懂硬盘上去读取数据的。
{//失败
cvReleaseImage(&image);
cvReleaseMat(&matrix);//c风格的释放
if( mat )
mat->release();//c++风格的释放
return 0;
}

return hdrtype == LOAD_CVMAT ? (void*)matrix :
hdrtype == LOAD_IMAGE ? (void*)image : (void*)mat;//最后返回c类型的图像指针,这个是为了考虑c风格的
}

上面看似不长啊,可这个怎么能干那么多的事情呢?那么就一步步的来分析吧。

这个看了半天,是不是没有什么实质性的东西?一个到底怎么读图还没完全了解吧。全都封装了起来(decoder),你看不到所有的细节,而只是一个大概的流程。这个流程不是自己都知道了么?

不管怎么样,我不想关注这个图是怎么解析的,只看这函数怎么把数据给读进了Mat中,先保持这个目标不变:

首先:传入了一个Mat类型的变量,这个变量是传了地址的,也就是会改变这个mat类型变量。Mat在构造函数中开始会构造什么呢?尤其是默认构造函数?其实他什么也没有构造,因为什么都不知道。

其次:Mat类型需要记录图像的哪些数据呢?一个是头:图像是灰度或彩色(这里姑且只考虑这两种),一个图像数据的大小(图像的宽与高);一个数据体:二维数组或是一维数组。

最后:从decoder中读入data数据。当然这里会牵涉到图像解码过程的(这个如果特别感兴趣就看看了,否则不用了)。

在第一篇处,我们只是在最表层的上面操作函数,当别人问我们时,我们其实什么也不知道的。就知道,imread是读取函数了,然后掉用其它的函数的乐乐。当然,上面我们可以好好学习人家为什么要这样做了!这里,看一个函数finddecoder()。这个函数主要是获取decoder对象,从而决定读取什么样后缀名的图像(jpg,bmp等等)

findDecoder()函数

声明:

ImageDecoder findDecoder( const string& filename );//在highgui/loadsave.cpp中
ImageDecoder findDecoder( const Mat& buf );
这个是一个函数的重载了,在第一篇,即imread函数中调用的是第一个,这里就跟进第一个。

定义:

ImageDecoder findDecoder( const string& filename )
{
size_t i, maxlen = 0;
for( i = 0; i < decoders.size(); i++ )//这里第一个decoders是什么呢?在文件中有这样的一个定义:static vector<ImageDecoder> decoders;好家伙,原来是一个向量,这里第一问,为什么要是一个向量,而且还是全局静态变量,就是说整个程序运行期间它都存在。其只初始化一遍。
{
size_t len = decoders[i]->signatureLength();//这一个循环是寻找第一个数据点保存的数据:signature。从这里可以看出,其实后缀名在这里没有什么用处,文件本身是保存了这个类型值的。
maxlen = std::max(maxlen, len);//读取数据长度为一个最大的。
}

FILE* f= fopen( filename.c_str(), "rb" );//熟悉的c函数,读取文件。哈哈,从这个可以看到,所有的文件都可以有FILE来读取的。
if( !f )
return ImageDecoder();//没有读取成功,返回一个空的decoder。处理错误的能力。
string signature(maxlen, ' ');

maxlen = fread( &signature[0], 1, maxlen, f );//从文件中读取signature数据,这里是用了string,且是按字节读取。string底层用了什么结构呢?string[0]返回的是一个什么值呢?这有待查询。

/*这里是一个试验:

int maxlen = 10 ;
string sig(maxlen,' ');
cout<<&sig<<endl;
cout<<((int*)&sig[0])<<endl;

cout<<((int*)&sig[1])<<endl;

从其中可以看出两个量的值是不同的:

0x22ff40
0x3e3cbc //这里和后面的数据相差一个字节,代表这是一个char类型的
0x3e3cbd

*/
fclose(f);
signature = signature.substr(0, maxlen);

for( i = 0; i < decoders.size(); i++ )
{
if( decoders[i]->checkSignature(signature) )//检测读取出来的数据是否与保存的数据signature一样,一样就代表是这个类型了。
return decoders[i]->newDecoder();//创建这个类型的decoder变量。
}

return ImageDecoder();
}

下面是decoders数据内容的来源:

struct ImageCodecInitializer
{
ImageCodecInitializer( )
{
decoders.push_back(new BmpDecoder);
encoders.push_back(new BmpEncoder);
#ifdef HAVE_JPEG
decoders.push_back(new JpegDecoder);
encoders.push_back(new JpegEncoder);
#endif
decoders.push_back(new SunRasterDecoder);
encoders.push_back(new SunRasterEncoder);
decoders.push_back(new PxMDecoder);
encoders.push_back(new PxMEncoder);
#ifdef HAVE_TIFF
decoders.push_back(new TiffDecoder);
#endif
encoders.push_back(new TiffEncoder);
#ifdef HAVE_PNG
decoders.push_back(new PngDecoder);
encoders.push_back(new PngEncoder);
#endif
#ifdef HAVE_JASPER
decoders.push_back(new Jpeg2KDecoder);
encoders.push_back(new Jpeg2KEncoder);
#endif
#ifdef HAVE_OPENEXR
decoders.push_back(new ExrDecoder);
encoders.push_back(new ExrEncoder);
#endif
// because it is a generic image I/O API, supporting many formats,
// it should be last in the list.
#ifdef HAVE_IMAGEIO
decoders.push_back(new ImageIODecoder);
encoders.push_back(new ImageIOEncoder);
#endif
}
};

static ImageCodecInitializer initialize_codecs; //这里直接的给定了decoders的内容。

这样读取图像数据又清晰了一步,首先是我们会首先保存好要解析的图像格式,即支持什么类型的图像。然后第一步是取读取保存在第一个数据点上的signature数据,然后再去读取下面的数据。所以,要很清楚的了解图像的头。

ImageDecoder类

ImageDecoder这个类,这个类其实就是一个图像数据的解析类。且看下面的

源代码:

class BaseImageDecoder //这就是我们要找的ImageDecoder类
{
public:
BaseImageDecoder();
virtual ~BaseImageDecoder() {};

int width() const { return m_width; };
int height() const { return m_height; };
int type() const { return m_type; };

virtual bool setSource( const string& filename );
virtual bool setSource( const Mat& buf );
virtual bool readHeader() = 0;
virtual bool readData( Mat& img ) = 0;

virtual size_t signatureLength() const;//(1)
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;
string m_filename;
string m_signature;//(2)
Mat m_buf;
bool m_buf_supported;
};
下面定义了一些类型,

typedef Ptr<BaseImageDecoder> ImageDecoder; //
这里typedef了一个类型ImageDecoder,这个最原始的类型为Ptr,猜想是一个指针,但是在源代码中没能找到其声明和定义的地方。但这不妨碍源码的阅读。

在findDecoder中用到了:static vector<ImageDecoder> decoders;

且在代码中有:decoders[i]->signatureLength();的调用,且可以看到(1)处就有这个函数。

size_t BaseImageDecoder::signatureLength() const
{
return m_signature.size();
}
上面这个函数其实返回的就是其保存的图像签名(signature)(2)

bool BaseImageDecoder::checkSignature(const string& signature) const
{
size_t len = signatureLength();
return signature.size()>= len&&memcmp(signature.c_str(),m_signature.c_str(),len)==0;
}
ImageDecoder BaseImageDecoder::newDecoder() const
{
return ImageDecoder();//这里其实就是一个直接的调用了一个构造函数
}
BaseImageDecoder::BaseImageDecoder()
{
m_width = m_height = 0;
m_type = -1;
m_buf_supported = false;
}
这里看到的只是一个Base类,那么要真真的执行就是涉及到特定的图像类别了。

这里不看别的,就看一个bmp类型的吧:

class BmpDecoder : public BaseImageDecoder
{
public:
BmpDecoder();
~BmpDecoder();

bool readData( Mat& img );//很明显这是读取数据
bool readHeader();
void close();
ImageDecoder newDecoder() const;//这里重新的给了一个声明,表明在调用的时候调用的是子类的东西。

protected:
RLByteStream m_strm;
PaletteEntry m_palette[256];//调试版(palette)
int m_origin;
int m_bpp;
int m_offset;
BmpCompression m_rle_code;

/*压缩的格式
enum BmpCompression
{
BMP_RGB = 0,
BMP_RLE8 = 1,
BMP_RLE4 = 2,
BMP_BITFIELDS = 3
};
*/
};

bmpDecoder类里面的东西很多,这里简要的给看看:
ImageDecoder BmpDecoder::newDecoder() const
{
return new BmpDecoder;
}

BmpDecoder::BmpDecoder()
{
m_signature = fmtSignBmp;//static const char* fmtSignBmp = "BM"; 设定了自己标签名
m_offset = -1;
m_buf_supported = true;//buf设定为true,表示可以读取数据
}
bmpDecoder类里面的东西很多,这里简要的给看看:

ImageDecoder BmpDecoder::newDecoder() const
{
return new BmpDecoder;
}

BmpDecoder::BmpDecoder()
{
m_signature = fmtSignBmp;//static const char* fmtSignBmp = "BM"; 设定了自己标签名
m_offset = -1;
m_buf_supported = true;//buf设定为true,表示可以读取数据
}
这里就完全的看到了底层的代码了。这就需要完全的理解bmp图像的保存格式了。还没完全的了解bmp,暂且把这个代码贴在这吧。

bool  BmpDecoder::readData(Mat& img){
uchar* data = img.data;
int step = (int)img.step;
bool color = img.channels() > 1;
uchar gray_palette[256];
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_offset < 0 || !m_strm.isOpened())
return false;

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

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

if(!color){
if(m_bpp <= 8){
CvtPaletteToGray( m_palette, gray_palette, 1 << m_bpp );
}
_bgr.allocate(m_width*3 + 32);
}
uchar *src = _src, *bgr = _bgr;

try{
m_strm.setPos(m_offset);
switch(m_bpp){
/************************* 1 BPP ************************/
case 1:
for( y = 0; y < m_height; y++, data += step){
m_strm.getBytes( src, src_pitch );
FillColorRow1( color ? data : bgr, src, m_width, m_palette );
if( !color )
icvCvt_BGR2Gray_8u_C3C1R( bgr, 0, data, 0, cvSize(m_width,1));
}
result = true;
break;

/************************* 4 BPP ************************/
case 4:
if( m_rle_code == BMP_RGB){
for( y = 0; y < m_height; y++, data += step )
{
m_strm.getBytes( src, src_pitch );
if( color )
FillColorRow4( data, src, m_width, m_palette );
else
FillGrayRow4( data, src, m_width, gray_palette );
}
result = true;
}
else if( m_rle_code == BMP_RLE4 ) // rle4 compression
{
uchar* line_end = data + width3;
y = 0;

for(;;)
{
int code = m_strm.getWord();
int len = code & 255;
code >>= 8;
if( len != 0 ) // encoded mode
{
PaletteEntry clr[2];
uchar gray_clr[2];
int t = 0;

clr[0] = m_palette[code >> 4];
clr[1] = m_palette[code & 15];
gray_clr[0] = gray_palette[code >> 4];
gray_clr[1] = gray_palette[code & 15];

uchar* end = data + len*nch;
if( end > line_end ) goto decode_rle4_bad;
do
{
if( color )
WRITE_PIX( data, clr[t] );
else
*data = gray_clr[t];
t ^= 1;
}
while( (data += nch) < end );
}
else if( code > 2 ) // absolute mode
{
if( data + code*nch > line_end ) goto decode_rle4_bad;
m_strm.getBytes( src, (((code + 1)>>1) + 1) & -2 );
if( color )
data = FillColorRow4( data, src, code, m_palette );
else
data = FillGrayRow4( data, src, code, gray_palette );
}
else
{
int x_shift3 = (int)(line_end - data);
int y_shift = m_height - y;

if( code == 2 )
{
x_shift3 = m_strm.getByte()*nch;
y_shift = m_strm.getByte();
}

len = x_shift3 + ((y_shift * width3) & ((code == 0) - 1));

if( color )
data = FillUniColor( data, line_end, step, width3,
y, m_height, x_shift3,
m_palette[0] );
else
data = FillUniGray( data, line_end, step, width3,
y, m_height, x_shift3,
gray_palette[0] );

if( y >= m_height )
break;
}
}
result = true;
decode_rle4_bad: ;
}
break;

/************************* 8 BPP ************************/
case 8:
if( m_rle_code == BMP_RGB )
{
for( y = 0; y < m_height; y++, data += step )
{
m_strm.getBytes( src, src_pitch );
if( color )
FillColorRow8( data, src, m_width, m_palette );
else
FillGrayRow8( data, src, m_width, gray_palette );
}
result = true;
}
else if( m_rle_code == BMP_RLE8 ) // rle8 compression
{
uchar* line_end = data + width3;
int line_end_flag = 0;
y = 0;

for(;;)
{
int code = m_strm.getWord();
int len = code & 255;
code >>= 8;
if( len != 0 ) // encoded mode
{
int prev_y = y;
len *= nch;

if( data + len > line_end )
goto decode_rle8_bad;

if( color )
data = FillUniColor( data, line_end, step, width3,
y, m_height, len,
m_palette[code] );
else
data = FillUniGray( data, line_end, step, width3,
y, m_height, len,
gray_palette[code] );

line_end_flag = y - prev_y;
}
else if( code > 2 ) // absolute mode
{
int prev_y = y;
int code3 = code*nch;

if( data + code3 > line_end )
goto decode_rle8_bad;
m_strm.getBytes( src, (code + 1) & -2 );
if( color )
data = FillColorRow8( data, src, code, m_palette );
else
data = FillGrayRow8( data, src, code, gray_palette );

line_end_flag = y - prev_y;
}
else
{
int x_shift3 = (int)(line_end - data);
int y_shift = m_height - y;

if( code || !line_end_flag || x_shift3 < width3 )
{
if( code == 2 )
{
x_shift3 = m_strm.getByte()*nch;
y_shift = m_strm.getByte();
}

x_shift3 += (y_shift * width3) & ((code == 0) - 1);

if( y >= m_height )
break;

if( color )
data = FillUniColor( data, line_end, step, width3,
y, m_height, x_shift3,
m_palette[0] );
else
data = FillUniGray( data, line_end, step, width3,
y, m_height, x_shift3,
gray_palette[0] );

if( y >= m_height )
break;
}

line_end_flag = 0;
}
}

result = true;
decode_rle8_bad: ;
}
break;
/************************* 15 BPP ************************/
case 15:
for( y = 0; y < m_height; y++, data += step )
{
m_strm.getBytes( src, src_pitch );
if( !color )
icvCvt_BGR5552Gray_8u_C2C1R( src, 0, data, 0, cvSize(m_width,1) );
else
icvCvt_BGR5552BGR_8u_C2C3R( src, 0, data, 0, cvSize(m_width,1) );
}
result = true;
break;
/************************* 16 BPP ************************/
case 16:
for( y = 0; y < m_height; y++, data += step )
{
m_strm.getBytes( src, src_pitch );
if( !color )
icvCvt_BGR5652Gray_8u_C2C1R( src, 0, data, 0, cvSize(m_width,1) );
else
icvCvt_BGR5652BGR_8u_C2C3R( src, 0, data, 0, cvSize(m_width,1) );
}
result = true;
break;
/************************* 24 BPP ************************/
case 24:
for( y = 0; y < m_height; y++, data += step )
{
m_strm.getBytes( src, src_pitch );
if(!color)
icvCvt_BGR2Gray_8u_C3C1R( src, 0, data, 0, cvSize(m_width,1) );
else
memcpy( data, src, m_width*3 );
}
result = true;
break;
/************************* 32 BPP ************************/
case 32:
for( y = 0; y < m_height; y++, data += step )
{
m_strm.getBytes( src, src_pitch );

if( !color )
icvCvt_BGRA2Gray_8u_C4C1R( src, 0, data, 0, cvSize(m_width,1) );
else
icvCvt_BGRA2BGR_8u_C4C3R( src, 0, data, 0, cvSize(m_width,1) );
}
result = true;
break;
default:
assert(0);
}
}
catch(...)
{
}

return result;
}

关于Image Engineering & Computer Vision的更多讨论与交流,敬请关注本博和新浪微博songzi_tea.