こんにちは。デバイスソフトウエア開発部の斎藤です。
生産性向上エッジAIカメラシステム「PROLICA®」を用いた画像解析システムの開発を担当しています。
PROLICAは、ネットワークカメラとエッジコンピューターを使用してリアルタイムに物体検出を行うことを主な機能としていますが、AIとは独立した機能として、画像内の信号機(赤・青・黄)の状態を輝度差から判定する仕組みも備えています。
本記事では、指定した範囲の「輝度差」を算出して、信号機の状態を判定する機能を紹介します!
PROLICAの詳細や他の機能につきましては、以下の弊社HPや、これまで私が執筆した記事をご覧ください。
生産性向上AIカメラ「PROLICA®」
信号機状態解析機能の活用シーン
PROLICAでは、屋外での車両・人物検知をトリガーに、「指定したエリアに侵入したらパトランプを鳴らす」「大型車両が通過したら注意喚起メッセージを表示する」などのハードウェア制御を実行する案件が半数以上を占めています。
その中で、「青信号時に右車線へ車両が侵入したらパトランプを鳴動させる」のような、信号機の状態が絡む要件のプロジェクトも存在します。
こうした要件を満たすため、ピクセル単位の輝度差解析による信号機状態判定機能を開発し、各現場に導入してきました。
ディープラーニングに頼らないことの利点
昨今のディープラーニング技術の発展に伴い、「PROLICAはAIで物体を検出できるなら、AIで信号機の色を判定すればよいのでは?」と思う方もいるかもしれません。
しかし、ディープラーニングに頼らないことで、以下のような利点があります。
高速処理とリアルタイム性が高い
輝度差の計算は足し算・引き算程度の軽量な演算で済むため、リアルタイム性が重要となるエッジでの高速な処理が可能です。PROLICAのようなエッジAIカメラでは、CPU上でこの処理を行ってもごくわずかな負荷で済み、GPUリソースを別のタスクに回す余裕も生まれます。
判定ロジックが安定する
輝度差のようなルールベースの判定は、挙動が予測可能でデバッグしやすく、振る舞いを直感的に理解・変更できます。
一方、ディープラーニングモデルはブラックボックス的で、ある環境下でなぜ誤検知したのか原因追及が難しいことがあります。
本手法であれば「輝度値がこの範囲にあるから赤信号と判断している」のような基準があるため、想定外の事態にも対応しやすくなります。
エッジカメラのようにリソースに限りのあるシステムでは、AIは使うべきところで使い、そうでない部分はシンプルに済ませるというメリハリが重要です。
弊社のPROLICAでも、ディープラーニングによる車両検出と、今回紹介した信号色判定を同時に活用することで、「”信号が青” かつ “車両が存在しない” 時にイベントを飛ばす」などの複雑な条件下でも安定したリアルタイム処理を実現しています!
そもそも輝度差とは?
輝度差とは、画像中の2つの領域間における明るさの差のことです。
画像処理の世界では、RGBなどの色情報だけでなく、HSV の V 成分(輝度)を用いて明るさを数値化します。この明るさ(輝度)の数値を比較し、その差を調べることで、物体の有無や状態を判断することが可能になります。
信号機の場合、ランプが点灯するとその部分だけが明るくなります。点灯しているランプ部分の輝度と、点灯していないランプの輝度との差(=輝度差)を計算することで、どのランプが点灯しているかを高精度で判定することができるのです!
単純に「赤」「青」「黄」を色として見分けるのではなく、なぜ「輝度差」を用いるのか、そして実際にどのように輝度差を計算しているかを、次項以降で説明していきます。
色ではなく「輝度」を使う理由
画像から色を判定する場合、RGBのような「色相情報」を用いる方法が考えられます。
しかし、PROLICAでは、色ではなく輝度(明るさ)を使用して、信号機の状態を判定しています。色ではなく輝度差を使うのは、以下のような理由があります。
照明や天候などによる見え方の影響を低減できる
周囲の明るさや太陽光の向き次第では、カメラに映る信号機の色味が大きく変動することがあります。色そのものを閾値判定に使うと、こうした環境の変化で誤判定しやすくなります。
しかし、輝度差であれば、点灯しているランプ部分の明るさの変化として捉えられるため、周辺環境による色相の揺らぎに左右されにくいのです。
夜間のサチュレーションによる白色化が発生しても判定できる
夜間のように光量が不足する環境では、カメラのシャッタースピードを下げて十分な明るさを確保する必要があります。
しかし、シャッタースピードを遅くすると、信号機の明るいランプがカメラセンサーの表現可能な明るさの上限を超えてしまい、ピクセルが最大値(255)に飽和する「サチュレーション(飽和)」が発生し、映像上でランプ部分が白飛びしてしまいます。
こちらも色ではなく輝度差を用いることで、白飛びしても明るさの差分でどのランプが光っているかを判定できます。
以下は、サチュレーション発生時と通常状態の、実際の信号機の画像です。
どの画像も肉眼では赤青を判定できますが、サチュレーション発生時の画像はライト部分が白に近い色となっており、コンピューターが青や赤として認識できない可能性があることを何となく理解していただけるかと思います。
一文でまとめると、赤色や青色ではなく「より強く光っている方はどちらか」という判断基準の方が、様々な環境条件下でも判定しやすいため、輝度差を使用しているということです!
信号機画像で輝度差判定を検証
ここまででご説明した通り、「輝度差」を使うことで、サチュレーション(白飛び)が発生した状況でも正確に信号機の状態を判定できます。
検証用に、PROLICAのコードを簡略化した輝度差判定のサンプルスクリプトを作成してみました!
本来は黄色信号も判定していますが、今回は赤青の2パターンを判定するコードになっています。
サンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
import cv2 import argparse # 赤および緑ランプ領域のROI(ピクセル座標) ROI_STOP = {"top": 4, "bottom": 6, "left": 3, "right": 5} ROI_GO = {"top": 20, "bottom": 23, "left": 4, "right": 7} # 輝度差判定用の閾値(環境によって調整が必要) DIFF_THRESHOLD = 0 # 指定したROI内の平均輝度を計算 def calc_avg_luminance(img, roi): region = img[roi['top']:roi['bottom'], roi['left']:roi['right']] hsv = cv2.cvtColor(region, cv2.COLOR_BGR2HSV) return int(cv2.mean(hsv)[2]) # ROIを矩形で描画 def draw_rois(img): cv2.rectangle( img, (ROI_STOP['left'], ROI_STOP['top']), (ROI_STOP['right'], ROI_STOP['bottom']), (0, 0, 255), 2 ) cv2.rectangle( img, (ROI_GO['left'], ROI_GO['top']), (ROI_GO['right'], ROI_GO['bottom']), (0, 255, 0), 2 ) return img # 輝度差に基づいて信号機の状態を判定 def determine_signal_state(img): lum_stop = calc_avg_luminance(img, ROI_STOP) lum_go = calc_avg_luminance(img, ROI_GO) if (lum_go - lum_stop) > DIFF_THRESHOLD: return "Blue signal" elif (lum_stop - lum_go) > DIFF_THRESHOLD: return "Red signal" else: return "Undetermined" if __name__ == "__main__": parser = argparse.ArgumentParser(description="Traffic light state detection by luminance difference") parser.add_argument("image_path", help="Path to the input image file") args = parser.parse_args() image_path = args.image_path img = cv2.imread(image_path) # 輝度と信号機状態を計算 lum_stop = calc_avg_luminance(img, ROI_STOP) lum_go = calc_avg_luminance(img, ROI_GO) diff = lum_go - lum_stop state = determine_signal_state(img) print(f"Image path: {image_path}") print(f"Green signal luminance: {lum_go}") print(f"Red signal luminance: {lum_stop}") print(f"Difference: {diff}") print(f"Result: {state}") annotated = draw_rois(img) cv2.imwrite("annotated_signal.jpg", annotated) |
上記スクリプトを使用して、前項で紹介した、「サチュレーション発生時」と「通常時(サチュレーションなし)」の赤信号・青信号の画像4枚を解析すると、以下のような結果になりました。
実行結果を表にまとめると、以下のようになります。
コマンド実行結果(表)
画像名 | サチュレーション | 実際の信号状態 | 赤ランプ輝度 | 青ランプ輝度 | 輝度差 | 判定結果 |
---|---|---|---|---|---|---|
normal_blue.png | 無 | 青 | 63 | 230 | 167 | Blue signal |
normal_red.png | 無 | 赤 | 244 | 70 | -174 | Red signal |
saturation_blue.png | 有 | 青 | 93 | 252 | 159 | Blue signal |
saturation_red.png | 有 | 赤 | 228 | 111 | -117 | Red signal |
「実際の信号状態」と「判定結果」を見比べていただくと、通常状態(サチュレーション無し)・サチュレーション発生時共に、正確に信号機状態を判定できていることがわかります。
おわりに
今回は、PROLICAの信号状態判定機能について紹介しました。
画像認識や解析分野では、ディープラーニングの進化が著しい一方で、特定のタスクではAIよりもコードを書いた方が確実ということもまだまだあるので、今後もコーディング力向上や技術のキャッチアップを積極的にしていきたいです!
PROLICAについての疑問やご質問がありましたら、以下のリンクからお気軽にお問い合わせください。
最後に、エコモットではソフトウェアエンジニアを募集しています!
IoTやWeb開発に興味のある方はぜひ、弊社採用ページもご覧ください。