こんにちは。ピャペロンです。
今回は前回の温度を取得した続きから進めていくよ!
前回のリンク第29回 パペロで赤外線アレイセンサを動かしてみよう!【前編:簡易サーモグラフィを作ろう】
今回行うこと
赤外線アレイセンサを使い顔の表面温度を取得する
顔の検出
まずは顔の検出だよ!今回はOpenCVを使って顔の検出を行うよ!
OpenCVはオープンソースのコンピュータービジョンライブラリの一つで、画像の処理以外にも画像の解析や物体検出など様々なことができるよ!
OpenCVと言えば最近OpenCV AI Kitを発表して話題になったパぺ!
まずはさっくりと顔認識するプログラムを作ってみよう!以下のプログラムを実行してみよう!
#!/usr/bin/env python3 # sample30-1.py import numpy as np import cv2 as cv GREEN = (0, 255, 0) # B(青)・G(緑)・R(赤) WIDTH = 800 # 画面幅 HEIGHT = (int)(WIDTH*3/4) capture = cv.VideoCapture(0) # VideoCapture オブジェクトを取得する face_cascade_path = './haarcascade_frontalface_default.xml' # 学習データ face_cascade = cv.CascadeClassifier(face_cascade_path) # カスケード分類機 while(True): ret, frame = capture.read() # 画像キャプチャ frame = cv.resize(frame, (WIDTH, HEIGHT)) # 画像のリサイズ frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) # 画像を白黒化して顔識別を高速化 # 顔識別をする。最小サイズは画面の1/16以上 faces = face_cascade.detectMultiScale( frame_gray, scaleFactor=1.2, minNeighbors=2) # 見つかった顔の処理 for x, y, w, h in faces: # ターゲットマーカー cv.rectangle(frame, (x, y), (x+w, y+h), GREEN) cv.imshow('FACE CAPTURE TEST', frame) # Qを押された際にbreakする if cv.waitKey(1) & 0xFF == ord('q'): break capture.release() cv.destroyAllWindows() print("終了")
実行してカメラに顔を向けると顔に四角い枠が表示されるよ!
これで顔の認識はできるようになったね!次は温度の取得および補正をしてみよう!
温度の補正
上の内容で顔の位置がわかったので次は前回の内容と組み合わせて顔表面の温度を取得していこう!
測定値は冷風が当たる、近くに発熱減があるなどの外部の環境やセンサの個体差によって変わるから注意が必要!
ピャペロンの環境だとセンサから遠いと低めにばらつきが大きく、近いと高めに安定する傾向にあったよ。
先ほどの変動をふまえて今回の環境ではこのように温度補正をしたよ!
max_temp = max_temp / 4 - 0.01 * w + 12 # 顔の大きさが小さいほど(距離が遠いほど)補正を多く入れる
図分で動かしてみるときは例えば、体温計で測った値に近くになるように調整するなどしてみよう!
顔表面の温度取得
以下のプログラム含め下のように配置してあげるよ!
./ ├── haarcascade_frontalface_default.xml ├── hogehoge.ttf └── sample30-2.py
haarcascade_frontalface_default.xmlはGitHubのOpenCVプロジェクト内のhaarcascade_frontalface_default.xmlに、フォントは好みのものを選んでね!
#!/usr/bin/env python3 # sample30-2.py import numpy as np from PIL import ImageFont, ImageDraw, Image import cv2 as cv import time import smbus bus = smbus.SMBus(1) size = 8 # 赤外線アレイセンサの列数 addr = 0x69 # 取得アドレス 0x68と選択式 GREEN = (0, 255, 0) # B(青)・G(緑)・R(赤) RED = (0, 0, 255) SIZE = 8 # 一列のセンサー数 WIDTH = 800 # 画面幅 HEIGHT = (int)(WIDTH*3/4) capture = cv.VideoCapture(0) # VideoCapture オブジェクトを取得する fontpath = './hogehoge.ttf' # 任意のフォントの指定を行う font = ImageFont.truetype(fontpath, 24) # フォントサイズ24 small_font = ImageFont.truetype(fontpath, 12) # フォントサイズ12 face_cascade_path = './haarcascade_frontalface_default.xml' # 学習データ face_cascade = cv.CascadeClassifier(face_cascade_path) # カスケード分類機 fps = 0 # FPS初期値 def write_scale(array): # 文字系描写 draw.text(((int)(WIDTH/2-120), 0), 'PRESS "Q" TO EXIT', font=font, fill=GREEN) # 終了方法を表示 draw.line(((0, 120), (fps, 120)), fill=RED, width=5) draw.text((0, 80), "FPS:"+str(fps), font=font, fill=GREEN) # FPSを表示 draw.line(((0, 160), (len(faces)*10, 160)), fill=RED, width=5) draw.text((0, 120), "PCS:"+str(len(faces)), font=font, fill=GREEN) # 対象数を表示 for i in range(SIZE): for j in range(SIZE): draw.text(((int)(WIDTH/SIZE*j+WIDTH/16), (int)(HEIGHT/SIZE*i+HEIGHT/16)), "%2.2f" % (array[i][j]/4), font=small_font, fill=GREEN) # 温度表示 def readdata(): linedata = [] for i in range(8): data = bus.read_i2c_block_data(addr, 0x80+0x10*i, 16) oneline = [] for j in range(8): oneline.append((int((data[2*j+1] & 0x07) * 256 + data[2*j]))) linedata.append(oneline) return(linedata) # センサー安定のため30秒暖機運転 for wakeuptime in range(30, 0, -1): readdata() print("\r残り"+"%02d秒" % wakeuptime, end="") time.sleep(1) print("\r****起動****") while(True): face_no = 0 start = time.time() # 開始時間 linedata = readdata() # Grid-Eyeから温度情報を取得 temp_array = np.asarray(linedata) # listをnumpyarrayに ret, frame = capture.read() # 画像キャプチャ frame = cv.resize(frame, (WIDTH, HEIGHT)) # 画像のリサイズ frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY) # 画像を白黒化して顔識別を高速化 # 顔識別をする。最小サイズは画面の1/16以上 faces = face_cascade.detectMultiScale( frame_gray, scaleFactor=1.2, minNeighbors=2, minSize=((int)(WIDTH/4), (int)(HEIGHT/4))) img_pil = Image.fromarray(frame) # 配列の各値を8bit整数型(0~255)をPIL Imageに変換 draw = ImageDraw.Draw(img_pil) # drawインスタンスを生成 write_scale(temp_array) # 見つかった顔の処理 for x, y, w, h in faces: # ターゲットマーカー draw.rectangle([x, y, x+180, y-30], GREEN, width=3) draw.text((x, y-30), "W:"+str(w)+" H:" + str(h), font=font, fill=RED) # 座標表示 # 枠の表示 draw.rectangle([x, y+h/3, x+w, y+h-h/3], outline=GREEN) draw.rectangle([x+w/3, y, x+w-w/3, y+h], outline=GREEN) draw.rectangle([x, y, x+w, y+h], outline=RED, width=3) draw.line(((x+w/2, y), (x, y+h/2), (x+w/2, y+h), (x+w, y+h/2), (x+w/2, y)), fill=RED) # 識別順表示 draw.text((x+w/2, y+h), str(face_no+1), font=font, fill=GREEN) max_temp = 0 # 検出した最高温度表示 # 検出範囲の温度マップ表示 for i in range((int)(size*x/WIDTH), (int)(size*(x+w)/WIDTH+0.5)): for j in range((int)(y/HEIGHT*size), (int)((y+h)/HEIGHT*size+1)): draw.text([(int)(WIDTH/size*i+WIDTH/16), (int)(HEIGHT/size*j+HEIGHT/16)], "%2.2f" % (temp_array[j][i]/4), font=small_font, fill=RED) if max_temp < temp_array[j][i]: max_temp = temp_array[j][i] # 測定データから温度の値に変え補正値を追加(各自調整お願いします) max_temp = max_temp / 4 - 0.01 * w + 12. # 温度を表示 draw.text((x+w/2+20, y+h), str("%2.1f℃" % (max_temp)), font=font, fill=GREEN) # 下部に検出した顔一覧を表示 face = frame[y: y + h, x: x + w] face_pil = Image.fromarray(face).resize((50, 50)) img_pil.paste(face_pil, ((0+face_no % 2*50), (200+(face_no >> 1)*70))) draw.text(((15+face_no % 2*50), (245+(face_no >> 1)*70)), str(face_no+1), font=font, fill=GREEN) face_no = face_no + 1 frame = np.array(img_pil) # imgpilを画像にする cv.imshow('FACE CAPTURE TEST', frame) # Qを押された際にbreakする if cv.waitKey(1) & 0xFF == ord('q'): break # 開始時間から現在の時間をもとにFPSを算出する(0除算対策に1e-308秒加算済) fps = (int)(1/(time.time() - start + 1e-308)) capture.release() cv.destroyAllWindows() print("終了")
これを実行すると顔の下に温度が表示されるよ!
次回はこれらの取得できたデータを元にパペロと連携させていくよ!