y-matsui::weblog

電子楽器、音楽、コンピュータ、プログラミング、雑感。面倒くさいオヤジの独り言

動画カメラでキャプチャした静止画にEXIF(緯度経度)を埋め込む実験

s.h.logさんのDirectShow.NETで静止画キャプチャ のサンプルプロジェクト”070104_1130_WebcamCaptureImage.zip”を元にして、動画カメラからJPEGを生成して、EXIF情報を付加するテストプログラムを作った。
exif_tag.gif
元々EXIF情報が無いJPEGに、EXIFを追加する方法について、なかなか情報が少ないようなのだが、
画像のExif情報を取得する、設定するEXIF情報の保存を参考に、元々EXIF情報が無い場合の付加方法を参考にしつつ、
Image.Saveメソッドで一度EXIF形式で保存、再度ファイルから読んで、別のタグを見つけ、IDを偽装して挿入、ファイルを別名保存する方法で成功した。
Image.Save(FileName,ImageFormat.Exif)で、Unknown(0301)、Unknown(0303)というのが付加されるので、これを
if *1
{
PropertyItem pi = img.PropertyItems[0];
なんていう感じで捕まえて、無理やり別のIDをねじこんじゃえばOK。
追加したEXIFタグは
メーカー(0x010F、ASCII)、ソフトウェア(0x0131、ASCII)、オリジナル撮影日時(0x9003、ASCII)、GPSタグバージョン(0x0000、BYTE)、北緯南緯(0x0001、ACSII)、緯度(0x0002、RATIONAL)、東経西経(0x0003、ASCII)、緯度(0x0004、RATIONAL)、測地系(0x0012、ASCII)
BYTEやRATIONALは相当に面倒くさい。泥臭く1バイトづつbyte配列に書き出していくという地味な感じ。
特に緯度経度は、度、分、秒ごとにintを6つ使って、分数表現。計24バイトのバイナリ配列だ。

EXIFフォーマット
参考:F6EXIF
ID,TYPEなんかが分かりやすく説明されている。

EXIFのデータTYPE(参考:画像のExif情報を取得する、設定する
ID TYPE
1 Byte
2 ASCII 形式でエンコードされた Byte オブジェクトの配列
3 16 ビット整数
4 32 ビット整数
5 有理数を表す 2 つの Byte オブジェクトの配列
6 未使用
7 未定義
8 未使用
9 SLong
10 SRational

■ソースの抜粋(c#
//そのままでは動きません。!!

//WindowsForm上に表示しているビットマップをExif形式で保存
pictureBox0.Image.Save(FileName,ImageFormat.Exif);

//新しいImageインスタンスをファイルから作成する
Image img = Image.FromFile(FileName);

if *2
{
PropertyItem pi = img.PropertyItems[0];

//加工用バイト配列
byte data;
byte
buf;

//基本情報
//原画像データの生成日時
string date = DateTime.Now.ToShortDateString().Replace("/", ":") + " " + DateTime.Now.ToLongTimeString();
setExif(img, 0x9003, "ASCII", date);

//画像タイトル:0x10E ASCII


//メーカー名:0x010F ASCII
string company = "会社名なんかを英語で書く";
setExif(img, 0x010F, "ASCII", company);

//モデル名:0x0110 ASCII


//画像方向:0x0112 SHORT


//ソフトウェア:0x0131 ASCII
string software = "ソフト名を英数で書く";
setExif(img, 0x0131, "ASCII", software);


//GIS情報
//GPSタグ番号 2.0.0.0で固定
pi.Id = 0x0000;
pi.Type = 1;
data = new byte[4];
buf = new byte[1];

         //泥臭く2,0,0,0をバイト配列に書き込む
buf = BitConverter.GetBytes(2);
Buffer.BlockCopy(buf, 0, data, 0, 1);
buf = BitConverter.GetBytes(0);
Buffer.BlockCopy(buf, 0, data, 1, 1);
buf = BitConverter.GetBytes(0);
Buffer.BlockCopy(buf, 0, data, 2, 1);
buf = BitConverter.GetBytes(0);
Buffer.BlockCopy(buf, 0, data, 3, 1);

pi.Value = data;
pi.Len = pi.Value.Length;
img.SetPropertyItem(pi);

//緯度NS Nで固定
setExif(img, 0x0001, "ASCII", "N");

//経度EW Eで固定
setExif(img, 0x0003, "ASCII", "E");

//測地系 WGS-84で固定
setExif(img, 0x0012, "ASCII", "WGS-84");

//緯度(lat):0x0002 RATIONAL
//[百分率の小数点以下] = [分] / 60 + [秒] / 3600
double lat = [百分率表記の緯度];
double lon = [百分率表記の経度];

data = new byte[24];
buf = new byte[4];

int i_do;
int i_fun;
int i_byo;
         //百分率を度、分、秒にする
double d = (lat % 1)*3600;
i_do = (int)(lat/1);
i_fun= (int)(d/60);
i_byo = (int)((d%60)*1000);

pi.Id = 0x0002;
pi.Type = 5;

buf = BitConverter.GetBytes(i_do);
Buffer.BlockCopy(buf, 0, data, 0, 4);
buf = BitConverter.GetBytes(1);
Buffer.BlockCopy(buf, 0, data, 4, 4);
buf = BitConverter.GetBytes(i_fun);
Buffer.BlockCopy(buf, 0, data, 8, 4);
buf = BitConverter.GetBytes(1);
Buffer.BlockCopy(buf, 0, data, 12, 4);
buf = BitConverter.GetBytes(i_byo);
Buffer.BlockCopy(buf, 0, data, 16, 4);
buf = BitConverter.GetBytes(1000);
Buffer.BlockCopy(buf, 0, data, 20, 4);

pi.Value = data;
pi.Len = pi.Value.Length;
img.SetPropertyItem(pi);


//経度(lon)
pi.Id = 0x0004;
pi.Type = 5;

d = (lon % 1) * 3600;
i_do = (int)(lon / 1);
i_fun = (int)(d / 60);
i_byo = (int)((d % 60)*1000);

buf = BitConverter.GetBytes(i_do);
Buffer.BlockCopy(buf, 0, data, 0, 4);
buf = BitConverter.GetBytes(1);
Buffer.BlockCopy(buf, 0, data, 4, 4);
buf = BitConverter.GetBytes(i_fun);
Buffer.BlockCopy(buf, 0, data, 8, 4);
buf = BitConverter.GetBytes(1);
Buffer.BlockCopy(buf, 0, data, 12, 4);
buf = BitConverter.GetBytes(i_byo);
Buffer.BlockCopy(buf, 0, data, 16, 4);
buf = BitConverter.GetBytes(1000);
Buffer.BlockCopy(buf, 0, data, 20, 4);

pi.Value = data;
pi.Len = pi.Value.Length;
img.SetPropertyItem(pi);

}

//ファイル保存
string s = DateTime.Now.ToShortDateString().Replace("/", "") + DateTime.Now.ToLongTimeString().Replace(":", "").Replace(" ", "");
img.Save(s + "_" + FileName, FileFormat);

       //内部で勝手に生成したイメージオブジェクトを破棄
img.Dispose();
       //作業用に書き出したEXIFファイルを削除
File.Delete(FileName);

*1:img.PropertyItems != null) && (img.PropertyItems.Length != 0

*2:img.PropertyItems != null) && (img.PropertyItems.Length != 0