作者:中科大鹏

1 前言

因为最近有个项目开发,需要通过读取JPEG图片文件的GPS位置信息,显示到相应的地图界面中。这两天,查询了相关资料,发现这方面科普性的文章挺多,但适合项目应用开发的示例代码有限。为此,自己查阅资料,梳理了解析GPS信息的相关过程,编写了部分代码,在此分享给大家。不足之处望多多包涵。

文章只列举了很少部分的JPEG格式说明,关于JPEG文件格式详细介绍,可以参阅相关文献(文末会分享查阅的接口标准和参考文献),另外,本文只针对需要的GPS信息进行了解析,其余的很多信息直接略过,大家可以参看接口标准说明文件。

2 JPEG图像数据的基本结构

2.1原始图像数据的基本结构

图像数据采用的存储格式,取决于需要的图像数据类型:

  • 未压缩RGB数据:   采用TIFF Rev. 6.0标准修订版的RGB全彩图像类型格式
  • 未压缩YCbCr数据: TIFF 6.0 修订版的扩展YCbCr 图像类型数据格式
  • JPEG压缩数据:     JPEG标准的ADCT。

就压缩数据而言,DSC应用程序所需要的属性信息记录在文件APP1信息中,写入APP1中的数据需要与TIFF兼容。图像文件使用了一种存储压缩和未压缩数据属性信息的通用方法,这样可以得到更简单的格式;同时又利用标记的可扩展性机制,实现了为附加信息添加私有标记的可能。

2.2未压缩RGB数据的基本结构

未压缩RGB数据的按照TIFF Rev. 6.0 RGB 标准的全色彩图像要求进行记录。

文件的通用属性信息记录在TIFF Rev. 6.0指定的Tag标签中。Exif特定的属性信息使用该标准为TIFF保留的私有Tag标签来记录。私有Tag标签指向该属性的信息集合(Exif IFD)。同样,JPEG文件的GPS信息也属于扩展属性信息,记录在TIFF Rev. 6.0中规定的标记中。

需要注意的是,在TIFF标准中,并未规定每个IFD(Image File Directory)值的记录位置。

JPEG文件结构主要包括:

  • File Header,                             文件头
  • 0th IFD,                                    第0个IFD
  • 0th IFD Value,                          第0个IFD的值
  • 1st IFD,                                     第1个IFD
  • 1st IFD Value,                            第一个IFD的值
  • 1st (Thumbnail) Image Data,     缩略图
  • 0th (Primary) Image Data.          图片数据

基本文件结构如图所示:

图 1 未压缩数据文件的基本结构

TIFF标准中定义的文件头占8字节,其中,到IFD的偏移量字段表示从TIFF头开始至第0个IFD的起始地址的偏移位置。

同样,第0个IFD存储的IFD偏移量,表示第1个IFD(缩略图)的存储地址。当没有记录缩略图,即没有第1个IFD时,第0个IFD存储的偏移量以00000000.H结束。

TIFF头部结构如表所示:

表 1 TIFF头部结构

名称

长度(字节)

描述

Byte Order

2

“II”(4949.H)或 “MM”(4D4D.H)

•     II 表示数字存储遵循 intel 的字节序,即小端存储

•     MM 表示数据存储遵循 Motorola 的字节序,即大端存储

不同的存储字节序的选择主要是因为不同厂商的不同的数码产品的差异引起。大部分的数码相机使用 Intel 的字节序,也有些奇葩的产品的,比如Sony 的大部分产品都是使用Intel字节序的。

重要:Byte Order直接影响到数据内容,所以在解析Exiff数据前必须检查文件的Byte align

固定值

2

固定为002A.H

到IFD的偏移量

4

第0个 IFD偏移量。如果TIFF头后面紧跟着第0个IFD,则它被写为00000008.H。(从Byte Order起算。后续的数据偏移量也从Byte Order起算)

2.3 JPEG压缩数据的基本结构

压缩数据文件按照ISO/IEC 10918-1中规定的JPEG DCT格式进行记录,并插入应用片段(APP1)。APP1紧随着ISO标记而记录,ISO标记指示了文件的开头(见图)。同样,可以根据需要记录多个APP2,并且可以紧随APP1之后。Exif不使用APP1和APP2之外的APPn或COM段。因此,Exif读取程序设计需要跳过未知的APPn和COM数据段。

图 2 压缩数据文件的基本结构

互操作性:APP1由APP1标记、Exif标识符代码和属性信息本身组成。APP1的大小包含所有这些元素,其长度不得超过JPEG标准规定的64KB。

属性信息存储于TIFF结构中,包括1个文件头,最多2个IFD(0th IFD和1th IFD)。0th IFD记录与压缩图像(主图像)有关的属性信息。1th IFD可用于记录缩略图等。

APP2由APP2标记、FPXR标识符代码和扩展记录或流数据的内容列表组成。多个APP2标记段的字符串可用于记录超过64KB的数据。

2.4 缩略图数据的基本结构

缩略图数据使用两种现有的图像格式记录在第一个IFD中,与主图像类似。

缩略图的大小没有限制,也不是强制性的。

缩略图不用采用与主图像相同的数据结构。然而,当主图像记录为未被压缩的RGB数据或未压缩的YCbCr数据时,缩略图不能记录为JPEG压缩数据(见表)。

表 2 主图像和压缩图像适应性

以压缩格式记录缩略图时,按照标准TIFF Rev. 6.0 RGB Full Color Images 或者TIFF Rev. 6.0 Extensions YCbCr Images要求将其记录在第一个IFD中。

Exif特定的记录格式用于压缩的缩略图。此时,压缩标签值被设置为“6”,第一个IFD的标签用于指定位置和大小。图像在指定位置存储为符合JPGE标准DCT格式的数据流(从SOI到EOI)。JPEG数据流中不记录APPn标记、COM标记或重置标记,见图。为了避免重复定义,第一个IFD不用于记录指示TIFF图像的标记或记录在别处作为JPEG标记段的信息。

图 3 具有压缩缩略图的Exif文件结构

3 Tag标签

3.1 属性信息的特征

RGB数据符合标准Rev. 6.0 RGB Full Color Images, YCbCr符合TIFF Rev. 6.0 扩展YCbCr Images标准。因此,TIFF后面紧跟的数据库应按照TIFF标准记录信息。除了TIFF标准中规定的强制性属性外,Exif标准还添加了用于DSC或其他系统的TIFF可选标签,包括用于记录DSC特定属性信息的Exif特定标签,以及用于记录位置信息的GPS标签。同样,还有一些原始规格用于记录Exif压缩缩略图的信息并不包括在标准中。

文件记录的压缩数据与非压缩数据的区别在于:

  • 以压缩数据记录主图像时,没有指定主图像或其地址指针的Tag标签。
  • 缩略图数据以压缩形式记录时,使用Exif特定的Tag标签指定其地址和大小。
  • 无论主图像还是缩略图,与JPEG标准定义的Tag标签信息不会重复记录。
  • 与压缩相关的信息能够用Tag标签记录。

3.2 IFD结构

Exif标准中使用的IFD共计12字节,结构如下:

字节0-1:  标签Tag字段

字节2-3:  类型Type字段

字节4-7:  数量Count字段

字节8-11: 下一个IFD偏移字段。

(1)Tag标签

每个Tag标签分配唯一2字节数字来标识该字段,Exif 0th IFD和1st IFD的标签号都与TIFF标签号相同。

(2)类型Type

Exif使用了以下类型:

表 3 IFD数据类型

类型编码

大小(字节)

描述

1

1

BYTE

8bit unsigned integer

2

1*n

ASCII

ASCII信息码。最后一个字节必须以空字符结尾。

3

2

SHORT

(2-byte) unsigned integer

4

4

LONG

(4-byte) unsigned long

5

8

LONG * 2

第一个数字是分子,第二个数字是分母

7

n

UNDEFINED

可以包含任意数值

9

4

SLONG

32-bit (4-byte) signed integer

10

8

SLONG * 2

第一个数字是分子,第二个数字是分母

(3)数量Count字段

数值的数量。需要注意的是,数量不是字节的总和,而是根据类型Type值确定的个数。比如,在类型Type=03的情况下,Count=1,此时数据是2个字节。

(4)偏移量

这个字段记录从TIFF标头开始到记录值本身位置的偏移量。如果数值是4字节,则记录的是数值本身;如果数值长度小于4字节,则该值存储在从左侧开始的4字节中,即偏移区域的低位开始。例如,在大端存储big endian(TIFF Headers Byte Order为 4D4D.H)时,如果short类型值为1,则存储为00010000.H。

注意:Interoperability字段的Tag标签应按从最小号开始按顺序记录。标签值(value)记录的顺序和位置没有规定。

3.3 Exif特定的IFD

表 4 Exif特定的IFD

IFD

Tag标签

Type

Count

Default

Exif IFD

34665 (8769.H)

LONG

1

--

GPS IFD

34853 (8825.H)

LONG

1

--

Interoperability IFD

40965 (A005.H)

LONG

1

--

3.4 GPS信息

表 5 GPS信息

IFD

Tag标签

Type

Count

默认值及含义

GPSVersionID

0 (0.H)

BYTE

4

2.2.0.0

2.2.0.0 = Version 2.2

Other = reserved

GPSLatitudeRef

1 (1.H)

ASCII

2

--

'N' = North latitude

'S' = South latitude

GPSLatitude

2 (2.H)

RATIONAL

3

---

GPSLongitudeRef

3 (3.H)

ASCII

2

--

'E' = East longitude

'W' = West longitude

GPSLongitude

4(4.H)

RATIONAL

3

---

GPSAltitudeRef

5 (5.H)

BYTE

1

0

0 = Sea level

1 = (negative value)

GPSAltitude

6 (6.H)

RATIONAL

1

---

GPSTimeStamp

7 (7.H)

RATIONAL

3

---

GPSSatellites

8 (8.H)

ASCII

可变

---

GPSStatus

9 (9.H)

ASCII

2

---

'A' = in progress

'V' = Interoperability

GPSMeasureMode

10 (A.H)

ASCII

2

---

'2' = 2-D

'3' = 3-D

GPSDOP

11 (B.H)

RATIONAL

1

---

GPSSpeedRef

12 (C.H)

ASCII

2

'K'

'K' = Kilometers per hour

'M' = Miles per hour

'N' = Knots

GPSSpeed

13 (D.H)

RATIONAL

1

---

……

……

……

……

……

4 数据解析示例

4.1 示例JPEG文件头部信息数据

00000000h: FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 ; ??.JFIF.....x

00000010h: 00 78 00 00 FF E1 00 C4 45 78 69 66 00 00 49 49 ; .x..?腅xif..II

00000020h: 2A 00 08 00 00 00 02 00 31 01 02 00 10 00 00 00 ; *.......1.......

00000030h: 26 00 00 00 25 88 04 00 01 00 00 00 36 00 00 00 ; &...%?.....6...

00000040h: 00 00 00 00 4C 6F 63 61 53 70 61 63 65 56 69 65 ; ....LocaSpaceVie

00000050h: 77 65 72 00 06 00 01 00 02 00 02 00 00 00 4E 00 ; wer...........N.

00000060h: 00 00 02 00 05 00 03 00 00 00 84 00 00 00 03 00 ; ..........?....

00000070h: 02 00 02 00 00 00 45 00 00 00 04 00 05 00 03 00 ; ......E.........

00000080h: 00 00 9C 00 00 00 05 00 01 00 01 00 00 00 00 00 ; ..?............

00000090h: 00 00 06 00 05 00 01 00 00 00 B4 00 00 00 00 00 ; ..........?....

000000a0h: 00 00 1E 00 00 00 01 00 00 00 37 00 00 00 01 00 ; ..........7.....

000000b0h: 00 00 87 0F 68 0F 80 96 98 00 67 00 00 00 01 00 ; ..?h.€枠.g.....

000000c0h: 00 00 1D 00 00 00 01 00 00 00 10 A1 D8 0C 80 96 ; ...........∝.€?

000000d0h: 98 00 20 4E 00 00 10 27 00 00                   ; ? N...'..

4.2 JPEG数据层级解析

首先把原始数据按照几个大块进行分割。

APP数据都是按照标志(2字节)、长度(4字节)、数据正文(长度根据数据定)的结构。如表所示。

表 6 JPEG数据层级解析

信息

类型

存储

位置

Hex

描述

文件

标志

00.H.

FF D8

Start Of Image (SOI),JPEG文件开始标识符

APP0

02.H

FF E0

Application-sepcific  0 (APP0)标志

注:FF En, n是从0~F的数字,如FF E0,表示APPn标志

04.H

00 10

APP0数据帧长度,不包括标识符FF E0, 包括长度符 00 10自身

06.H

4A 46 49 46 00 01 01 01 00 78 00 78 00 00

APP0数据帧信息内容

APP1

14.H

FF E1

Application-sepcific  1 (APP1)标志

16.H

00 C4

APP1数据帧长度

18.H

45 78 69 66 00 00

……………………………

20 4E 00 00 10 27 00 00

APP1数据帧信息内容

解析见下。

4.3 JFIF APP0解析

JFIF APP0数据因为没有下一层级,可以直接解析,数据包内容、包括的字段及解析过程见表。

00000002h: FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 ; ?.JFIF.....x.x

00000012h: 00 00                                         ; ..

表 7 JFIF APP0解析

字段

大小

(字节)

Hex

描述

字段标志

2

FF E0

APP0标志

字段容量

2

00 10

APP0数据帧长度,不包括标识符FF E0, 包括长度符 00 10自身

标识符

5

4A 46 49 46 00

JFIF的ASCII码,以空字符结尾

JFIF版本

2

01 01

第一个字节主要版本,第二个字节,次要版本,此处为1.1版本

密度单位

1

01

密度单位

0=无单位;1=点数/英寸;2=点数/厘米

水平分辨率

2

00 78

水平像素密度,非零数字。此处120 dpi

垂直分辨率

2

00 78

垂直像素密度,非零数字。此处120 dpi

缩略图X像素数目

1

00

缩略图水平像素数目,可以为零。

缩略图Y像素数目

1

00

缩略图垂直像素数目,可以为零。

缩略图像素数量

(由上面两个字段确定)

RGB缩略图3×n

n=缩略图像素总数=缩略图X像素×缩略图Y像素

此处无

4.4 JFIF APP1解析

JFIF APP1数据循环套层,需要分层解析。

(1)APP1原始数据

00000014h: FF E1 00 C4 45 78 69 66 00 00 49 49 2A 00 08 00 ; ?腅xif..II*...

00000024h: 00 00 02 00 31 01 02 00 10 00 00 00 26 00 00 00 ; ....1.......&...

00000034h: 25 88 04 00 01 00 00 00 36 00 00 00 00 00 00 00 ; %?.....6.......

00000044h: 4C 6F 63 61 53 70 61 63 65 56 69 65 77 65 72 00 ; LocaSpaceViewer.

00000054h: 06 00 01 00 02 00 02 00 00 00 4E 00 00 00 02 00 ; ..........N.....

00000064h: 05 00 03 00 00 00 84 00 00 00 03 00 02 00 02 00 ; ......?........

00000074h: 00 00 45 00 00 00 04 00 05 00 03 00 00 00 9C 00 ; ..E...........?

00000084h: 00 00 05 00 01 00 01 00 00 00 00 00 00 00 06 00 ; ................

00000094h: 05 00 01 00 00 00 B4 00 00 00 00 00 00 00 1E 00 ; ......?........

000000a4h: 00 00 01 00 00 00 37 00 00 00 01 00 00 00 87 0F ; ......7.......?

000000b4h: 68 0F 80 96 98 00 67 00 00 00 01 00 00 00 1D 00 ; h.€枠.g.........

000000c4h: 00 00 01 00 00 00 10 A1 D8 0C 80 96 98 00 20 4E ; .......∝.€枠. N

000000d4h: 00 00 10 27 00 00                               ; ...'..

(2)APP1数据结构

APP1的数据结构及组成见表。他包括基本字段(标志、容量)、TIFF头、IFD数据包入口及数据信息存储段。其中,IFD数据包又包括标志、类型、数量、数据或是数据偏移量(小于4字节的直接存储数据,大于4字节的存储数据偏移量,具体数据到数据信息段读取)。

表 8 JFIF APP1解析

字段

大小

(字节)

Hex

描述

字段标志

2

FF E1

APP1标志

字段容量

2

00 C4

APP1数据帧长度,不包括标识符FF E1, 包括长度符 00 C4自身

Exif标识符

6

45 78 69 66 00 00

Exif标识符的ASCII码,以空字符结尾

TIFF头

编码顺序

2

49 49

根据CPU的不同,可以是“II”(4949.H)或“MM”(4D4D.H)

•     II 表示数字存储遵循 intel 的字节序,即小端存储

•     MM 表示数据存储遵循 Motorola 的字节序,即大端存储

不同的存储字节序的选择主要是因为不同厂商的不同的数码产品的差异引起。大部分的数码相机使用 Intel 的字节序,也有些奇葩的产品的,比如Sony 的大部分产品都是使用Intel字节序的,唯独D700

重要:字节序直接影响到数据内容,所以在解析Exif数据前必须检查文件的Byte align

标签标示

2

2A 00

固定值。

•     如果使用 Intel 字节序,则对应的存储值为 0x2A00

•     如果使用Motorola 的字节序,则存储值为 0x002A

IFD偏移量

4

08 00 00 00

最后四个字节表示到 IFD0(Image File Directory)的偏移。

偏移量从TIFF头起算(后面的数据也是从这里起算,所以头的位置必须记下来,本文件头位置0X1E)

本处偏移8,也就是紧接着就是IFD数据

IFD数量

2

02 00

包含的IFD(Image File Directory)数量。

IFD0

Tag

2

31 01

Tag标志

此处表示 Software used

Type

2

02 00

IFD数据类型

此处表示 ASCII

数量

4

10 00 00 00

IFD数据类型的个数(注意,不是字节数,所占字节数是每个类型对应字节数 * 个数)

此处表示16个字符(含结尾空字符)

数据偏移量

4

26 00 00 00

对应数据的存储位置偏移量,从TIFF头起算。

此处表示数据位置在0X1E + 0X26 = 0X44

据此查找下一字段

数据

n

4C 6F 63 61 53 70 61 63 65 56 69 65 77 65 72 00

对应值

LocaSpaceViewer

IFD1

Tag

2

25 88

此处表示 GPS信息,这是一个扩展信息,需要再次分块解析

Type

2

04 00

此处表示 Long,该字段的固定设置,没有实际意义

数量

4

01 00 00 00

此处表示1个Long数据,该字段的固定设置,没有实际意义

数据偏移量

4

36 00 00 00

此处表示数据位置在0X1E + 0X36 = 0X54

据此查找下一字段

数据

n

详细解析见下面

(3)APP1存储的GPS数据解析

GPS数据存储于后续的数据段中,分别有不同的子帧组成(0X00至0X12),根据IFD入口找到存储起始位置,从起始位置读取子帧个数,再按子帧循环解析。

每个子帧组成格式为TagID(2字节)、类型(2字节)、数量(4字节)、数据或数据偏移量(小于4字节的直接存储数据,大于4字节的存储数据偏移量,具体数据到数据信息段读取),格式于IFD格式一致。

a) GPS信息原始数据

00000054h: 06 00 01 00 02 00 02 00 00 00 4E 00 00 00 02 00 ; ..........N.....

00000064h: 05 00 03 00 00 00 84 00 00 00 03 00 02 00 02 00 ; ......?........

00000074h: 00 00 45 00 00 00 04 00 05 00 03 00 00 00 9C 00 ; ..E...........?

00000084h: 00 00 05 00 01 00 01 00 00 00 00 00 00 00 06 00 ; ................

00000094h: 05 00 01 00 00 00 B4 00 00 00 00 00 00 00 1E 00 ; ......?........

000000a4h: 00 00 01 00 00 00 37 00 00 00 01 00 00 00 87 0F ; ......7.......?

000000b4h: 68 0F 80 96 98 00 67 00 00 00 01 00 00 00 1D 00 ; h.€枠.g.........

000000c4h: 00 00 01 00 00 00 10 A1 D8 0C 80 96 98 00 20 4E ; .......∝.€枠. N

000000d4h: 00 00 10 27 00 00                               ; ...'..

b) GPS信息解析结构

表 9 GPS数据解析

字段

大小

(字节)

Hex

描述

数据子帧个数

2

06 00

数据子帧个数,本文6个

子帧1

Tag ID

2

01 00

表示GPSLatitudeRef

Type

2

02 00

表示ASCII码

容量

4

02 00 00 00

表示两个字符(含结尾空字符)

数值

4

4E 00 00 00

表示“N”,北纬

子帧2

Tag ID

2

02 00

表示GPSLatitude

Type

2

05 00

表示两个long构成的分数

容量

4

03 00 00 00

表示3个分数,分别对应度分秒

偏移量

4

84 00 00 00

表示数据存储位置,0X1E + 0X84 = 0XA2

数据

3 * 2 * 4

1E 00 00 00 01 00 00 00

37 00 00 00 01 00 00 00

87 0F 68 0F 80 96 98 00

度 : 30/1 = 30

分:55/1 = 55

秒:258477959/10000000 = 25.8477959

子帧3

Tag ID

2

03 00

表示GPSLongitudeRef

Type

2

02 00

表示ASCII码

容量

4

02 00 00 00

表示两个字符(含结尾空字符)

数值

4

45 00 00 00

表示“E”,东经

子帧4

Tag ID

2

04 00

表示GPSLongitude

Type

2

05 00

表示两个long构成的分数

容量

4

03 00 00 00

表示3个分数,分别对应度分秒

偏移量

4

9C 00 00 00

表示数据存储位置,0X1E + 0X84 = 0XBA

数据

3 * 2 * 4

67 00 00 00 01 00 00 00 1D 00 00 00 01 00 00 00 10 A1 D8 0C 80 96 98 00

度 : 103/1 = 103

分:29/1 = 29

秒:215523600/10000000 = 21.5523600

子帧5

Tag ID

2

05 00

表示GPSAltitudeRef

Type

2

01 00

表示BYTE

容量

4

01 00 00 00

表示1个字符

数值

4

00 00 00 00

表示0

子帧6

Tag ID

2

06 00

表示GPSAltitude

Type

2

05 00

表示两个long构成的分数

容量

4

03 00 00 00

表示1个分数

偏移量

4

B4 00 00 00

表示数据存储位置,0X1E + 0XB4 = 0XD2

数据

1 * 2 * 4

20 4E 00 00 10 27 00 00

20000/10000 = 2.0 m

5. 部分示例代码

// JPEGInfo.cpp : 定义控制台应用程序的入口点。

//Get GPS Info From JPEG File

//pladaybreak@163.com

//2022.3.30

#include "stdafx.h"

#include <Windows.h>

#include <iostream>

#include <string>

using namespace std;

//const values

const unsigned short u2SOI = 0XFFD8; //SOI    ff d8    文件开始

const unsigned short u2APP0 = 0XFFE0;   //APP0   ff e0    定义交换格式和图像识别信息

const unsigned short u2DQT = 0XFFDB; //DQT    ff db    定义量化表

const unsigned short u2SOF0 = 0XFFC0;   //SOF0   ff c0    帧开始

const unsigned short u2DHT = 0XFFC4; //DHT    ff c4    霍夫曼(Huffman)表

const unsigned short u2SOS = 0XFFDA; //SOS    ff da    扫描行开始

const unsigned short u2EOI = 0XFFD9; //EOI    ff d9    文件结束

const unsigned short u2TAG_IFD_GPS = 0X8825; //GPS IFD Tag

//GPS Mark标志

unsigned long    GPSMarkVer        = 0X00000001; //GPSVersion

unsigned long    GPSMarkLat        = 0X00000002; //GPSLatitude

unsigned long    GPSMarkLon        = 0X00000004; //GPSLongitude

unsigned long    GPSMarkAlt        = 0X00000008; //GPSAltitude

unsigned long    GPSMarkTime       = 0X00000010; //GPSTimeStamp        GPS time (atomic clock)

unsigned long    GPSMarkSats       = 0X00000020; //GPSSatellites       GPS satellites used for measurement

unsigned long    GPSMarkStat       = 0X00000040; //GPSStatus           GPS receiver status

unsigned long    GPSMarkMode       = 0X00000080; //GPSMeasureMode  GPS measurement mode

unsigned long    GPSMarkDOP        = 0X00000100; //GPSDOP          Measurement precision

unsigned long    GPSMarkSpeed  = 0X00000200; //GPSSpeed            Speed of GPS receiver

unsigned long    GPSMarkTrack  = 0X00000400; //GPSTrack            Direction of movement

unsigned long    GPSMarkMap        = 0X00000800; //GPSMapDatum     Geodetic survey data used

const unsigned char TIFF_Header_Length = 6;

const unsigned int  GPS_TAG = 0X8825;

const unsigned long u4ExifFla = 0X66697845;  //Exif标识符

//global variable

string  failInfo;                      //返回的错误信息

DWORD   dwFleLen = 0;              //图片文件的容量(字节)

unsigned char    *pu1FleDate       = NULL;  //读取的图片文件信息字符串

unsigned char TIFF_Header_Pos  = 0X1E; //Tiff头的位置

unsigned short   u2TifByteOrder    = 0X4949; //Byte Order

//Written as either "II" (4949.H) (little endian) or "MM" (4D4D.H)

//(big endian) depending on the CPU of the machine doing the

//recording.

struct MsgE0_APP0

{

unsigned char u1APP0Inden[5];   //识别APP0标记

unsigned char u1MajorVersion;   //Major version   主要版本号

unsigned char u1MinorVersion;   //Minor version   次要版本号

unsigned char u1UnitsXY;        //<Units for the X and Y densities>  X和Y的密度单位

//units = 0:无单位

//units = 1:点数 / 英寸

//units = 2:点数 / 厘米

unsigned short    u2Xdensity;       //水平方向像素密度;

unsigned short    u2Ydensity;       //垂直方向像素密度;

unsigned char u1Xthumbnail; //缩略图水平像素数目

unsigned char u1Ythumbnail; //缩略图垂直像素数目

unsigned char *pu1ThumbnailRGBBitmapl;        //缩略RGB位图(n为缩略图的像素数)

};

struct MsgIFDGps

{

unsigned long u2GPSMsg_Mark;    //标志GPS数据信息有无的标志,从右至左 0~16位分别表示信息0~18(0X00~0X12)

unsigned long u4GPSVerIF;       //4BYTE GPSVersionIF

int               i4GPSLatRef;  //GPS Latitude Ref  1-北纬,-1-南纬

int               i4GPSLatDeg;  //GPS Latitude 的度

int               i4GPSLatMin;  //GPS Latitude 的分

int               i4GPSLatSec;  //GPS Latitude 的 10^-7秒

double            f8GPSLatDDD;  //以度为单位的纬度值

int               i4GPSLonRef;  //GPSLongitudeRef   1-东经,-1-西经

int               i4GPSLonDeg;  //GPS Longitud 的度

int               i4GPSLonMin;  //GPS Longitud 的分

int               i4GPSLonSec;  //GPS Longitud 的 10^-7秒

double            f8GPSLonDDD;  //以度为单位的经度值

double            f8GPSAlt;    //GPS高度  m

int               i4GPSTimeHour;    //GPSTimeStamp   时

int               i4GPSTimeMin; //GPSTimeStamp 的分

int               i4GPSTimeSec; //GPSTimeStamp 的秒

int               i4Nouse;     //保留

};

struct MsgJPEGInfo

{

MsgE0_APP0  msgEO_APP0;

MsgIFDGps msgIFDGps;

};

struct MsgIFD

{

USHORT u2IFDTag;           //IFD的数据标志

USHORT u2IFDType;          //IFD的数据类型

ULONG  u4IFDCounts;            //IFD的数据数量

ULONG    u4IFDOffsetOrData; //IFD的数据或者数据存储偏移量

};

struct MsgJPEGInfo    msgJPEGInfo;  //存储的JPEG文件的信息

struct MsgIFD        msgIFD;      //存储的IFD入口信息

//交换数据高低位

unsigned short u2ChageHL(unsigned short u2DataOrgn)

{

if (u2TifByteOrder == 0X4949)

{

union

{

unsigned short u2tmp;

unsigned char u1tmp[2];

};

u2tmp = u2DataOrgn;

unsigned char u1Data;

u1Data       = u1tmp[0];

u1tmp[0] = u1tmp[1];

u1tmp[1] = u1Data;

return u2tmp;

}

else

return u2DataOrgn;

}

//交换数据高低位

unsigned long u4ChageHL(unsigned long u4DataOrgn)

{

if (u2TifByteOrder == 0X4949)

{

union

{

unsigned long u4tmp;

unsigned char u1tmp[4];

};

u4tmp = u4DataOrgn;

unsigned char u1Data;

//首尾交换 0<-->3

u1Data       = u1tmp[0];

u1tmp[0] = u1tmp[3];

u1tmp[3] = u1Data;

//中间交换 1<-->2

u1Data       = u1tmp[1];

u1tmp[1] = u1tmp[2];

u1tmp[2] = u1Data;

return u4tmp;

}

else

return u4DataOrgn;

}

// 获取IFD数据类型对应的数据长度

int getIFDTypeLength(USHORT u2Type)

{

switch (u2Type)

{

case 1:

case 2:

return 1;

case 3:

return 2;

case 4:

return 4;

case 5:

return 8;

case 7:

return 1;

case 9:

return 4;

case 10:

return 8;

default:

return 1;

}

return 1;

}

//处理IFD子帧数据信息,从此开始

//处理软件版本号

void processIFD_Software(ULONG u4DataLength)

{

printf("\n*************处理 软件版本号 数据:\n");

char *pSoftware = NULL;

pSoftware = new char[u4DataLength];

memset(pSoftware, 0, u4DataLength);

memcpy(pSoftware, pu1FleDate + TIFF_Header_Pos + msgIFD.u4IFDOffsetOrData, u4DataLength);

printf("SoftWare: \t\t%s\n", pSoftware);

delete[]pSoftware;

}

//处理GPS信息

void processIFD_GPS(ULONG u4DataLength)

{

printf("\n*************处理 GPS 数据:\n");

//定位数据信息位置

DWORD dwPos = TIFF_Header_Pos + msgIFD.u4IFDOffsetOrData;

DWORD dwDataPos = TIFF_Header_Pos ;

int n = 2; //读取的变量长度

//读取子帧数目

USHORT u2SubFramNum = 0;

memcpy(&u2SubFramNum, pu1FleDate + dwPos, n); dwPos += n;

//循环读取 IFD 入口数据

USHORT u2IFDTag;           //IFD的数据标志

USHORT u2IFDType;          //IFD的数据类型

ULONG  u4IFDCounts;            //IFD的数据数量

ULONG    u4IFDOffsetOrData; //IFD的数据或者数据存储偏移量

int      i4GPSAltitudeRef = 1;      //GPS高度参考值(水面以上为正,以下为负)

ULONG    u4TmpData1 = 0, u4TmpData2 = 0;

ULONG    u4D = 0, u4M = 0, u4S=0;

double   f8S = 0.0;

for (int i = 0; i < u2SubFramNum; i++)

{

n = 2;

memcpy(&u2IFDTag, pu1FleDate + dwPos, n);     dwPos += n;

memcpy(&u2IFDType, pu1FleDate + dwPos, n);         dwPos += n;

n = 4;

memcpy(&u4IFDCounts, pu1FleDate + dwPos, n);       dwPos += n;

memcpy(&u4IFDOffsetOrData, pu1FleDate + dwPos, n);     dwPos += n;

//处理IFD 子帧字段信息

switch ( u2IFDTag )

{

case 0X0000:  //GPSVersion

msgJPEGInfo.msgIFDGps.u4GPSVerIF = u4IFDOffsetOrData;

msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark = msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark | GPSMarkVer;

printf("GPS版本:\t\t%d\n", u4IFDOffsetOrData);

break;

case 0X0001:  //GPSLatitudeRef

if (u4IFDOffsetOrData == 0X0000004E) //字母'N'

{

msgJPEGInfo.msgIFDGps.i4GPSLatRef = 1;

printf("纬度参考:\t\t北半球\n");

}

else

{

msgJPEGInfo.msgIFDGps.i4GPSLatRef = -1;

printf("纬度参考:\t\t南半球\n");

}

break;

case 0X0002:  //GPSLatitude

//找到数据存储位置

dwDataPos = TIFF_Header_Pos + u4IFDOffsetOrData;

n = 4;

memcpy(&u4TmpData1, pu1FleDate + dwDataPos, n);        dwDataPos += n;

memcpy(&u4TmpData2, pu1FleDate + dwDataPos, n);        dwDataPos += n;

u4D = u4TmpData1 / u4TmpData2;

msgJPEGInfo.msgIFDGps.i4GPSLatDeg = u4D;

memcpy(&u4TmpData1, pu1FleDate + dwDataPos, n);        dwDataPos += n;

memcpy(&u4TmpData2, pu1FleDate + dwDataPos, n);        dwDataPos += n;

u4M = u4TmpData1 / u4TmpData2;

msgJPEGInfo.msgIFDGps.i4GPSLatMin = u4M;

memcpy(&u4TmpData1, pu1FleDate + dwDataPos, n);        dwDataPos += n;

memcpy(&u4TmpData2, pu1FleDate + dwDataPos, n);        dwDataPos += n;

u4S = u4TmpData1 *(u4TmpData2 / 10000000);

msgJPEGInfo.msgIFDGps.i4GPSLatSec = u4S;

msgJPEGInfo.msgIFDGps.f8GPSLatDDD = msgJPEGInfo.msgIFDGps.i4GPSLatRef

* ((double)u4D + (double)u4M / 60.0 + (double)u4S / 1e7 / 3600.0);

msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark = msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark | GPSMarkLat;

printf("纬度值:\t\t%d °%2d′ %10.7lf″ [ %12.7lf°]\n", u4D, u4M, (double)u4S / 1e7, msgJPEGInfo.msgIFDGps.f8GPSLatDDD);

break;

case 0X0003:  //GPSLongitudeRef

if (u4IFDOffsetOrData == 0X00000045) //字母'E'

{

msgJPEGInfo.msgIFDGps.i4GPSLonRef = 1;

printf("经度参考:\t\t东半球\n");

}

else

{

msgJPEGInfo.msgIFDGps.i4GPSLonRef = -1;

printf("经度参考:\t\t西半球\n");

}

break;

case 0X0004:  //GPSLongitude

//找到数据存储位置

dwDataPos = TIFF_Header_Pos + u4IFDOffsetOrData;

n = 4;

memcpy(&u4TmpData1, pu1FleDate + dwDataPos, n);        dwDataPos += n;

memcpy(&u4TmpData2, pu1FleDate + dwDataPos, n);        dwDataPos += n;

u4D = u4TmpData1 / u4TmpData2;

msgJPEGInfo.msgIFDGps.i4GPSLonDeg = u4D;

memcpy(&u4TmpData1, pu1FleDate + dwDataPos, n);        dwDataPos += n;

memcpy(&u4TmpData2, pu1FleDate + dwDataPos, n);        dwDataPos += n;

u4M = u4TmpData1 / u4TmpData2;

msgJPEGInfo.msgIFDGps.i4GPSLonMin = u4M;

memcpy(&u4TmpData1, pu1FleDate + dwDataPos, n);        dwDataPos += n;

memcpy(&u4TmpData2, pu1FleDate + dwDataPos, n);        dwDataPos += n;

u4S = u4TmpData1 *( u4TmpData2 / 10000000);

msgJPEGInfo.msgIFDGps.i4GPSLonSec = u4S;

msgJPEGInfo.msgIFDGps.f8GPSLonDDD = msgJPEGInfo.msgIFDGps.i4GPSLonRef

* ((double)u4D + (double)u4M / 60.0 + (double)u4S / 1e7 / 3600.0);

msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark = msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark | GPSMarkLon;

printf("经度值:\t\t%d °%2d′ %10.7lf″ [ %12.7lf°]\n", u4D, u4M, (double)u4S / 1e7, msgJPEGInfo.msgIFDGps.f8GPSLonDDD);

break;

case 0X0005:  //GPSLongitudeRef

if (u4IFDOffsetOrData == 0X00000001) //水面以下

{

i4GPSAltitudeRef = -1;

printf("高度参考:\t\t水面以下\n");

}

else

{

i4GPSAltitudeRef = 1;

printf("高度参考:\t\t水面以上\n");

}

break;

case 0X0006:  //GPSAltitude

//找到数据存储位置

dwDataPos = TIFF_Header_Pos + u4IFDOffsetOrData;

n = 4;

memcpy(&u4TmpData1, pu1FleDate + dwDataPos, n);        dwDataPos += n;

memcpy(&u4TmpData2, pu1FleDate + dwDataPos, n);        dwDataPos += n;

msgJPEGInfo.msgIFDGps.f8GPSAlt = i4GPSAltitudeRef * (double)u4TmpData1 / (double)u4TmpData2;

msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark = msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark | GPSMarkAlt;

printf("高度值:\t\t%.3lf m\n", msgJPEGInfo.msgIFDGps.f8GPSAlt);

break;

default:

break;

}

}

}

//以上处理IFD子帧数据信息,到此结束

//处理一帧IFD数据

void processIFD()

{

ULONG    u4DataLength = 0; //数据存储占用字节数

u4DataLength = getIFDTypeLength(msgIFD.u2IFDType) * msgIFD.u4IFDCounts;

switch (msgIFD.u2IFDTag)

{

case 0X0131:  //此处表示 Software used

processIFD_Software(u4DataLength);

break;

case 0X8825:  //此处表示 GPS信息,这是一个扩展信息,需要再次分块解析

processIFD_GPS(u4DataLength);

break;

default:

break;

}

}

//处理1帧 0XE0 JFIF应用数据块 数据

/*******************

DWORD   dwStarPos    处理的起始位置,从标志数据头标识符开始

DWORD   dwFrameLen        数据帧长度

*****************/

void processFrame_0XE0(DWORD dwStarPos, DWORD dwFrameLen)

{

DWORD i2Pos = 4 + dwStarPos;

unsigned short u2DataTmp;

memcpy(msgJPEGInfo.msgEO_APP0.u1APP0Inden, pu1FleDate + i2Pos, 5);    i2Pos += 5;

msgJPEGInfo.msgEO_APP0.u1MajorVersion = pu1FleDate[i2Pos];       i2Pos += 1;

msgJPEGInfo.msgEO_APP0.u1MinorVersion = pu1FleDate[i2Pos];       i2Pos += 1;

msgJPEGInfo.msgEO_APP0.u1UnitsXY         = pu1FleDate[i2Pos];       i2Pos += 1;

memcpy(&u2DataTmp, pu1FleDate + i2Pos, 2);

msgJPEGInfo.msgEO_APP0.u2Xdensity = u2ChageHL(u2DataTmp );           i2Pos += 2;

memcpy(&u2DataTmp, pu1FleDate + i2Pos, 2);

msgJPEGInfo.msgEO_APP0.u2Ydensity = u2ChageHL(u2DataTmp );           i2Pos += 2;

unsigned char u1Xthumbnail = 0, u1Ythumbnail = 0;

u1Xthumbnail = pu1FleDate[i2Pos];                                       i2Pos += 1;

u1Ythumbnail = pu1FleDate[i2Pos];                                       i2Pos += 1;

msgJPEGInfo.msgEO_APP0.u1Xthumbnail = u1Xthumbnail;

msgJPEGInfo.msgEO_APP0.u1Ythumbnail = u1Ythumbnail;

if (u1Xthumbnail > 0 && u1Ythumbnail > 0 && i2Pos + 3 * u1Xthumbnail * u1Ythumbnail <= dwStarPos + dwFrameLen )

{

msgJPEGInfo.msgEO_APP0.pu1ThumbnailRGBBitmapl = new unsigned char[3 * u1Xthumbnail * u1Ythumbnail];

memcpy(msgJPEGInfo.msgEO_APP0.pu1ThumbnailRGBBitmapl, pu1FleDate + i2Pos, 3 * u1Xthumbnail * u1Ythumbnail);

}

printf("********处理JFIF应用数据块:\n");

printf("识别APP0标记       :%c%c%c%c\n", msgJPEGInfo.msgEO_APP0.u1APP0Inden[0],

msgJPEGInfo.msgEO_APP0.u1APP0Inden[1],

msgJPEGInfo.msgEO_APP0.u1APP0Inden[2],

msgJPEGInfo.msgEO_APP0.u1APP0Inden[3] );

printf("主要版本号         :%d\n", msgJPEGInfo.msgEO_APP0.u1MajorVersion);

printf("次要版本号         :%d\n", msgJPEGInfo.msgEO_APP0.u1MajorVersion);

printf("X和Y的密度单位     :%d\n", msgJPEGInfo.msgEO_APP0.u1UnitsXY);

printf("水平方向像素密度   :%d\n", msgJPEGInfo.msgEO_APP0.u2Xdensity);

printf("垂直方向像素密度   :%d\n", msgJPEGInfo.msgEO_APP0.u2Ydensity);

printf("缩略图水平像素数目 :%d\n", u1Xthumbnail);

printf("缩略图垂直像素数目 :%d\n", u1Ythumbnail);

}

//处理1帧 0XE1 EXIFF应用数据块 数据

/*******************

DWORD       dwStarPos    处理的起始位置

DWORD       dwFrameLen        数据帧长度

*****************/

void processFrame_0XE1(DWORD dwStarPos , DWORD dwFrameLen)

{

DWORD dwPos = dwStarPos + 4;

unsigned long u4TmpData = 0;

unsigned long u4OffsetIFD = 0;

unsigned short u2TmpData = 0;

int n = 4;

//Exif标识符

memcpy(&u4TmpData, pu1FleDate + dwPos, n);         dwPos += n;

if (u4TmpData != u4ExifFla)

{

failInfo = "Exiff标志错误";

throw(failInfo);

}

//跳过两个补位码

n = 2;

dwPos += n;

//记录TIFF_Header_Pos 位置

TIFF_Header_Pos = dwPos;

//TIFF头 Byte Order

n = 2;

memcpy(&u2TifByteOrder, pu1FleDate + dwPos, n);        dwPos += n;

//固定标志

n = 2;

memcpy(&u2TmpData, pu1FleDate + dwPos, n);             dwPos += n;

if (u2TmpData != 0X002A )

{

failInfo = "Exiff固定标志0X002A错误!";

throw(failInfo);

}

//IFD偏移量

n = 4;

memcpy(&u4OffsetIFD, pu1FleDate + dwPos, n);       dwPos += n;

//跟踪到IFD数据位置 ,由标志头 加 偏移量得到

dwPos = u4OffsetIFD + TIFF_Header_Pos;

//IFD数量

unsigned short u2IFDNum = 0;

n = 2;

memcpy(&u2IFDNum, pu1FleDate + dwPos, n);         dwPos += n;

//循环读取 IFD 入口数据

USHORT u2IFDTag;           //IFD的数据标志

USHORT u2IFDType;          //IFD的数据类型

ULONG  u4IFDCounts;            //IFD的数据数量

ULONG    u4IFDOffsetOrData; //IFD的数据或者数据存储偏移量

for (int i = 0; i < u2IFDNum; i++)

{

n = 2;

memcpy(&u2IFDTag,          pu1FleDate + dwPos, n);    dwPos += n;

memcpy(&u2IFDType,         pu1FleDate + dwPos, n);    dwPos += n;

n = 4;

memcpy(&u4IFDCounts,       pu1FleDate + dwPos, n);    dwPos += n;

memcpy(&u4IFDOffsetOrData,  pu1FleDate + dwPos, n);    dwPos += n;

//保存待处理的IFD信息

msgIFD.u2IFDTag = u2IFDTag;

msgIFD.u2IFDType = u2IFDType;

msgIFD.u4IFDCounts = u4IFDCounts;

msgIFD.u4IFDOffsetOrData = u4IFDOffsetOrData;

//处理IFD信息

processIFD();

}

}

//处理1帧数据

/*******************

unsigned char u1InfoIden,       数据帧标识符

DWORD       dwCurPos              当前处理开始的位置

DWORD       dwFrameLen                 数据帧长度

*****************/

void processFrameInfo(unsigned char u1InfoIden, DWORD dwCurPos, DWORD dwFrameLen)

{

switch (u1InfoIden)

{

case 0XE0:

processFrame_0XE0(dwCurPos, dwFrameLen);  //JFIF应用数据块

break;

case 0XE1:

processFrame_0XE1(dwCurPos, dwFrameLen);

break;

default:

break;

}

}

int main(int argc, _TCHAR* argv[])

{

msgJPEGInfo.msgEO_APP0.pu1ThumbnailRGBBitmapl = NULL;

msgJPEGInfo.msgIFDGps.u2GPSMsg_Mark = 0X00000000;

DWORD dwFileRead = 0;

//打开 JPEG file

_TCHAR sFile[] = _T("d:\\Users\\PlaDayBreak\\Desktop\\77\\Test Pos.JPEG");

HANDLE hFile = CreateFile(sFile,// argv[1],  //LPCTSTR lpcszFileName

GENERIC_WRITE | GENERIC_READ,       //DWORD dwAccess

FILE_SHARE_WRITE | FILE_SHARE_READ, //DWORD dwShareMode

NULL,                             //LPSECURITY_ATTRIBUTES lpSecurityAttributes

OPEN_ALWAYS,                      //DWORD dwCreate

FILE_ATTRIBUTE_NORMAL,            //DWORD dwFlagsAndAttributes

NULL                              //HANDLE hTemplateFile

);

try

{

if (hFile == 0)

{

failInfo = "fail to open file!";

throw(failInfo);

}

DWORD dwTmp = 0;

dwFleLen = GetFileSize(hFile, &dwTmp);

pu1FleDate = new unsigned char[dwFleLen];

ReadFile(hFile, pu1FleDate, dwFleLen, &dwFileRead, NULL); //把文件信息读入内存;

CloseHandle(hFile);

//开始解析数据

//1. 校验SOF

unsigned short u2Tmp;

DWORD dwCurPos = 0;//当前读取的文件位置

memcpy(&u2Tmp, pu1FleDate + dwCurPos, 2); dwCurPos += 2;

if (u2ChageHL(u2Tmp) != u2SOI)

{

failInfo = "Bad TIFF header";

throw(failInfo);

}

//2. 开始读取信息类型

unsigned short u2InfoLen = 0;

unsigned char u1InfoIden = 0;//消息标志

//printf("消息 : 长度 : 起始位置\n");

while (dwCurPos < dwFleLen)

{

if (pu1FleDate[dwCurPos] != 0XFF)

{

dwCurPos++;

continue;

}

dwCurPos++;

u1InfoIden = pu1FleDate[dwCurPos];// 消息标志

dwCurPos++;

if (dwCurPos >= dwFleLen)

break;

memcpy(&u2InfoLen, pu1FleDate + dwCurPos, 2);

u2InfoLen = (pu1FleDate[dwCurPos ] << 8 )+ pu1FleDate[dwCurPos + 1];

dwCurPos += u2InfoLen;

if (dwCurPos >= dwFleLen)

break;

//printf("FF%2X : %4d : %4d\n", u1InfoIden, u2InfoLen, dwCurPos - u2InfoLen - 2);

processFrameInfo(u1InfoIden, dwCurPos - u2InfoLen - 2, u2InfoLen);

}

}

catch (string e)

{

cout << e << endl;

}

return 1;

}

解析结果:

6 参考文献

1

jpg文件格式分析_bluesky_sunshine的博客-CSDN博客_jpg文件格式

《JPEG文件格式分析》

2

JPEG文件格式解析(一) Exif 与 JFIF - 云+社区 - 腾讯云

《JPEG文件格式解析(一) Exif 与 JFIF》

3

jpg 格式举例详解_Eleven_ZY的博客-CSDN博客

《JPEG 格式举例详解——非常详细》

4

Exchangeable image file format for digital still cameras: Exif Version 2.2

5

Exchangeable image file format for digital still cameras: Exif Version 2.3

6

TIFF Revision 6.0

解析JPEG文件的GPS信息相关推荐

  1. IIQ文件内gps信息的分析

    版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的. 近日有网友咨询,使用<vb.net 教程 5-7 Bitmap类 3 获得图片信息Exif ...

  2. C语言解析pcap文件得到HTTP信息实例(原创,附源码)

    原文:http://xiexiaohui.com.host2.ugocn.com/index.php/archives/34 转载请注明出处.来自 hello xiexh (xiexiaohui092 ...

  3. c语言编程题报文解析,C语言解析pcap文件得到HTTP信息实例

    程序功能为解析由Wireshark生成的pcap文件. 实现步骤: 1)用Wireshark软件抓包得到test.pcap文件 2)程序:分析pcap文件头 -> 分析pcap_pkt头 -&g ...

  4. Java解析wav文件基本格式信息

    本文提供的一种获取wav文件基本格式的一种方式,方便在前端完成一些数据的操作.最近写Java处理音频文件的相关代码,为了便于查看获取的音频文件的格式,查看访问其基本格式信息在进行下一步数据处理之前是非 ...

  5. java解析音频文件/音乐播放器

    说明: 主要是用了Java sound(刚开始我也不知道,百度什么的,查查的就明白了,或者直接参考jdk的API文档),我没有打印所有的信息,想要什么参考官方API文档自己加,在此附上官方的demo( ...

  6. DICOM笔记-解析JPEG压缩格式DCM文件

      项目中使用了DICOM文件保存图像,之前经常遇到DICOM内放置的是short类型或者float类型的二维图像,按照之前的代码处理JEPG压缩的DICOM文件,当然会出现问题:从网上查到资料,是由 ...

  7. JPEG图像EXIF数据信息的解析

    刚刚结束本科生活的我,那会还比较颓废,但是因为读研,导致宝宝七月初就到了学校,来了以后也不知道该做什么,就出去浪呀~哈哈,不料来了不久后接到老师的任务,让宝宝把JPEG图像的EXIF信息解读出来,当时 ...

  8. 从EXIF JPEG图片中提取GPS位置信息

    符合EXIFJPEG标准的图片,除了记录图片压缩数据之外,在存储了拍摄参数,其中包括拍摄时GPS参数信息,因此,可以利用程序从EXIF元数据信息中解析提取GPS位置信息. 1. Java读取EXIF信 ...

  9. 导入数据任务(id:373985)异常, 错误信息:解析导入文件错误,请检查导入文件内容,仅支持导入json格式数据及excel文件

    导入数据任务(id:373985)异常, 错误信息:解析导入文件错误,请检查导入文件内容,仅支持导入json格式数据及excel文件 参考文章: (1)导入数据任务(id:373985)异常, 错误信 ...

最新文章

  1. 一文览尽LiDAR点云目标检测方法
  2. Java ClassLoader详解
  3. python绘图实例-Python matplotlib基础绘图函数示例
  4. 下列代码之后的结果为()?
  5. dtoj#4263. duliu
  6. 同步工具之CyclicBarrier循环栅栏
  7. 1.4_select_sort_选择排序
  8. 判断app访问还是web访问网站
  9. 用python海龟画一个三角形_海龟画笔---和孩子一起学python
  10. FFS学习 (FTL)
  11. 小米网卡驱动linux,小米笔记本 Air 13.3 在 Linux Mint 下安装 nvidia 驱动
  12. uni-app 微信、支付宝APP支付流程
  13. 12、Urban Radiance Fields
  14. 基于JAVA家电售后管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
  15. JS基础之强制类型转换
  16. EVM 操作码(Opcode)与 字节码(Bytecode)
  17. 2005路网 2008路网 2009路网 2010路网 2011路网 2012路网 2013路网 2014路网 2015路网2016路网 2017路网 2018路网 2019路网下载与分析
  18. 四、无限法则roe-滑雪进阶入门小贴士
  19. 微软模拟飞行2020服务器多少内存,微软模拟飞行配置要求高吗 微软模拟飞行2020配置要求介绍_游侠网...
  20. Office2010 整合 SP2補丁

热门文章

  1. Shell语言(一)
  2. Linux命令行与shell脚本编程大全.第3版.pdf
  3. Centos在NAT模式下的设置
  4. 如何一键制作css精灵图?
  5. Spring AOP:搞清楚advice的执行顺序
  6. perl脚本语言学习
  7. 修改服务器端口后防火墙要设置吗,服务器设置完防火墙需要重启吗
  8. Azure BareMetal 裸金属
  9. 详解ZStack高级功能--裸金属服务
  10. 杰理之NLP 参数【篇】