GoPro HERO10 で撮影したmp4からGPSデータを抽出しGPX,KMLを出力する(C#)

はじめに

前回の記事

GoPro HERO10の動画(mp4)からFFMpegでメタデータを抽出する
GoPro HERO10で撮影した動画(mp4)からC#でGPSデータを抽出していきます.GPS等のセンサデータは,mp4ファイルにメタデータとして格納されていますので,今回は,C#からFFMpegを呼び出してmp4からメタデータを取り出すプログラムを作成しました.

で,GoPro HERO10で撮影した動画(mp4)からffmpegを使ってメタデータを取り出しました.取り出したデータは,バイナリ形式のファイルとして保存されますので,このファイルからGPSデータを抽出して使える形にしたいと思います.

データのフォーマット

GPSデータといっても,測位結果が入っているだけ(のはず)なので,Rawデータを読んでリアルタイムで測位演算するよりは簡単(なはず)です.フォーマットは,こちらのサイトでまとめて下さってます(素晴らしい!)ので参考にさせていただきます.

GitHub - gopro/gpmf-parser: Parser for GPMF™ formatted telemetry data used within GoPro® cameras.
Parser for GPMF™ formatted telemetry data used within GoPro® cameras. - GitHub - gopro/gpmf-parser: Parser for GPMF™ formatted telemetry data used within GoPro®...

読み進めていきますと,データは下の図のような構造を一つの単位として記録されているようです.

* Big Endian

①:データの塊の頭はASCII文字4文字が入ります(FourCCと呼ばれている)
②:データ型をASCII文字1文字で表現
③:データ構造の1単位(ひとかたまり)の合計バイト数
④:データ構造の1単位の繰返し回数
⑤:データが格納される.データ部は32ビット単位で記録されるので,
  終端が32ビットに満たない場合はゼロ埋めされる

以下,GPSデータを読むときに関連しそうなものだけをピックアップしてみました.

FourCC(ASCII4文字)
DEVCデバイスから流れてくるデータの1単位.各種データはこの下にネストされる
DVIDデバイスID.デバイス毎に自動的に振られるユニークな番号
DVNMデバイス名(手元のGoProのデータでは「HERO10 Black」が入っている)
STRM各種telemetry/metadataの開始タグ.各種データはこの下にネストされる
STNMStream名
(GPSなら「GPS (Lat., Long., Alt., 2D speed, 3D speed))」が入っている)
SCALスケールファクタ(除数)
UNIT単位
(GPSならデフォルト設定で “degdegm m/sm/s”)が記録されていた
GPS5測位結果(別表に詳細)
GPSUUTC時刻 (別表に詳細)
GPSFFIX状況 (別表に詳細)
GPSPDOP値 (別表に詳細)
データ型
データ型
記号
定義備考
ccharASCII文字
Fchar[4]ASCII4文字(FourCC)
Uchar[16]UTC Date Time文字列
I (える)32-bit signed integer
L32-bit unsigned integer
S16-bit unsigned integer0 to 65536
データ構造
FourCC内容出力頻度備考
GPS5lat, lon, alt, 2D speed, 3D speed約18Hz座標系:WGS-84
deg, deg, m, m/s, m/s
GPSUUTC Date & Time約1Hzyymmddhhmmss.sss
GPSFGPS Fix約1Hz0: ロック無し
2: 2D測位
3: 3D測位
GPSP精度劣化指標(DOP)約1Hz100倍した整数値

実際のデータと照らし合わせてみる

ffmpegで抽出したファイルをバイナリエディタで開いて,上のデータフォーマットと照らし合わせてみます.各種データの先頭はASCII文字4文字が入っていますので見やすいです.FourCCを拾いながら構造をみていきますと,以下のようになっていました.

DEVC
    DVID
    DVNM
    STRM
        STNM: Accelerometer
            加速度関係のデータ色々?(今は無視)
    STRM
        STNM: Gyroscope
            Gyro関係のデータ色々?(今は無視)
      |
      |
    STRM--その他省略
      |
      |
    STRM
        STNM: GPS (Lat., Long., Alt., 2D speed, 3D speed)
        GPSF
        GPSU
        UNIT
        GPSA
        GPS5
      |
    STRM--その他省略
      |
      |
DEVC
    DVID
    DVNM
      |
      |
      以下同じように繰り返し

GPSUの値で想像するに,DEVCは1秒毎に書かれているようです.ホントに1秒かは分かりませんが...いずれにせよ,GPSデータの入っている場所が分かりましたので,その辺りを重点的に読んでみます.

GPSF

GPS Fix状態が3であることがわかります

GPSU

16バイトのデータが1回繰り返されるので,データ部は16バイトになっています.
UTC時刻が,2011年11月13日01時45分34.634秒ということがわかります

GPSP

2バイトのデータが1つなので,データ部の後ろ2バイトはゼロ埋めになっています.
データはDOP×100なので,この時のDOP値は1.43ということがわかります.
DOP値にも色々ありますが,書かれてないので何が出力されているかは分かりません.Fix=3でPDOPかGDOP,Fix=2ならそもそも2次元なのでHDOPですかね...

UNIT

char[3]が5回繰り返しです.lat(緯度), lon(経度), alt(高度), 2d speed, 3d speed の単位になります.

SCAL

4バイト(int32)を5回繰り返しです.latとlonは10,000,000で割る.altと2D speed は1,000で割る.3D speedは100で割ることになります.

GPSA

GPSAは参考にしたサイトでは出ていませんでした.新たに加わったデータでしょうか.
“MSLV”はMean Sea Level(平均海水面)ですかね...高度表現の基準が格納されているものと思われます.

GPS5

(int32が5つ=20バイト)が18回繰り返し.一つ目のブロックを読み取って,スケールファクタで割ると,緯度:34.9814846度,経度:135.9636815度,高度:150.790m,2D速度:0.041m/s,3D速度:0.02m/sになります.多分ちゃんと読めてると思います.

例えば緯度ですが,小数点以下7桁まで記録されています.これは,(1e-7)×3600 = 3.6e-4[秒]になり,日本付近では大雑把に緯度1秒が30mくらいですから,分解能としては,(3.6e-4)×30 = 0.01m程度となります.ですので,1cmくらいまでの分解能で記録できているということになります.

GPX,KML形式で書き出す

以上で,データの所在と読み出し方が分かりましたので,あとは繰り返し読み出して,GPXやKMLなど使いやすい形式で出力するだけです.GPXやKMLのフォーマットは色んなデータを埋め込めますが,必要最小限でテストしていきたいと思います.

今回は,GPX,KMLファイルのテンプレートとして次のような構造にしました.

GPXのテンプレート
<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1"  creator="SkyRail - https://skyrail.tech/"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns="http://www.topografix.com/GPX/1/0"  >
  <metadata>
    <time>2021-11-14T08:34:24.774Z</time>
  </metadata>
  <trk>
    <name>Track-01</name>
    <trkseg>
      <trkpt lat="34.9833524" lon="135.7561149">
        <ele>10.733</ele>
        <time>2021-11-14T08:35:00.084Z</time>
        <extensions>
          <gpxtpx:TrackPointExtension>
            <gpxtpx:speed>0</gpxtpx:speed>
            <gpxtpx:dop>3.8</gpxtpx:dop>
          </gpxtpx:TrackPointExtension>
        </extensions>
      </trkpt>
      <trkpt lat="34.9851535" lon="135.7604088">
        <ele>69.738</ele>
        <time>2021-11-14T08:35:02.119Z</time>
        <extensions>
          <gpxtpx:TrackPointExtension>
            <gpxtpx:speed>0.186</gpxtpx:speed>
            <gpxtpx:dop>3.8</gpxtpx:dop>
          </gpxtpx:TrackPointExtension>
        </extensions>
      </trkpt>
    </trkseg>
  </trk>
</gpx>

GPXの場合は,9~18行目のように,<trkpt>…</trkpt>で囲まれた部分が1つの点(データ)になります.緯度,経度,高度,時刻,速度,DOP値を埋め込んでいます.

KMLのテンプレート
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Demo</name>
<description>Description Demo</description>
<Placemark>
  <name>Track Title</name>
  <description>Track Description</description>
  <Style>
    <LineStyle>
      <color>FF1400BE</color>
      <width>4</width>
    </LineStyle>
  </Style>
  <LineString>
    <extrude>1</extrude>
    <tessellate>1</tessellate>
    <altitudeMode>clampToGround</altitudeMode>
    <coordinates>
135.7561149,34.9833524,69.738
135.7604088,34.9851535,59.572
135.7604162,34.9852428,52.853
    </coordinates>
  </LineString>
</Placemark>
</Document>
</kml>

KMLの場合は,19~23行目のように,<coordinates>…</coordinates>の間に,経度,緯度,高度を1点につき1行で埋め込めばOKです.

18行目で,altitudeModeをclampToGroundに設定しています.”clampToGround”にすると,GoogleEarthでの表示(点を線で繋いだ表示)は高度情報を無視して地表面に張り付いた表示になります.”absolute”にすると,海面からの高さ(標高)で表示されます.詳しくは以下に情報があります.

Altitude Modes  |  Keyhole Markup Language  |  Google for Developers

完成版のプログラム

以上をまとめて,次のような流れで処理を行うC#コンソールアプリを作成しました.

  1. GoProで撮影した動画から
  2. ffmpegでメタデータを抽出して(バイナリファイルに保存)
  3. GPSデータをGPXとKML形式で書き出す

全ソースコードはGitHubに置いています.ffprobe.exe,ffmpeg.exe,mp4動画のパス,ファイル名はプログラム内にベタ打ちしてますので適宜変更して下さい.

GitHub - Space-Sky-Rail/GoProGPS
Contribute to Space-Sky-Rail/GoProGPS development by creating an account on GitHub.

いつもは,もうちょっと機能追加してからとか,もうちょっとスマートなコードにしてからとか思っている内に,段々熱が冷めて何も公開せずに終わるのですが,今回は不完全でも公開することにしました.日曜プログラミングですので,ヘボい処理があってもご容赦下さい...

追記(GUIアプリ)

コンソール版,しかもファイル名等ベタ打ちはやはり使いにくいので,勢いでGUI付けました.
詳細は別ページにまとめています.C# (wpf, .NET6)で作りました.

タイトルとURLをコピーしました