2025.08.13プログラム修正
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データの緯度、経度を抽出する * ***********************************************/ #include<stdio.h> #include<stdlib.h> fpos_t Getfilesize(FILE *); //ファイルサイズを調べる int Jpgchk(FILE *); //jpegファイルかどうかを確認 int Thdchk(FILE *); // unsigned int bxl4(unsigned int); //4byte ビッグエンディアンを変換 unsigned short int bxl2(unsigned short int); //2byteのビッグエンディアンを変換 //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; /************************* * 構造体ポインタを受取り度、分、秒,NorSorEorWの表示 ************************/ void gpsprint(char *buff, unsigned char ewsn, char endian_f){ Gpoint *latilongi=(Gpoint *)buff; float degree,minute,second; //度,分,秒 if(endian_f=='B'){ degree=(float)bxl4(latilongi->deg)/(float)bxl4(latilongi->d_deno); minute=(float)bxl4(latilongi->min)/(float)bxl4(latilongi->m_deno); second=(float)bxl4(latilongi->sec)/(float)bxl4(latilongi->s_deno); }else{ degree=(float)latilongi->deg/(float)latilongi->d_deno; minute=(float)latilongi->min/(float)latilongi->m_deno; second=(float)latilongi->sec/(float)latilongi->s_deno; } printf("%1.0f°%1.0f'%1.2f''",degree,minute,second); printf("%c \n",ewsn); //N,E,W,Sの表示 } /************************** * main *************************/ int main(int argc,char *argv[ ]){ FILE *fp; fpos_t fsize; int i,j; unsigned char buff[2024]; char endian_f='L';//endian flag int tha; //Tiffヘッダアドレス unsigned int zthadr; //0thIFDaddr unsigned int gpsifd; //gps ifd addr unsigned int subadr; //緯度や経度のaddress unsigned short int entry; //IFDエントリ数 unsigned short int wrk2; //2byte wrk unsigned char ewsn; //東西南北 if(argc != 2){ printf(" exif_read ファイル名 \n"); exit(1); } if((fp=fopen(argv[1],"rb"))==NULL){ printf("%sをオープンできません。\n",argv[1]); exit(1); } //ファイルサイズの取得と表示 fsize=Getfilesize(fp); printf("ファイルサイズは %d byteです。\n",fsize); if(Jpgchk(fp)==0){ printf("Jpegファイルではありません\n"); exit(1); }else{ printf("Jpegファイルです\n"); } tha=Thdchk(fp); if(tha==-1){ printf("Exifが見つかりません\n"); exit(1); } printf("Tiffヘッダ先頭アドレス %02x\n",tha); fseek(fp,tha,SEEK_SET); //fpをTiffヘッダ 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に渡す //0th IFD先頭アドレス zthadr= thd->zerothifd; if(thd->endian == 0x4d4d){ //ビッグエンディアン zthadr = bxl4(zthadr); endian_f='B'; //endian flag 変更 } printf("0th IFD先頭アドレス %08x\n",zthadr); zthadr+=tha; 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); 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+=tha; //オフセットアドレスを加算 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); 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++; //北緯または南緯のタグまでポインタ移動 } ewsn=(unsigned char)gps_entry->entry_value; //'N'または'S' while(gps_entry->entry_tag!=0x0200){ gps_entry++; //緯度のタグまでポインタ移動 } subadr=bxl4(gps_entry->entry_value)+tha; //緯度データのあるアドレス }else{ //リトルエンディアン緯度の表示 while(gps_entry->entry_tag!=0x0001){ gps_entry++; //北緯または南緯のタグまでポインタ移動 } ewsn=(unsigned char)gps_entry->entry_value; while(gps_entry->entry_tag!=0x0002){ gps_entry++; //緯度のタグまでポインタ移動 } subadr=gps_entry->entry_value+tha; //緯度データのあるアドレス } 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"); gpsprint(buff,ewsn,endian_f); //角度、分、秒、北緯or 南緯の表示 if(endian_f == 'B'){ //ビッグエンディアン経度の表示 while(gps_entry->entry_tag!=0x0300){ gps_entry++; //東経または西経のタグのポインタまで移動 } ewsn=(unsigned char)gps_entry->entry_value; while(gps_entry->entry_tag!=0x0400){ gps_entry++; //経度のタグまでポインタ移動 } subadr=bxl4(gps_entry->entry_value)+tha; //経度データのあるアドレス }else{ //リトルエンディアン経度の表示 while(gps_entry->entry_tag!=0x0003){ gps_entry++; //東経または西経のタグまでポインタ移動 } ewsn=(unsigned char)gps_entry->entry_value; while(gps_entry->entry_tag!=0x0004){ gps_entry++; //経度のタグまでポインタ移動 } subadr=gps_entry->entry_value+tha; //経度データのあるアドレス } 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"); gpsprint(buff,ewsn,endian_f); //角度、分、秒、東経または西経の表示 return 0; } /*********************** * ファイルサイズを取得 ***********************/ #include <stdio.h> fpos_t Getfilesize(FILE *fp){ fpos_t fsize; fseek(fp, 0, SEEK_END); //ファイルポインタを終端へ fgetpos(fp, &fsize); //ファイルサイズを取得 return fsize; } /******************************************* * jpegファイルの確認 (先頭がff d8ならjpeg ) * 入力 FILE *fp * 戻り値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バイト目 * 'E'から数えて6バイト目をTiff Header * のオフセットアドレスとして返す * 3.もし見つからなかったら-1を返す ********************************************/ int Thdchk(FILE *fp){ unsigned char c; int i,tha=0; fseek(fp,0,SEEK_SET);//ファイルポインタを先頭にセット char chkstr[]="Exif"; for(i=0;i<=3;i++,tha++){ if(tha >= 0x400){ //'Exif'文字列の検索中止 tha=-1; break; } if((c=fgetc(fp))==EOF){ tha=-1; break; } if(c !=chkstr[i]){ i=0; continue; }else{ if(i==3 ){ //Exif 文字列発見 tha+=3; break; } } } return tha; } /************************ * 4byte(unsigned int型) * bigendian -> little ***********************/ unsigned int bxl4(unsigned int data){ unsigned int xdata; xdata=(data>>24) & 0x000000ff; //31〜24bitを7〜0bitへ移動 xdata |=(data>>8) & 0x0000ff00;//23〜16bitを15〜8bitへ移動 xdata |=(data<<8) & 0x00ff0000;//15〜8bitを23〜16bitへ移動 xdata |=(data<<24) & 0xff000000;//7〜0bitを31〜24bitへ移動 return xdata; } /************************ * 2byte(unsigned short int型) * bigendian -> little ***********************/ unsigned short int bxl2(unsigned short int data){ unsigned short int xdata; xdata =(data>>8) & 0x00ff; //15〜8bitを7〜0bitへ移動 xdata |=(data<<8) & 0xff00;//7〜0bitを15〜8bitへ移動 return xdata; } |
$ 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 $ |