2024.05.13
2024.04.07
サンプル写真 2024.03.09 撮影 道路にいたカモシカ DSC_0073.JPG
写真のファイルのexifという領域に撮影した状況が記録されています。exifを表示したり、変更したりするツールは既に
たくさんあるので、まったく必要ありません。c言語でバイナリファイルを操作したり、構造体を使ったプログラム作成の
練習のために調べました。
以下は、Exif情報を順番に追ってGPSの緯度、経度の情報にたどり着く手順。
詳しくは https://www.cipa.jp/j/std/std-sec.html#listTop
「デジタルスチルカメラ用画像ファイルフォーマット規格 Exif3.0 」 参照
jpegファイルの先頭から1kバイトぐらいのところまでにexif情報が記述されているようです。
ダンプツールはhexdumpを使います。
$ hexdump -C DSC_0073.JPG > 0073.log
$ less 0073.log
00000000 ff d8 ff
e1 21 3b 45 78 69 66 00 00 4d 4d 00 2a
|....!;Exif..MM.*| 00000010 00 00 00 08 00 0d 01 00 00 03 00 00 00 01 0c c0 |................| 00000020 00 00 01 01 00 03 00 00 00 01 09 90 00 00 01 0e |................| 00000030 00 02 00 00 00 09 00 00 00 aa 01 0f 00 02 00 00 |................| 00000040 00 06 00 00 00 b4 01 10 00 02 00 00 00 07 00 00 |................| 00000050 00 ba 01 12 00 03 00 00 00 01 00 01 00 00 01 1a |................| 00000060 00 05 00 00 00 01 00 00 00 c2 01 1b 00 05 00 00 |................| 00000070 00 01 00 00 00 ca 01 28 00 03 00 00 00 01 00 02 |.......(........| 00000080 00 00 01 32 00 02 00 00 00 14 00 00 00 d2 02 13 |...2............| 00000090 00 03 00 00 00 01 00 01 00 00 87 69 00 04 00 00 |...........i....| 000000a0 00 01 00 00 00 e6 88 25 00 04 00 00 00 01 00 00 |.......%........| 000000b0 03 3a 00 00 04 3c 44 53 43 5f 30 30 37 33 00 00 |.:...<DSC_0073..| 000000c0 53 48 41 52 50 00 53 48 2d 4d 31 39 00 00 00 00 |SHARP.SH-M19....| 000000d0 00 48 00 00 00 01 00 00 00 48 00 00 00 01 32 30 |.H.......H....20| 000000e0 32 34 3a 30 33 3a 30 39 20 31 30 3a 35 31 3a 34 |24:03:09 10:51:4| 000000f0 37 00 00 22 82 9a 00 05 00 00 00 01 00 00 02 84 |7.."............| |
スマホで撮影したjpeg形式の写真の先頭の方の領域にtiffヘッダーというのがある。(Tiff:Tag Image
File Format)
GPSのデータはこのTiffヘッダーを調べることから始まる。
1)まず先頭2バイトがff d8で始まることを確認。
2)そのあとExifという文字列を探す。必ずしもサンプルと同じ位置にあるとはかぎらない。。
3)Exifのあと2バイト後ろに、コードでいうと4d 4d ('MM')または49 49
('II')という文字列があるところが
tiffヘッダーの始まり。
00000000 ff d8 ff
e1 21 3b 45 78 69 66
00 00 4d 4d 00 2a |....!;Exif..MM.*| 00000010 00 00 00 08 00 0d 01 00 00 03 00 00 00 01 0c c0 |................| |
サイズ(byte) |
名称 | 概要 |
2 |
エンディアン |
'MM'はビッグエン
ディアン 'II'はリトルエンディアン |
2 |
マジックNO |
ビッグエンディアンのと
き下位から順に00 2aが入る リトルエンディアンのとき下位から順に2a 00が入る |
4 |
0th IFDへのオフセット |
0番目の
IFD(Image File Directory)のオフセットアドレス |
00000000 ff d8 ff
e1 21 3b 45 78 69 66 00 00 4d 4d 00 2a
|....!;Exif..MM.*| 00000010 00 00 00 08 00 0d 01 00 00 03 00 00 00 01 0c c0 |................| |
サイズ(byte) | 概 要 |
2 |
エントリ数 |
12 |
エントリ1 |
12 |
エントリ2 |
: |
: |
12 |
エントリn |
4 |
NEXT IFD |
サイズ
(byte) |
概 要 |
2 |
タグ フィールドを識別するための固有の番号 |
2 |
タイプ |
4 |
カウント 値の個数 バイト数ではない |
4 |
値 ただし、値が4バイトを超えるときはtiff
ヘッダからのオフセットアドレス |
タイプ(2byte) |
概要 |
00 01 | BYTE 8ビット符号無し整数 |
00 02 | ASCII文字コード |
00 03 |
SHORT 16ビット(2バイト)符号無し整数 |
00 04 | LONG 32ビット(4 バイト)符号無し整数 |
00 05 | RATIONAL LONG2個。最初のLONG は分子、2個目のLONGは分母を表す。カウントは分子と分母ペアで1 |
00 07 | UNDEFINED フィールドの定義により、どんな値をとってもよい 8 ビットバイト |
00 09 | SLONG 32ビット(4 バイト)符号付き整数(2の補数表現) |
00 0a | SRATIONAL カウント(Count) SLONG2 個。最初の SLONG は分子、2 個目の SLONG は分母を表す |
00000000 ff d8 ff
e1 21 3b 45 78 69 66 00 00 4d 4d 00 2a
|....!;Exif..MM.*| 00000010 00 00 00 08 00 0d 01 00 00 03 00 00 00 01 0c c0 |................| 00000020 00 00 01 01 00 03 00 00 00 01 09 90 00 00 01 0e |................| 00000030 00 02 00 00 00 09 00 00 00 aa 01 0f 00 02 00 00 |................| 00000040 00 06 00 00 00 b4 01 10 00 02 00 00 00 07 00 00 |................| 00000050 00 ba 01 12 00 03 00 00 00 01 00 01 00 00 01 1a |................| 00000060 00 05 00 00 00 01 00 00 00 c2 01 1b 00 05 00 00 |................| 00000070 00 01 00 00 00 ca 01 28 00 03 00 00 00 01 00 02 |.......(........| 00000080 00 00 01 32 00 02 00 00 00 14 00 00 00 d2 02 13 |...2............| 00000090 00 03 00 00 00 01 00 01 00 00 87 69 00 04 00 00 |...........i....| 000000a0 00 01 00 00 00 e6 88 25 00 04 00 00 00 01 00 00 |.......%........| 000000b0 03 3a 00 00 04 3c 44 53 43 5f 30 30 37 33 00 00 |.:...<DSC_0073..| 000000c0 53 48 41 52 50 00 53 48 2d 4d 31 39 00 00 00 00 |SHARP.SH-M19....| 000000d0 00 48 00 00 00 01 00 00 00 48 00 00 00 01 32 30 |.H.......H....20| 000000e0 32 34 3a 30 33 3a 30 39 20 31 30 3a 35 31 3a 34 |24:03:09 10:51:4| 000000f0 37 00 00 22 82 9a 00 05 00 00 00 01 00 00 02 84 |7.."............| |
00000340 30 30 00
00 00 00 00 0d
00 00 00 01 00 00 00 04
|00..............| 00000350 02 02 00 00 00 01 00 02 00 00 00 02 4e 00 00 00 |............N...| 00000360 00 02 00 05 00 00 00 03 00 00 03 dc 00 03 00 02 |................| 00000370 00 00 00 02 45 00 00 00 00 04 00 05 00 00 00 03 |....E...........| 00000380 00 00 03 f4 00 05 00 01 00 00 00 01 00 00 00 00 |................| 00000390 00 06 00 05 00 00 00 01 00 00 04 0c 00 0c 00 02 |................| 000003a0 00 00 00 02 4b 00 00 00 00 0d 00 05 00 00 00 01 |....K...........| 000003b0 00 00 04 14 00 0e 00 02 00 00 00 02 54 00 00 00 |............T...| 000003c0 00 0f 00 05 00 00 00 01 00 00 04 1c 00 12 00 02 |................| 000003d0 00 00 00 07 00 00 04 24 00 1b 00 07 00 00 00 0f |.......$........| 000003e0 00 00 04 2c 00 00 00 00 00 00 00 24 00 00 00 01 |...,.......$....| 000003f0 00 00 00 29 00 00 00 01 00 00 00 35 00 00 00 01 |...).......5....| 00000400 00 00 00 8a 00 00 00 01 00 00 00 2f 00 00 00 01 |.........../....| 00000410 00 00 00 04 00 00 00 01 00 00 03 34 00 00 00 01 |...........4....| 00000420 00 00 00 00 00 00 00 01 00 00 00 a7 00 00 00 01 |................| 00000430 57 47 53 2d 38 34 00 00 41 53 43 49 49 00 00 00 |WGS-84..ASCII...| 00000440 47 50 53 2d 46 49 58 00 00 09 01 03 00 03 00 00 |GPS-FIX.........| 00000450 00 01 00 06 00 00 01 12 00 03 00 00 00 01 00 01 |................| 00000460 00 00 01 1a 00 05 00 00 00 01 00 00 04 ae 01 1b |................| |
000003e0 00 00 04
2c 00 00 00 00 00 00 00
24 00 00 00 01 |...,.......$....| 000003f0 00 00 00 29 00 00 00 01 00 00 00 35 00 00 00 01 |...).......5....| 00000400 00 00 00 8a 00 00 00 01 00 00 00 2f 00 00 00 01 |.........../....| 00000410 00 00 00 04 00 00 00 01 00 00 03 34 00 00 00 01 |...........4....| 00000420 00 00 00 00 00 00 00 01 00 00 00 a7 00 00 00 01 |................| 00000430 57 47 53 2d 38 34 00 00 41 53 43 49 49 00 00 00 |WGS-84..ASCII...| 00000440 47 50 53 2d 46 49 58 00 00 09 01 03 00 03 00 00 |GPS-FIX.........| |
/********************************************* * jpegファイルからGPSデータの緯度、経度を抽出する * 2024.05.13 * ******************************************/ #include<stdio.h> #include<stdlib.h> #include<string.h> fpos_t Getfilesize(FILE *); //ファイルサイズを調べる int Jpgchk(FILE *); //jpegファイルかどうかを確認 int Thdchk(FILE*); //Tiff header chk unsigned int bxl4(unsigned int); //4byte unsigned int型ビッグエンディアンを //リトルエンディアンにする unsigned short int bxl2(unsigned short int); //2byte unsigned shortint型ビッグエンディアンを //リトルエンディアンにする //tiff header 構造体 struct tifhead{ unsigned short int endian; //2byte endian unsigned short int magic; //2byte magic number unsigned int zerothifd; //4byte 0th IFD addr }; //IFD 12byte 構造体 typedef struct{ unsigned short entry_tag; unsigned short entry_type; unsigned int entry_cnt; unsigned int entry_value; }Ifd; //緯度経度構造体 typedef struct{ unsigned int deg; //度 unsigned int d_deno; //度の分母 unsigned int min; //分 unsigned int m_deno; //分の分母 unsigned int sec; //秒 unsigned int s_deno; //秒の分母 }Gpoint; /************************** * main *************************/ int main(int argc,char *argv[]){ FILE *fp; fpos_t fsize; unsigned int tifa; //tiff header 先頭アドレス unsigned int zthadr; //0thIFDaddr unsigned int gpsifd; //gps ifd addr unsigned int subadr; //gps ifd addr unsigned int wrk,i,j; unsigned short int wrk2; //2byte wrk unsigned short int entry; unsigned char buff[2024]; unsigned char charw; float degree,minute,second,deno; //度,分,秒,分母 unsigned char endian_f; //endian flag 'B' or 'L' if(argc<2){ printf(" exif_r ファイル名 \n"); exit(1); } if((fp=fopen(argv[1],"rb"))==NULL){ printf("%sをオープンできません。\n",argv[1]); exit(1); } //ファイルサイズの取得と表示 fsize=Getfilesize(fp); printf("ファイルサイズは %dです。\n",fsize); if(Jpgchk(fp)==0){ printf("Jpegファイルではありません\n"); exit(1); } else{ printf("Jpegファイルです\n"); } tifa=Thdchk(fp); if(tifa==0){ printf("Exifが見つかりません\n"); exit(1); } printf("tiffヘッダ先頭アドレス %02x\n",tifa); fseek(fp,tifa,SEEK_SET); fread(buff,1,8,fp); //tiffヘッダ8byte取得 printf("TIFFヘッダ "); for(i=0;i<8;i++){ printf(" %02x ",buff[i]); } printf("\n"); struct tifhead *thd; thd=(struct tifhead *)buff; //tiffヘッダ8byteを構造体thdに渡す //endian flagセット if(thd->endian == 0x4d4d){ endian_f='B'; //big endian }else if(thd->endian ==0x4949){ endian_f='L'; //little endian } //0th IFD先頭アドレス zthadr= thd->zerothifd; if(endian_f == 'B') //ビッグエンディアン zthadr = bxl4(zthadr); printf("0th IFD先頭アドレス %08x\n",zthadr); zthadr+=tifa; printf("tiffヘッダ先頭アドレスを加算した0th IFD先頭アドレス %08x\n",zthadr); fseek(fp,zthadr,SEEK_SET); fread(&entry,2,1,fp); //0thifdのエントリー数2バイト取得 if(endian_f =='B') entry=bxl2(entry); // printf("0th ifd の先頭2byte(エントリー数) %04x H \n",entry); wrk=fread(buff,1,entry*12 ,fp); //エントリー数✕12byte読み込む printf("0th ifdエントリー数✕12バイト読み込む\n"); for(i=0;i<entry;i++){ for(j=0;j<12;j++){ printf(" %02x ",buff[i*12+j]); } printf("\n"); } wrk2=0; Ifd *ifd_entry=(Ifd *)buff; for(i=0;i<entry;i++,ifd_entry++){ wrk2=ifd_entry->entry_tag; if(endian_f =='B'){//ビッグエンディアン wrk2=bxl2(wrk2);// } if(wrk2 == 0x8825) break; } if(wrk2 != 0x8825){ printf("GPS情報が見つかりません\n"); exit(1); } gpsifd=ifd_entry->entry_value; //GPS情報があるオフセットアドレス if(endian_f=='B')//ビッグエンディアン gpsifd=bxl4(gpsifd); printf("GPS情報があるアドレス gpsifd= %08x \n",gpsifd); gpsifd+=tifa; //オフセットアドレスを加算 printf("GPS情報があるアドレス+tiffヘッダ先頭アドレス gpsifd= %08x \n",gpsifd); fseek(fp,gpsifd,SEEK_SET); //fpをgps IFDの先頭に移動 fread(&entry,2,1,fp); //gps ifdのエントリー数2バイト取得 if(endian_f =='B') entry=bxl2(entry); // printf("GPS ifd の先頭2byte(エントリー数) %04x H \n",entry); wrk=fread(buff,1,entry*12 ,fp); //エントリー数✕12byte読み込む printf("GPS ifdエントリー数✕12バイト読み込む\n"); for(i=0;i<entry;i++){ for(j=0;j<12;j++){ printf(" %02x ",buff[i*12+j]); } printf("\n"); } Ifd *gps_entry=(Ifd *)buff; if(endian_f=='B'){ while(gps_entry->entry_tag!=0x0100){ gps_entry++; //北緯または南緯のタグまでポインタ移動 } charw=(unsigned char)gps_entry->entry_value; //printf("%c ",charw); //NまたはSの表示 while(gps_entry->entry_tag!=0x0200){ gps_entry++; //緯度のタグまでポインタ移動 } subadr=bxl4(gps_entry->entry_value)+tifa; //緯度データのあるアドレス printf(" 緯度データのあるアドレス %08x\n",subadr); fseek(fp,subadr,SEEK_SET); //緯度データのあるアドレスに移動 fread(buff,1,24,fp);//度分秒のデータを読み込む printf("緯度データ\n"); for(i=0;i<24;i++) printf(" %02x ",buff[i]); printf("\n"); Gpoint *latitude=(Gpoint *)buff; degree=(float)bxl4(latitude->deg)/(float)bxl4(latitude->d_deno); minute=(float)bxl4(latitude->min)/(float)bxl4(latitude->m_deno); second=(float)bxl4(latitude->sec)/(float)bxl4(latitude->s_deno); printf("%1.0f°%1.0f'%1.2f''",degree,minute,second); printf("%c \n",charw); //NまたはSの表示 while(gps_entry->entry_tag!=0x0300){ gps_entry++; //東経または西経のタグのポインタまで移動 } charw=(unsigned char)gps_entry->entry_value; while(gps_entry->entry_tag!=0x0400){ gps_entry++; //経度のタグまでポインタ移動 } subadr=bxl4(gps_entry->entry_value)+tifa; //経度データのあるアドレス printf(" 経度データのあるアドレス %08x\n",subadr); fseek(fp,subadr,SEEK_SET); //経度データのあるアドレスに移動 fread(buff,1,24,fp);//度分秒のデータを読み込む printf("経度データ\n"); for(i=0;i<24;i++) printf(" %02x ",buff[i]); printf("\n"); Gpoint *longitude=(Gpoint *)buff; degree=(float)bxl4(longitude->deg)/(float)bxl4(longitude->d_deno); minute=(float)bxl4(longitude->min)/(float)bxl4(longitude->m_deno); second=(float)bxl4(longitude->sec)/(float)bxl4(longitude->s_deno); printf("%1.0f°%1.0f'%1.2f''",degree,minute,second); printf("%c \n",charw); //EまたはWの表示 }else{ //リトルエンディアンの場合 while(gps_entry->entry_tag!=0x0001){ gps_entry++; //北緯または南緯のタグまでポインタ移動 } charw=(unsigned char)gps_entry->entry_value; while(gps_entry->entry_tag!=0x0002){ gps_entry++; //緯度のタグまでポインタ移動 } subadr=gps_entry->entry_value+tifa; //緯度データのあるアドレス printf(" 緯度データのあるアドレス %08x\n",subadr); fseek(fp,subadr,SEEK_SET); //緯度データのあるアドレスに移動 fread(buff,1,24,fp);//度分秒のデータを読み込む printf("緯度データ\n"); for(i=0;i<24;i++) printf(" %02x ",buff[i]); printf("\n"); Gpoint *latitude=(Gpoint *)buff; degree=(float)latitude->deg/(float)latitude->d_deno; minute=(float)latitude->min/(float)latitude->m_deno; second=(float)latitude->sec/(float)latitude->s_deno; printf("%1.0f°%1.0f'%1.2f''",degree,minute,second); printf("%c \n",charw);//NまたはSの表示 while(gps_entry->entry_tag!=0x0003){ gps_entry++; //東経または西経のタグのポインタまで移動 } charw=(unsigned char)gps_entry->entry_value; while(gps_entry->entry_tag!=0x0004){ gps_entry++; //経度のタグまでポインタ移動 } subadr=gps_entry->entry_value+tifa; //経度データのあるアドレス printf(" 経度データのあるアドレス %08x\n",subadr); fseek(fp,subadr,SEEK_SET); //経度データのあるアドレスに移動 fread(buff,1,24,fp);//度分秒のデータを読み込む printf("経度データ\n"); for(i=0;i<24;i++) printf(" %02x ",buff[i]); printf("\n"); Gpoint *longitude=(Gpoint *)buff; degree=(float)longitude->deg/(float)longitude->d_deno; minute=(float)longitude->min/(float)longitude->m_deno; second=(float)longitude->sec/(float)longitude->s_deno; printf("%1.0f°%1.0f'%1.2f''",degree,minute,second); printf("%c \n",charw); //EまたはWの表示 } return 0; } /************************ * 2byte(unsigned short int型) * bigendian -> little ***********************/ unsigned short int bxl2(unsigned short int addr){ unsigned short int xaddr; xaddr =(addr>>8) & 0x00ff; xaddr |=(addr<<8) & 0xff00; return xaddr; } /************************ * 4byte(unsigned int型) * bigendian -> little ***********************/ unsigned int bxl4(unsigned int addr){ unsigned int xaddr; xaddr=(addr>>24) & 0x000000ff; xaddr |=(addr>>8) & 0x0000ff00; xaddr |=(addr<<8) & 0x00ff0000; xaddr |=(addr<<24) & 0xff000000; return xaddr; } /*********************** * ファイルサイズを取得 * *********************/ fpos_t Getfilesize(FILE *fp){ fpos_t fsize; fseek(fp, 0, SEEK_END); //ファイルを最終ポイントに移動 fgetpos(fp, &fsize); //ファイルサイズを取得 return fsize; } /******************************************* * jpegファイルの確認 (先頭がff d8ならjpeg ) * 戻り値res=1:jpeg 0:jpeg以外 *******************************************/ int Jpgchk(FILE *fp){ unsigned char c,d; int res; fseek(fp,0,SEEK_SET);//ファイルポインタを先頭にセット res=0; c=fgetc(fp); d=fgetc(fp); if ((c == 0xff)&& (d==0xd8)) res=1; //jpegファイルです return res; } /******************************************** * Tiff Header check * 1. 'Exif'という文字列を見つける * 2. 'Exif'の文字列の後ろ3バイト目 * が'MM'または'II'になっていることを確認 * 3. オフセットアドレスを返す ********************************************/ int Thdchk(FILE *fp){ unsigned char c; int res; fseek(fp,0,SEEK_SET);//ファイルポインタを先頭にセット res=0; while((c=fgetc(fp))!=EOF){ if(c=='E'){ if((c=fgetc(fp))!=EOF){ if(c=='x'){ if((c=fgetc(fp))!=EOF){ if(c=='i'){ if((c=fgetc(fp))!=EOF){ if(c=='f'){ res+=6; //printf("Exif offset addr=%x\n",res); break; } } } } } } } res++; if(res>100000){ res=0; break; } } return res; } |
$ gcc
exif_read.c -o exif_read |
$./exif_read
DSC_0073.JPG ファイルサイズは 2243649です。 Jpegファイルです tiffヘッダ先頭アドレス 0c TIFFヘッダ 4d 4d 00 2a 00 00 00 08 0th IFD先頭アドレス 00000008 tiffヘッダ先頭アドレスを加算した0th IFD先頭アドレス 00000014 0th ifd の先頭2byte(エントリー数) 000d H 0th ifdエントリー数✕12バイト読み込む 01 00 00 03 00 00 00 01 0c c0 00 00 01 01 00 03 00 00 00 01 09 90 00 00 01 0e 00 02 00 00 00 09 00 00 00 aa 01 0f 00 02 00 00 00 06 00 00 00 b4 01 10 00 02 00 00 00 07 00 00 00 ba 01 12 00 03 00 00 00 01 00 01 00 00 01 1a 00 05 00 00 00 01 00 00 00 c2 01 1b 00 05 00 00 00 01 00 00 00 ca 01 28 00 03 00 00 00 01 00 02 00 00 01 32 00 02 00 00 00 14 00 00 00 d2 02 13 00 03 00 00 00 01 00 01 00 00 87 69 00 04 00 00 00 01 00 00 00 e6 88 25 00 04 00 00 00 01 00 00 03 3a GPS情報があるアドレス gpsifd= 0000033a GPS情報があるアドレス+tiffヘッダ先頭アドレス gpsifd= 00000346 GPS ifd の先頭2byte(エントリー数) 000d H GPS ifdエントリー数✕12バイト読み込む 00 00 00 01 00 00 00 04 02 02 00 00 00 01 00 02 00 00 00 02 4e 00 00 00 00 02 00 05 00 00 00 03 00 00 03 dc 00 03 00 02 00 00 00 02 45 00 00 00 00 04 00 05 00 00 00 03 00 00 03 f4 00 05 00 01 00 00 00 01 00 00 00 00 00 06 00 05 00 00 00 01 00 00 04 0c 00 0c 00 02 00 00 00 02 4b 00 00 00 00 0d 00 05 00 00 00 01 00 00 04 14 00 0e 00 02 00 00 00 02 54 00 00 00 00 0f 00 05 00 00 00 01 00 00 04 1c 00 12 00 02 00 00 00 07 00 00 04 24 00 1b 00 07 00 00 00 0f 00 00 04 2c 緯度データのあるアドレス 000003e8 緯度データ 00 00 00 24 00 00 00 01 00 00 00 29 00 00 00 01 00 00 00 35 00 00 00 01 36°41'53.00''N 経度データのあるアドレス 00000400 経度データ 00 00 00 8a 00 00 00 01 00 00 00 2f 00 00 00 01 00 00 00 04 00 00 00 01 138°47'4.00''E $ |