
今回のお題は"台形の幾何補正"です。
「台形って何?」
はい、車や列車で前方を撮影した映像から、真上写真を得ること(=台形を長方形に)です。
ということで、やりたいことは
「前方映像として取得した道路や軌道の映像から、真上写真を作ろう」なのでした。
まずこちらをご覧ください。
![]() |
線路が縦に並行に伸び、枕木が綺麗に直行していますね。
やりたかったのは、静止画保存したものを(DxOなどを使って)バッチで処理することではなく、動画からダイレクトに幾何変形して、静止画ビューアで見せること。
結果から先に言えば、PythonでQGISプラグインとして実装できました。
![]() |
元の動画はこういう画角です。
![]() |
この赤線で囲ったところの台形を長方形に変形します。
![]() |
![]() |
水平線、垂直線で画像を回転する手法の、更に発展形で、パース変換という処理があります。
奥行きのある写真の投影変換ですね。台形を起こして長方形にします。
私が大変重用しているDxOの"PhotoLab"というのがあります。
パース変換を自動でしてくれたり、台形を指定して長方形に強要することもできます。
![]() |
![]() |
![]() |
一枚設定して、補正情報をコピーペーストすることで、一括処理にも対応しています。
![]() |
![]() |
DxOで処理をすると、*.dopファイルというのが生成されて、そこに処理時のパラメータがテキスト形式で記録されています。
この.dxoファイルの中に、KeystoningPointというパラメータがあり、これがまさに「この台形を長方形にしたよ」の証です。
座標値は正規化されており、縦横画素数を掛けると指定したX,Y座標になってます。
Overrides = {
KeystoningActive = true,
KeystoningPoints = {
0.4344940185546875,
-0.0019125227117910981,
0.35461607575416565,
0.97519111633300781,
0.68816280364990234,
1.0101222991943359,
0.5928160548210144,
-0.0090487487614154816,
}
,
KeystoningMode = "Parallel",
CropRect = {
0.33949017524719238,
0.029555149376392365,
0.35562554001808167,
0.54165977239608765,
}
,
CropActive = true,
CropRatio = -1,
}
,
このパラメータを使って台形を長方形に変形すれば良い・・・と分かったところで、実際にPythonのプログラムです。
DxOをパラメータ生成器として使って、DxOが書き出してくれたパラメータを流用するという"コバンザメ戦略"で行きます。
自作で画像エディタみたいなものを作るのが面倒臭いし、「どこをどう写像する」の情報が欲しいだけなので。
とはいえ、撮影の度、カメラの画角も対象物との図形的な関係も毎回変わってくるので、ここはプログラム内に固定値ではなく、外部jsonから供給します。
keystoning.json
{
"mode": "parallel",
"keystoning_points": [
[0.42649209499359131,0.13081993162631989],
[0.60012799501419067, 0.13193754851818085],
[0.64911401271820068,0.65973210334777832],
[0.37617066502571106,0.65972703695297241]
],
"crop_rect": [0.3010, 0.0885, 0.4106, 0.5733],
"output_size": [800, 600]
}
・機能
指定したディレクトリのjpegを読み込む load_images
パース変換 keystoning_from_json
不要削除 trim_bottom
・変換処理の詳細
# 補正対象の台形4点(元画像座標系)
# 台形の寸法とスケール調整
# 元画像の四隅を変形後の座標に変換
# 平行移動を加えて左上が (0, 0) になるよう補正
# 補正画像を生成
# 台形の中心を補正後座標系で取得
アフィン変換で画像全体を中央に移動
# ✅ ステップ1:縦方向にスケーリング(器の寸法を再構成)
# 台形の底辺と高さを補正後+平行移動後の座標で取得
# スケーリング後の座標に合わせて変換
# 下端から上に向かって、黒以外のピクセルが現れる位置を探す
# 黒い余白を切り落とす
・keystoning.pyの使い方
私がQGISプラグインのビューア表示時に使用している例です。
サンプルとしてkeystoning.pyとkeystoning.jsonを添付しますが、余分なメソッドや作りかけの部品(残骸)も多く残っています。
ここで、frameというのは、動画をOpenCVで開いてキャプチャした静止画です。
宣言部
from . import keystoning as ks
処理部
# 1. 映像取得
index = max(0, min(index, self.total_frames - 1))
self.current_index = index
print(f"🎯 cap.set → index={index}")
self.cap.set(cv2.CAP_PROP_POS_FRAMES, index)
ret, frame = self.cap.read()
print(f"📽️ cap.read → ret={ret}, frame={'OK' if frame is not None else 'None'}")
if not ret or frame is None:
self.image_label.setText("❌ フレーム取得失敗")
return
target_width = frame.shape[1]
try:
keystoning =ks.keystoning_from_json(index,frame, "keystoning")
angle = ks.detect_rail_angle_with_pairing(keystoning)
if (angle is not None) and (80 <= angle <= 100) and (abs(90-angle) >= 1.0):
print(f"🚃 レールの角度を検出しました: {angle:.2f}度、垂直に補正します")
keystoning = ks.rotate_and_crop(keystoning, angle,target_width) # ← 回転によって生じる黒い部分をトリミングする
frame= ks.trim_bottom(keystoning,0.01)
except Exception as e:
print("幾何変形失敗:",e)
・keystoning.pyの処理時間
2998ファイル@8フォルダ 1.05GB 11分 →4.5fps
0045_45000-45999_keystoning 343ファイル 1分
0046_46000-46999_keystoning 579ファイル 2分
0047_47000-47999_keystoning 460ファイル 2分
0048_48000-48999_keystoning 249ファイル 1分
0049_49000-49999_keystoning 391ファイル 2分
0050_50000-50999_keystoning 256ファイル 1分
0051_51000-51999_keystoning 350ファイル 1分
0052_52000-52999_keystoning 362ファイル 1分
※計測はExplorerのタイムスタンプ(分)での計測なので正確さは無い!
▼この記事を書いたひと

R&Dセンター 松井 良行
R&Dセンター 技術戦略担当部長。コンピュータと共に35年。そしてこれからも!
●富士見事務所 TEL : 052-228-8733 FAX : 052-323-3337
〒460-0014 愛知県名古屋市中区富士見町13−22 ファミール富士見711 地図
交通部 R&Dセンター


