项目在开发过程中,需要读取JPG影像中的exif的GPS,相机参数,影像大小等参数,根据exif属性的格式定义,解析所需要信息的字段,成功提取内容。
Exif 编辑
Exif是一种图像文件格式,它的数据存储与JPEG格式是完全相同的。实际上Exif格式就是在JPEG格式头部插入了数码照片的信息,包括拍摄时的光圈、快门、白平衡、ISO、焦距、日期时间等各种和拍摄条件以及相机品牌、型号、色彩编码、拍摄时录制的声音以及GPS全球定位系统数据、缩略图等。你可以利用任何可以查看JPEG文件的看图软件浏览Exif格式的照片,但并不是所有的图形程序都能处理Exif信息。图像右击属性一栏可以查看当前的exif属性值。

Exif 属性字段
读取exif属性,首先需要了解属性字段的标识符。我们根据标识符提取对应的exif属性值。下图中的标签号位属性标识符字段,其他属性的标识符根据标准文档查找,下文不一一列出

Exif 解析代码
1.首先从读取图像中字节流中查找到对应的exif信息的起始指针。

int W_ExifInfo::DecodeExif(const char * fPath)
{m_pkFile = fopen(fPath, "rb");int a = fgetc(m_pkFile);if (a != 0xff || fgetc(m_pkFile) != M_SOI){cout<<"底图打开失败"<<endl;fclose(m_pkFile);return 0;}for(;;) //{int itemlen;int marker = 0;int ll,lh, got;unsigned char * Data;for (int i = 0; i < 7; i++){marker = fgetc(m_pkFile);if (marker != 0xff) break;if (i >= 6){cout<< "底图打开失败,太多的填充字符"<<endl;fclose(m_pkFile);return 0;}}if (marker == 0xff){cout<< "底图打开失败,太多的填充字符"<<endl;fclose(m_pkFile);return 0;}lh = fgetc(m_pkFile);ll = fgetc(m_pkFile);itemlen = (lh << 8) | ll;if (itemlen < 2){cout<< "底图打开失败,无效的图片标记"<<endl;fclose(m_pkFile);return 0;}Data = (unsigned char *)malloc(itemlen);if (Data == NULL){cout<< "底图打开失败,内存分配失败"<<endl;fclose(m_pkFile);return 0;}Data[0] = (unsigned char)lh;Data[1] = (unsigned char)ll;got = fread(Data+2, 1, itemlen-2,m_pkFile);if (got != itemlen-2){fclose(m_pkFile);return 0;}if (M_EXIF == marker){if (memcmp(Data+2, "Exif", 4) == 0){ProcessExif((unsigned char *)Data+2, itemlen);}}}fclose(m_pkFile);return 1;
}

2.判断是否符合exif标准,同时查找到字符流中关于属性信息起始地址。

int W_ExifInfo::ProcessExif(unsigned char * CharBuf, unsigned int length)
{static const unsigned char ExifHeader[] = "Exif\0\0";if (memcmp(CharBuf+0, ExifHeader,6)){cout<<"底图打开失败,错误的EXIF信息头"<<endl;return 0;}if (memcmp(CharBuf+6,"II",2) == 0){m_iMotorolaOrder = 0;}else{if (memcmp(CharBuf+6,"MM",2) == 0){m_iMotorolaOrder = 1;}else{cout<<"底图打开失败,无效的EXIF对准标记"<<endl;return 0;}}if (Get16u(CharBuf+8) != 0x2a){cout<<"底图打开失败,无效的EXIF头"<<endl;return 0;}int FirstOffset = Get32u(CharBuf+10);if (FirstOffset < 8 || FirstOffset > 16){cout<<"底图打开失败,无效的偏移"<<endl;return 0;}unsigned char * LastExifRefd = CharBuf;if (!ProcessExifDir(CharBuf+14, CharBuf+6, length-6, &LastExifRefd)){return 0;}return 1;
}

3.根据每个属性的定义格式如下:
Tag 标识符
Format: 格式,tag TYPE
count: 最多的字符个数为
offset: 偏移量,但是这里的偏移量要记得加上从(II 49 49)+1D
我们根据属性定义的格式,找到对应的数据字符流,根据格式进行数据解析,得到最后的所需要的值。

#define TAG_GPS2_OFFSET         0x8825  //GPS 偏移
#define TAG_GPS_LATITUDE        0x0002  //纬度值
#define TAG_GPS_LONGITUDE       0x0004  //经度值
#define TAG_GPS_ALTITUDE        0x0006  //高度
#define TAG_MAKE                0x010F  //相机制造商
#define TAG_EXIF_IMAGEHEIGHT    0xA003  //图像高度
#define TAG_EXIF_IMAGEWIDTH     0xA002  //图像宽度int W_ExifInfo::ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase,unsigned ExifLength, unsigned char ** const LastExifRefdP)
{int NumDirEntries = Get16u(DirStart);if ((DirStart + 2 + NumDirEntries*12) > (OffsetBase+ExifLength)){cout<<"底图打开失败,目录大小非法"<<endl;return 0;}for (int de=0;de<NumDirEntries; de++){int Tag, Format, Components;unsigned char * ValuePtr;int BytesCount;unsigned char * DirEntry;DirEntry = DirStart+2+12*de;Tag = Get16u(DirEntry);Format = Get16u(DirEntry+2);Components = Get32u(DirEntry+4);if ((Format-1) >= NUM_FORMATS){cout<<"底图打开失败,EXIF DIR格式代码非法"<<endl;return 0;}BytesCount = Components * BytesPerFormat[Format];if (BytesCount > 4){unsigned OffsetVal;OffsetVal = Get32u(DirEntry+8);if (OffsetVal+BytesCount > ExifLength){cout<<"底图打开失败,EXIF指针偏移值非法"<<endl;return 0;}ValuePtr = OffsetBase+OffsetVal;}else{ValuePtr = DirEntry+8;}if (*LastExifRefdP < ValuePtr+BytesCount){*LastExifRefdP = ValuePtr+BytesCount;}switch(Tag){case TAG_MAKE:{m_strCameraMake = ConvertToString((char*)ValuePtr, 31);break;}case TAG_EXIF_IMAGEWIDTH:{m_lImageWidth = (long)ConvertAnyFormat(ValuePtr, Format);break;}case TAG_EXIF_IMAGEHEIGHT:{m_lImageHeight = (long)ConvertAnyFormat(ValuePtr, Format);break;}case TAG_GPS_LATITUDE:{m_fDegree = (float)ConvertAnyFormat(ValuePtr, Format);m_fMinute = (float)ConvertAnyFormat(ValuePtr+8, Format);m_fSecond = (float)ConvertAnyFormat(ValuePtr+16, Format);if ((0 == m_fDegree)&& (0 == m_fMinute)&& (0 == m_fSecond)){break;}else{//qDebug()<<"m_fLatitude = ("<<m_fDegree<<", "<<m_fMinute<<", "<<m_fSecond<<")";this->m_fLatitude = m_fDegree + m_fMinute/60.0 + m_fSecond/3600.0;break;}}case TAG_GPS_LONGITUDE:{m_fDegree = (float)ConvertAnyFormat(ValuePtr, Format);m_fMinute = (float)ConvertAnyFormat(ValuePtr+8, Format);m_fSecond = (float)ConvertAnyFormat(ValuePtr+16, Format);//qDebug()<<"m_fLongitude = ("<<m_fDegree<<", "<<m_fMinute<<", "<<m_fSecond<<")";this->m_fLongitude = m_fDegree + m_fMinute/60.0 + m_fSecond/3600.0;break;}case TAG_GPS_ALTITUDE:{m_fAltitude = (float)ConvertAnyFormat(ValuePtr, Format);break;}default:break;}if (Tag == TAG_EXIF_OFFSET || Tag == TAG_INTEROP_OFFSET  || Tag == TAG_GPS2_OFFSET){unsigned char * SubdirStart;SubdirStart = OffsetBase + Get32u(ValuePtr);if (SubdirStart < OffsetBase ||SubdirStart > OffsetBase+ExifLength){cout<<"底图打开失败,非法目录链接"<<endl;return 0;}ProcessExifDir(SubdirStart, OffsetBase, ExifLength, LastExifRefdP);continue;}}return 1;
}

Exif 运行结果
下面结果仅截取影像列表中的GPS信息。
./Image/DJI_0001.JPG 120.228119 31.585005 138.457001
./Image/DJI_0002.JPG 120.228119 31.585005 138.356995
./Image/DJI_0003.JPG 120.228119 31.585005 138.257004
./Image/DJI_0004.JPG 120.228119 31.585005 138.457001

参考文档
http://blog.csdn.net/fioletfly/article/details/53605959
https://baike.baidu.com/item/Exif/422825?fr=aladdin

jpg读取exif属性值相关推荐

  1. zigbee zcl规范及其协议栈实现3 读取服务器端属性值

    zigbee zcl规范及其协议栈实现2   中有介绍 对通用命令的处理,按照那个思路和信息流程添加读取服务器端属性值的功能 客户端samplesw想要知道与自己的12号端点SAMPLESW_ENDP ...

  2. C++ AO读取shapefile的属性值

    C++ AO读取一个shapefile文件的所有属性值     #include "stdafx.h"     #include "iostream.h"   ...

  3. @value 注入静态属性_SpringBoot使用@Value读取属性值

    今天给大家讲一下如何在SpringBoot项目中使用@Value注解读取配置文件的属性值. 1给普通变量赋值1. 给普通变量赋值时,直接在变量声明之上添加@Value()注解即可 @Component ...

  4. 深入理解java注解,java的4个元注解,注解三要素——定义、使用及读取执行,深入了解注解的底层本质,通过反射自动、动态获取注解所有属性以及属性值

    1. 注解的定义 注解也是一种引用类型,编译后会生成 .class 字节码文件,作用就是为程序进行标识,不同注解能实现不同功能. 2. 注解的使用 3. 注解的读取执行 3.1 得不到注解信息,得到的 ...

  5. SpringBoot中通过ConfigurationProperties注解的方式读取application.yml中配置的属性值

    场景 在SpringBoot后台项目中,某些固定的属性需要配置在配置文件application.yml中. 比如上传到服务器的文件路径. 然后在其他业务代码中比如上传文件接口中需要或者到配置的这个属性 ...

  6. gdal mysql乱码_GDAL读取S-57海图数据中文属性值乱码问题解决(续)

    上篇博文中的代码使用了函数wcstombs来进行处理,今天发现GDAL库里面提供了宽字节转单字节的函数,名字叫CPLRecodeFromWChar(这个函数需要libiconv库的支持,也就是在编译G ...

  7. GDAL C#读取shp中文属性值乱码问题

    GDAL的C#版本读取shp中,如果属性值中含有中文,读出来有可能是乱码的问题,根据SWIG生成的C#代码调试发现问题所在,在Ogr.cs文件中有这么一个函数,代码如下: internal stati ...

  8. GDAL读取S-57海图数据中文属性值乱码问题解决(续)

    上篇博文中的代码使用了函数wcstombs来进行处理,今天发现GDAL库里面提供了宽字节转单字节的函数,名字叫CPLRecodeFromWChar(这个函数需要libiconv库的支持,也就是在编译G ...

  9. GDAL读取S-57海图数据中文属性值乱码问题解决

    使用GDAL读取S-57海图数据时,对于属性表中的中文属性值读出来是乱码.如图1所示. 图1 S57海图数据中文乱码字段 通过调试代码发现,S-57文件中的中文是按照宽字节存储在文件中,而GDAL在读 ...

最新文章

  1. filter 在CSS用的效果
  2. Yahoo为啥赚不到钱
  3. 【GVA】gorm多对多many2many删除数据的同时级联删除关联中间表中的关联数据
  4. 什么是JAVA内容仓库(Java Content Repository)
  5. 洛谷 P3853 [TJOI2007]路标设置
  6. tfw文件如何导入cad_教你三维家3d设计软件如何导入cad文件
  7. C++——OOP(Object-Oriented Programming) vs. GP(Generic Programming)
  8. 用freebsd搭建日志服务器
  9. hdu 4027 Can you answer these queries?
  10. Java小游戏集合 开源分享
  11. 图解设计模式-Flyweight模式
  12. ESP8266 ESP8089 ESP8285 用户手册文档汇总
  13. python ocr文字识别竖排繁体_(以繁体竖排为例)OCR各种软件使用效果对照..docx...
  14. MATLAB程序设计与应用刘卫国(第三版)课后实验答案——12
  15. <figure> <figcaption>筆記
  16. 读《自控力:斯坦福大学最受欢迎心理学课程》体会
  17. 全志T3开发板——嵌入式入门学习测试教程(4)
  18. java版VR全景漫游制作平台 - 1介绍
  19. C语言:最大公约数。
  20. 时间戳转化为时间小样例

热门文章

  1. pyhton (for in if)用法
  2. 使用Redis实现用户积分及TopN排行榜功能
  3. 计算机用户登录设置成2000,Win2000设置技巧
  4. Linux命令详解(14)useradd命令
  5. 如果高考考Python,这些题目你会做吗?
  6. 如何成为一个优雅的硬件工程师?
  7. 操作系统学习福利:600 条最强 Linux 命令总结
  8. 国鼎代理极海APM32F030x8系列MCU手持式激光测距仪应用方案
  9. Zipkin服务端搭建使用教程
  10. 水果编曲FL Studio 21强化来袭!FL Studio21制作人版下载及切换中文教程