【OpenCV】学习札记与源码分析: imread( )函数
引言
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; }========================================================= 转载请注明出处:http://blog.****.net/songzitea/article/details/11096287 =========================================================