在经历第一份工作的2年半后,有幸能够从新进入到图像处理这个领域来,与以前工作时只能空闲时间看看OpenCV源码、博客和了解OpenCV最新动态这种三天打鱼两天晒网的不同,这次自己可以专心扎进这里面来了。学习图像处理自然少不了OpenCV,其源码完全开源、强大的使用群体和社区资源是学习图像处理的不二之选,其源码也是十分值得图像处理人研究学习的。从这期开始将逐渐分析其种的源码实现,也算是对平时的工作、学习做个备份,正所谓好记性不如烂笔头,且能看、能说和能写是完全不同的。本期将主要讲解OpenCV中对JPEG图片的解码处理。

使用OpenCV处理的图像的第一步就是载入图像数据了,这是一个图片解码的过程,这次将以JPEG图片为例进行说明。读取图片自然是离不开imread这个接口了,其函数原型如下:

Mat imread(const String& filename, int flags)

从OpenCV源码上看imread接口接受一个文件名和flags作为参数,filename就不用说了,自然就是我们要读入的图片,flags可以理解读取图片的方式了,其定义在imgcodecs.hpp里,具体如下,我们可以根据处理需要选择读取方式。

//! Imread flagsenum ImreadModes {IMREAD_UNCHANGED            = -1, //!< If set, return the loaded image as is (with alpha channel, otherwise it gets cropped).IMREAD_GRAYSCALE            = 0,  //!< If set, always convert image to the single channel grayscale image.IMREAD_COLOR                = 1,  //!< If set, always convert image to the 3 channel BGR color image.IMREAD_ANYDEPTH             = 2,  //!< If set, return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit.IMREAD_ANYCOLOR             = 4,  //!< If set, the image is read in any possible color format.IMREAD_LOAD_GDAL            = 8,  //!< If set, use the gdal driver for loading the image.IMREAD_REDUCED_GRAYSCALE_2  = 16, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/2.IMREAD_REDUCED_COLOR_2      = 17, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/2.IMREAD_REDUCED_GRAYSCALE_4  = 32, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/4.IMREAD_REDUCED_COLOR_4      = 33, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/4.IMREAD_REDUCED_GRAYSCALE_8  = 64, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/8.IMREAD_REDUCED_COLOR_8      = 65, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/8.IMREAD_IGNORE_ORIENTATION   = 128 //!< If set, do not rotate the image according to EXIF's orientation flag.};

imread接着调用了 imread_(filename, flags, LOAD_MAT, &img);函数,这函数上面也有注释,就是load the data了,说白了就是对图片进行解码,返回图片裸流数据了(就暂且这么叫吧),让我们移位更进步,到这个里面去看看,imread其中乾坤就在这里面了。

在imread_ 里面首先映入眼帘的都是ImageDecoder decoder;从这个命名来看这就是一个图片解码器了,由于我们起初并不知道需要什么样的解码方式才能解传入的图片,这就需要代码实现去找对应图片格式的解码器。这里有2种方式GdalDecoder().newDecoder()和findDecoder,第一个没用过,从其他解释来看这是针对遥感图像数据处理了,这里将不做详解,以后用到在see see。findDecoder接口只传入了一个filename,没错这就是我们读入图片的文件命令,findDecoder里面首先是遍历所有注册的解码器,找到最大的signatureLength,这个我们可以在grfmt_base.cpp里面找到,其实现如下:

size_t BaseImageDecoder::signatureLength() const
{return m_signature.size();}

从代码看可以知道这是属于BaseImageDecoder类的一个方法,而它的处理结果是返回m_signature的大小,针对于JPEG图片其赋值为 m_signature = "\xFF\xD8\xFF";这个可以在grfmt_jpeg.cpp里找到。找到这个长度后,就打开文件读取这个长度的数据到signature里去,接下来自然还是遍历所有注册的解码器,找到那个与传入图片signature相对应的解码器。

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;}

假设已经找到图片对应的解码器了,接下来的处理就是decoder->readHeader(),找到其对应的JPEG实现:

bool  JpegDecoder::readHeader()
{volatile bool result = false;close();JpegState* state = new JpegState;m_state = state;state->cinfo.err = jpeg_std_error(&state->jerr.pub);state->jerr.pub.error_exit = error_exit;if( setjmp( state->jerr.setjmp_buffer ) == 0 ){jpeg_create_decompress( &state->cinfo );if( !m_buf.empty() ){jpeg_buffer_src(&state->cinfo, &state->source);state->source.pub.next_input_byte = m_buf.ptr();state->source.pub.bytes_in_buffer = m_buf.cols*m_buf.rows*m_buf.elemSize();}else{m_f = fopen( m_filename.c_str(), "rb" );if( m_f )jpeg_stdio_src( &state->cinfo, m_f );}if (state->cinfo.src != 0){jpeg_read_header( &state->cinfo, TRUE );state->cinfo.scale_num=1;state->cinfo.scale_denom = m_scale_denom;m_scale_denom=1; // trick! to know which decoder used scale_denom see imread_jpeg_calc_output_dimensions(&state->cinfo);m_width = state->cinfo.output_width;m_height = state->cinfo.output_height;m_type = state->cinfo.num_components > 1 ? CV_8UC3 : CV_8UC1;result = true;}}if( !result )close();return result;}

这完全就是调用的JPEG实现了,大致就是创建JPEG解码器,设置输入源为文件,读取文件的宽高通道信息了。如果对这部分不是很清楚的,可以下载下libjpeg的源码,其example.c、libjpeg.txt都是你理解的好帮手。

接下来就是创建OpenCV图片管理器了,再接着就是读取图片,同理找到JPEG的对应实现:

bool  JpegDecoder::readData( Mat& img )
{volatile bool result = false;size_t step = img.step;bool color = img.channels() > 1;if( m_state && m_width && m_height ){jpeg_decompress_struct* cinfo = &((JpegState*)m_state)->cinfo;JpegErrorMgr* jerr = &((JpegState*)m_state)->jerr;JSAMPARRAY buffer = 0;if( setjmp( jerr->setjmp_buffer ) == 0 ){/* check if this is a mjpeg image format */if ( cinfo->ac_huff_tbl_ptrs[0] == NULL &&cinfo->ac_huff_tbl_ptrs[1] == NULL &&cinfo->dc_huff_tbl_ptrs[0] == NULL &&cinfo->dc_huff_tbl_ptrs[1] == NULL ){/* yes, this is a mjpeg image format, so load the correcthuffman table */my_jpeg_load_dht( cinfo,my_jpeg_odml_dht,cinfo->ac_huff_tbl_ptrs,cinfo->dc_huff_tbl_ptrs );}if( color ){if( cinfo->num_components != 4 ){cinfo->out_color_space = JCS_RGB;cinfo->out_color_components = 3;}else{cinfo->out_color_space = JCS_CMYK;cinfo->out_color_components = 4;}}else{if( cinfo->num_components != 4 ){cinfo->out_color_space = JCS_GRAYSCALE;cinfo->out_color_components = 1;}else{cinfo->out_color_space = JCS_CMYK;cinfo->out_color_components = 4;}}jpeg_start_decompress( cinfo );buffer = (*cinfo->mem->alloc_sarray)((j_common_ptr)cinfo,JPOOL_IMAGE, m_width*4, 1 );uchar* data = img.ptr();for( ; m_height--; data += step ){jpeg_read_scanlines( cinfo, buffer, 1 );if( color ){if( cinfo->out_color_components == 3 )icvCvt_RGB2BGR_8u_C3R( buffer[0], 0, data, 0, cvSize(m_width,1) );elseicvCvt_CMYK2BGR_8u_C4C3R( buffer[0], 0, data, 0, cvSize(m_width,1) );}else{if( cinfo->out_color_components == 1 )memcpy( data, buffer[0], m_width );elseicvCvt_CMYK2Gray_8u_C4C1R( buffer[0], 0, data, 0, cvSize(m_width,1) );}}result = true;jpeg_finish_decompress( cinfo );}}close();return result;
}

这里采用逐行读取解码的形式将解码后的图像数据拷贝给Mat ptr,至此对于JPEG的OpenCV imread实现就完全讲解完了,整个过程梳理下来,自己也收获不少。这就是自己对这部分的理解,欢迎大家留言交流。附上一个很好的libjpeg使用教程l:

点击打开链接

OpenCV源码剖析之imread JPEG相关推荐

  1. 【opencv源码剖析】背景建模mog2

    前言 opencv实现的背景建模方法有很多,早期的opencv版本modules/video/src下有acmmm2003(2.3.1版本).codebook(2.3.1版本).gmg(2.4.8版本 ...

  2. windows+vscode+opencv源码安装配置

    一.参考资料 VScode搭建OpenCV环境 OpenCV使用CMake和MinGW-w64的编译安装 win10下VSCode配置opencv4.4.0(超详细教程,亲测有效) VSCODE中配置 ...

  3. 修改并编译OpenCV源码提升霍夫变换线检测效果

    在做图像处理的时候,经常需要用到MATLAB验证与OpenCV实现共同进行,本文动手动机就是:OpenCV提供的Hough线检测不能满足我的要求,故需要对OpenCV源码进行修改.本人菜鸟,才学C++ ...

  4. 双目相机标定OpenCV源码讲解

    双目相机标定OpenCV源码讲解 背景介绍 所述内容 参考资料 摄像机标定部分代码 代码思路 代码中的其他函数 找角点&求内参 求外参 求矫正映射矩阵 后记 背景介绍 暑假接近两个月的时间做了 ...

  5. 老李推荐:第14章4节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-端口转发 1...

    老李推荐:第14章4节<MonkeyRunner源码剖析> HierarchyViewer实现原理-装备ViewServer-端口转发 在初始化HierarchyViewer的实例过程中, ...

  6. JS魔法堂:mmDeferred源码剖析

    一.前言 avalon.js的影响力愈发强劲,而作为子模块之一的mmDeferred必然成为异步调用模式学习之旅的又一站呢!本文将记录我对mmDeferred的认识,若有纰漏请各位指正,谢谢.项目请见 ...

  7. 一次搞定OpenCV源码及扩展模块的编译与环境配置

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自|计算机视觉工坊 版本:VS2017.CMake3.12. ...

  8. Kafka源码剖析 —— 网络I/O篇 —— 浅析KafkaSelector

    为什么80%的码农都做不了架构师?>>>    ##NioSelector和KafkaSelector有什么区别? 先说结论,KafkaSelector(org.apache.kaf ...

  9. Mongoose源码剖析:Introduction and Installation

    引言 要剖析Mongoose的源码,首先你得知道它的一些基本情况和特性.并去使用它.本文就是介绍Mongoose是个什么东西?及如何安装和使用?这里假设你知道什么web服务器软件.web服务器使用什么 ...

最新文章

  1. 桑文锋的数据“长征”
  2. 记录处理搜狗新闻分类
  3. 石头剪刀布 -2013编程之美全国测试赛 每日一练
  4. infopath视图切换
  5. 远程连接ubuntu的MongoDB遇到的坑
  6. Python查找中国城市、省份
  7. 安川机器人外部急停信号点不开_安川机器人示教器常见故障维修方法
  8. 353万播放0转化背后,虚假繁荣的微博生态
  9. 什么是tomcat?tomcat是干什么用的?下面带你们认识tomcat!通俗易懂!
  10. 破解电视盒 运营商送的,各种型号。通用 TTL 破解电视盒,更新华为悦盒
  11. 大疆精灵4RTK连接千寻位置FindM Pro、FindCM
  12. 贪玩蓝月角色服务器找不到了,贪玩蓝月合服角色处理方法曝光
  13. 浅谈JavaScript、ES5、ES6 ,,转自http://www.cnblogs.com/lovesong/p/4908871.html
  14. 【CSS3】一文搞懂盒子模型
  15. html 图片放大保证不失真,教你如何在保证图片不失真的情况下缩小图片大小
  16. 用CSS实现一个抽奖转盘(附详细代码+思路)
  17. iOS应用中crash 奔溃解析
  18. java关键字汉化_Java关键字 - 乱流的个人空间 - OSCHINA - 中文开源技术交流社区
  19. 送给广大IT男同学的金句良言
  20. 一文读懂什么是cookie和session。

热门文章

  1. 零基础到CS开发高手通用权限管理系统全程实录
  2. Excel、大量数据、违禁词数据分析思路
  3. Docker部署neo4j
  4. 计算机应用基础论坛贴子怎么发,大家请进·帮助小弟·计算机应用基础
  5. log10()的运用
  6. 羽毛球拍选购常见问题解疑
  7. 在线钢琴应用AutoPiano
  8. PHP实现调查报告的代码,2015 年 JavaScript 开发者调查报告:PHP是最好的编程语言!...
  9. dagre-d3 基于d3.js v3版本以上
  10. java实现将指定字符串替换为制定长度的空格