[TBU 更新予定] RGBDT Sensor Fusion by Deep Learning (P2)


RealSenseとSeekThermalをセンサーフュージョンする

この動画で紹介したセンサフュージョンについてです。

センサフュージョンはRGB画像の画素とThermal画像の画素をDepth値も考慮して対応付けるニューラルネットワークで実行しました。入力に $(x_{rgbd}, y_{rgbd}, d_{rgbd})$, 出力に $(x, y)$ となるようにします。
Thermal画像をRGBDに合わせるのに、入出力が逆では?と思われるかもしれませんが、対応関係が分かればnumpyの配列として保持できるのでどっちをどっちに写像することもできます。詳しい解説はまた記事を更新したときにでも書こうかなと思っています。
このニューラルネットワークを学習する際に使うデータセットはThermal画素の一点一点を取得する装置を用いて得ました。かなり時間がかかりますが、自動化してしまえば寝ている間にセンサフュージョンしてくれます。

装置

上の図のような装置を作成し、RGBDとThermal画素の対応点を取得しました。センサの情報についてはこちらにまとめの画像を掲載しますが、前回の記事(P1)で導入について軽く説明しました。

Thermal画素の座標を取得するには、以下のような発熱するカイロの中身を二次元コード付きの小さな箱(サーマルボックス)を自作して使います。二次元コード認識にはOpenCVのARUcoを用いて、RGBDの座標を取得するのに使います。

import cv2
aruco = cv2.aruco
p_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_250)# -----ARUCO-----
#corners, ids, rejectedImgPoints = aruco.detectMarkers(image_out, p_dict)
corners, ids, rejectedImgPoints = aruco.detectMarkers(cv_img, p_dict)
image_out_marked = aruco.drawDetectedMarkers(cv_img.copy(), corners, ids) 

周囲よりサーマルボックスの温度が高ければSeekThermalにおける座標を特定できます(二値化して白領域の重心を座標にする)。

# --- Calculate Thermal COG ---
thm_gray = cv2.cvtColor(thermal_color_img.copy(), cv2.COLOR_BGR2GRAY)# Grayscale thermal image
ret, img_thm_bi = cv2.threshold(thm_gray,160,255,cv2.THRESH_BINARY)  # Heat marker
#ret, img_thm_bi = cv2.threshold(thm_gray,80,255,cv2.THRESH_BINARY)  # Cool marker
#kernel = np.ones((5, 5), np.uint8)
#img_thm_bi = cv2.erode(img_thm_bi, kernel, iterations=3)
#img_thm_bi = cv2.bitwise_not(img_thm_bi)  # Cool marker
ksize=7
img_thm_bi = cv2.medianBlur(img_thm_bi,ksize)
points_thm = np.array(img_thm_bi)
points_xy_thm = np.where( points_thm == 255 )

# What to do if the heat becomes uniform or something strange gets in
# Do not get coordinates if the heat source is too widespread
if len(points_xy_thm[1])>0 and len(points_xy_thm[0])>0 :
    thm_x_max = max(points_xy_thm[1])
    thm_x_min = min(points_xy_thm[1])
    thm_y_max = max(points_xy_thm[0])
    thm_y_min = min(points_xy_thm[0])
    if abs(thm_x_max-thm_x_min) < w_thm/5 and abs(thm_y_max-thm_y_min) < h_thm/5:
        mu2 = cv2.moments(points_thm, False)
        cog_x,cog_y = int(mu2["m10"]/mu2["m00"]) , int(mu2["m01"]/mu2["m00"])# Thermal (x, y)

2次元コードを自作のRGBDTセンサで撮影し、RGBDとThermalの両方の位置を全画素にわたって取得します。この時、Depthもあるレンジで区切って三脚で高さを変えながら撮影します。

ネットワーク構造

RealSenseのRGBDデータの解像度は640x480, SeekThermalのThermalデータの解像度は206x156です。センサフュージョン後の解像度は453x333です。SeekThermalの何画素かがRGBDの1つの画素に対応するため、SeekThermal解像度より大きくなるのだと思います(多分)。そう考えるとSeekThermalのレンズの絞りを変えるとこの解像度も変わると思います(僕はガムテープで画角マックスで固定しています)。

すべてのRGBD-Thermal対応点の総数は62万組ほどです。これをcsvに $[x_{rgbd}, y_{rgbd}, d_{rgbd}, x_{thermal}, y_{thermal}]$のような形で62万行ほど書き込み、データセットとしました(約12MB)。
学習は600epochsくらいで、1時間くらいで終わったように記憶しています(いろいろ試しすぎてわけわからなくなりました、結構失敗してて絶望していたので)。

結果

こんな感じのThermal画像が

こんな感じに変換されて

こういう風にRGBDと画素合わせできました。

RGB Thermal-corrected RGB-Thermal

OpenCVで合わせた場合と比較すると僕の手法がよく合っているのが分かると思います。OpenCVでも距離に応じて画素合わせを手作業でやっていけばそれなりにセンサフュージョンできると思いますが、たぶん僕の手法よりも大変で時間かかる上に精度が作業クオリティに依存してしまうという問題点はあると思います。

最後に

とはいえ僕の手法が簡単で高精度、というわけではありません。とりあえずニューラルネット使いたかったので、自分の思ったことは思った通りにできたと思います。何かいい方法があればぜひ教えてください。
これをもとに作成したデータセットは以下で配布しています。よければ見ていってください。何か需要のありそうなアイデアがあればTwitterなどで教えてください。
https://robotaim.booth.pm/

本記事の著者

moriitkys 森井隆禎
ロボットを作ります。
AI・Robotics・3DGraphicsに興味があります。最近はいかにしてお金を稼ぐかを考え、そのお金でハードをそろえようと企んでいます。
資格・認定:G検定、Pythonエンジニア認定データ分析試験、AI実装検定A級、TOEIC:810(2019/01/13)