レンズの歪曲収差と対応方法

歪曲収差とは

歪曲収差 (distortion) は、球面収差非点収差コマ収差像面湾曲と並んでザイデル収差の一つで[1][2]、典型的なあらわれかたとしては、撮像面(イメージセンサ等)に並行な被写体面のテストパターン等の矩形が矩形として撮影されない、あるいは同じ光学系を逆方向に使い投影した時にテストパターン等の矩形が矩形として投影されない、といったような収差となる[2]

歪曲収差

レンズの種類と画角の違い

一般的にはあまり馴染みのない話ですが、カメラに取り付けるレンズにはその目的・用途によって様々な種類があります。
一般的にはより広い範囲を写す「広角レンズ」、より遠くを写す「望遠レンズ」、中間の「標準レンズ」です。

顔による認証処理、いわゆる顔認証では「広角レンズ」によって生まれる「歪曲収差」が問題になります。
ウェブカメラやスマホの自画撮り用内面カメラはより広い範囲を写すという目的があり、そのため広角レンズを採用している事と比較的カメラに近づいている状態で撮影されることによって歪曲収差が起きやすくなっています。(レンズの種類だけでなく焦点距離も重要。非常に性能の高い一眼レフやスマートフォンではチップ内で補正をかけ収差を補正している機種もあります)

左がオリジナル、右が歪曲収差を起こした図
左がオリジナル、右が歪曲収差を起こした図

同じ人物を写した場合でも、標準レンズと広角レンズではこのような歪曲収差が生まれます。
顔認証における処理では「顔の各パーツ間の距離」を測定しているので、違うレンズで写した顔は Face distance ( 顔距離 ) が大きくなる傾向があり、しばしば認証率の低下を引き起こします。

補正のやり方

通常の顔認証では「顔を登録するカメラ」と「顔を認証するカメラ」が同じ場合があります。例えばドア横に設置してあるような小型の認証機器がこれにあたります。この場合は歪曲収差が起きていても「登録時」と「認証時」に同じ収差が起きるため認証率に影響を与えません。

認証用カメラのキャリブレーション

理由があって「顔を登録するカメラ」と「顔を認証するカメラ」が違う場合、例えば登録顔画像は社員証の写真を使い認証時にはノートパソコンの内蔵カメラを使うなどと言った場合には歪曲収差によって認証率が非常に落ちます。

こういった時には歪みを補正する「キャリブレーション」を行い補正数値を割り出します。

一般的なカメラで補正機能を持っていない場合、特に RAW 現像段階で LensFun のデータベースからレンズデータを引っ張ってきて補正を行います。サポートされているレンズは膨大ですが一眼レフなどに使用されるものが多く、勿論ノートパソコンの内蔵カメラや USB カメラの補正用データはありません。

必要なもの

A4 用紙に印刷したチェッカーボードを硬いしっかりした板状のものに貼り付けて使用します。私は小さな黒板が手元にありましたので貼り付けて使用しました。

自作のチェッカーボード
自作のチェッカーボード

これをノートパソコンの内蔵カメラで撮影します。なるべく顔のある位置にカメラに対して水平・垂直に撮影するようにします。

余談ですが、カメラと顔までの距離は非常に重要です。
広角レンズではカメラに近いほど歪曲収差が出やすく、逆に離れると歪曲収差は目立たなくなります。ですのでいつも座っている姿勢で顔があるはずの位置にチェッカーボードをかざします。
例えば中腰で後ろから覗き込むような姿勢でカメラに映るとレンズと顔までの距離が遠いため歪曲収差の値が変わってきてしまいます。
これは非常に重要ですのでお気に留めて頂くと幸いです。

チェッカーボードを撮影している様子
チェッカーボードを撮影している様子

似たような方法に openCV を使った本格的なキャリブレーション方法もありますが、チェッカーボードをいろいろな角度で 20 枚以上撮影しなければならないため、またそこまで本格的な補正数値は(今の所)必要ないため簡易的な方法を取ります。また今回は ImageMagick ver.6.x を使用することを前提にしています。ver.7.x からはコマンドの書式が変わりますので注意してください。スクリプトは Python 3.x ですが内部で ImageMagick を呼び出します。

傾きを補正する

なるべく水平に垂直に…と思っていても補正に使うレベルにはなかなかなりません。まず取得したチェッカーボードの傾き補正を行います。

# coding: utf-8
import decimal as d
import subprocess


def barrel_value_cmd( image, value ):
	# ~ print(value); exit();
	cmd = "convert {} ".format(image)
	rotate_value = "-rotate '{}'".format(value) 
	output_image = " rotate_{}.png".format(value)
	
	cmd = cmd + rotate_value + ' line.png -gravity center -composite' + output_image
	# ~ print(cmd); exit();  

	res = subprocess.run(cmd, shell=True)
	print(res)


if __name__ == '__main__':
	
	value_C_list = []
	num = d.Decimal('-3.0')
	for i in range( 60 ):
		num += d.Decimal('0.1')
		value_C_list.append(float(num))
		# ~ print(value_C_list)
	
	for value in value_C_list:
		# ~ print(value); continue; # ok
		barrel_value_cmd( 'original.png', value )

gif アニメーションは以下のようにします。
(何をやっているのか分かりやすくする為に gif アニメーションにしています。実際の現場では必要ありません。)

rotate_gif$ convert -delay 10 -loop 0 -resize 50% rotate*.png rotate.gif

rotate の値は -1.5 であることが分かりました。

歪曲収差の補正

# coding: utf-8
import decimal as d
import subprocess


def barrel_value_cmd( image, value ):
	# ~ print(value); exit();
	cmd = "convert {} ".format(image) + rotate
	barrel_value = " -distort barrel '0.0 0.0 {}'".format(value)  
	output_image = " barrel_{}.png".format(value)
	
	cmd = cmd + barrel_value + ' line.png -gravity center -composite' + output_image

	res = subprocess.run(cmd, shell=True)
	print(res)


if __name__ == '__main__':
	
	rotate = ' -rotate -1.5'
	
	value_C_list = []
	num = d.Decimal('0.00')
	for i in range( 20 ):
		num -= d.Decimal('0.005')
		value_C_list.append(float(num))
		# ~ print(value_C_list)
	
	for value in value_C_list:
		# ~ print(value); continue; # ok
		barrel_value_cmd( 'barrel.png', value )

ImageMagick の引数は -distort barrel ‘0.0 0.0 -0.045’ であることが分かりました。

同じ様に歪曲収差がかかっている女性の写真を補正すると下の図のようになります。

歪曲収差の補正
補正後の写真と歪曲収差のない写真の比較
補正処理後の写真(左)と歪曲収差のない写真(右)の比較
補正値は ‘0.0 0.0 -0.325’

次に Face01_Imager_115 を用いて元のオリジナル顔画像ファイルと各補正値による補正処理をかけた顔画像ファイルとの face distance ( 赤枠 ) を比べてみました。(下図)

各補正処理後の顔画像ファイルと Face distance ( 赤枠 )
各補正処理後の顔画像ファイルと Face distance ( 赤枠 )

これによるとオリジナル画像同士の face distance は 0.13 、補正値が -0.325 と -0.30 の補正処理済み顔画像ファイルはそれぞれ face distance が 0.14 で差異が最小でした。

反対に最も face distance 値が大きかったのは補正値 -0.025 の処理済みファイルで face distance 値は 0.27 でした。補正値 -0.025 はこの中でもっとも補正値が小さい処理をしたファイルです。つまり歪曲収差がもっとも大きいファイルです。

オリジナルの face distance 0.13 からどれだけ距離が離れるかに注目すると、最も距離が短いもので 0.01 、最も距離の長いもので 0.14 でした。

この事は注目に値します。というのは現場の運用に於いてしきい値を決める際、0.4 にするのか 0.54 ( 0.4+0.14 ) にするのかの違いが出てしまうからです。カメラのキャリブレーション、つまり正しい補正値を算出することによって、そうしない時と比べて face distance 値が 0.14 の違いが出てきてしまう…。これは顔認証の対象となる人数が多くなればなるほど問題になってきます。

補正処理はチェッカーボードを一枚写して短いスクリプトをたった一度走らせるだけで済みますので、登録顔画像ファイルを作ったときと認証するときのカメラが違う場合には是非実行することをおすすめいたします。

Face01 の前処理として組み込む

本来であれば登録用顔写真ファイルを作成するときもこのようなキャリブレーションを行うといいのですが、そこまで求めることも出来ない場合もままあります。簡易的なキャリブレーションですが USB カメラやノートパソコンの内蔵カメラを認証時に使う場合であれば、顔認証処理の前段階に設置することで「一定の効果」を上げることが出来ます。

チェッカーボードの gif 画像では緑の基準線が引かれていますが、顔認証時は基準線の丸になるべく顔が収まるようにすると良いでしょう。歪曲収差を簡易的に補正しているのでエッジ部分(チェッカーボードの端)の湾曲が出てくるからです。

基本的に Face01 は「顔認証エンジンをご提供する」コンセプトですのでここで触れているようなカメラの管理機構を SDK へ組み込む予定はございません。しかしながら画像処理全体でカメラのキャリブレーションは非常に重要です。東海顔認証ではこれまでのように情報提供に努めてまいります。

まとめ

今回ではレンズによる歪曲収差とそれによる顔認証の認証率低下の理由、そしてその補正方法について解説いたしました。一般利用者方は全く意識する必要はありませんが機器の管理者の方は知っておくと役に立つと思います。レンズについては難しい用語が出てきますが慣れてしまえば何ということもありません。高性能スマホや高性能一眼レフでは内部で補正を行っているので気づきにくい裏舞台なのかもしれませんが、こうした舞台裏を知っているとカメラと顔の距離(焦点距離)も非常に重要だとお気づきになるかと思います。そのため、他の顔認証システムを提供なさっている企業様は半透明の鏡を使って目の位置や顔をおさめる丸印が描いてあったりします。またスマートフォンでは画面に直接写っている顔を映して枠内に顔が入るように誘導されます。これらは焦点距離を一定にする効果があります。つまり歪曲収差を意識してインターフェースを作成しているのです。(下図)

顔位置合わせ
【2020年】10社から比較した証明写真アプリ無料おすすめランキングTOP5 から引用

今回は少々むずかしい内容になりました。こうした収差はテレビに映る芸能人を顔認証のテストにしている場合はほとんど起こりません。これはプロのカメラマンが収差を起こさないようにしているからです。以前金正恩の影武者探しで Face01_Graphics を使い顔認証処理をしましたが、どの写真も収差は全く感じさせませんでした。(ここで言う収差とは歪曲収差も含め複数の収差を指します)彼には複数のプロのカメラマンがついていると聞いたことがありますが流石にプロなんだなぁ…と感心しました。

今回レンズについてお話をしましたが、読み返していたらお蔵入りしているソニーのミラーレス一眼を引っ張り出してきたくなりました。レンズ沼…という単語があるとおりこだわりだしたらキリがないのですがそこが楽しいところです。最近はスマートフォンのカメラしか使っていないのですが、さっと撮れるスナップの便利さは中々代えがたいものがあります。

今回は以上です。最後までお読みいただきありがとうございました。