abc760で使ったカメラ台はカメラが前方に飛び出し、しっかりと固定しないと不安定である。そこで、フリーのstlデータを参考に形状を修正し、新たに3Dプリンタを使ってカメラ台を作成した。サーボモータ(SG-90)の取付にはサーボ付属のホーンを加工無く使う構造とした。
カメラ台用stlデータ(camera-sg90.zip)
![]() |
![]() |
![]() |
| 作成したパーツ | サーボにホーンを取付 | パーツを組上げネジで固定 |
画像処理が行えるOpenCV(CompuerVision)を使って、ラズパイカメラで取り込んだ画像の中で、人の顔の検出を行い、カメラの中心になるようにモータを制御する。
書籍「実例で学ぶRaspberryPi電子工作」(講談社)を参考にした。
![]() |
![]() |
![]() |
| 画像処理前 | 2値化 | エッジ検出 |
![]() |
![]() |
![]() |
| 原画(Lena)512x512 | 円検出(ライト、赤丸) | 顔検出(赤角) |
顔認識&サーボ制御 <tracking-face.py>
# -*- coding: utf-8 -*-
import picamera
import picamera.array
import cv2
import math
import pigpio
cascade_path = "/usr/share/opencv/haarcascades/haarcascade_frontalface_alt.xml"
cascade = cv2.CascadeClassifier(cascade_path)
pi = pigpio.pi()
# === サーボ用定数
Pan = 17 # 左右サーボ接続ピン
Tlt = 18 # 上下サーボ接続ピン
Pan_hm = 1500
Tlt_hm = 1500
Pan_max = 2000 # Pan動作範囲
Pan_min = 1000
Tlt_max = 1700 # Tilt動作範囲
Tlt_min = 1200
# === 画像用定数
Width_x = 320 # 画像の横画素数
Width_y = 240 # 画像の縦画素数
Cent_x = Width_x / 2
Cent_y = Width_y / 2
Ratio_x = 1.0 # 画素数とサーボ移動量の比
Ratio_y = 1.0
# === 初期化
pan_pos = Pan_hm # サーボを初期位置に
tlt_pos = Tlt_hm
pi.set_servo_pulsewidth(Pan, pan_pos)
pi.set_servo_pulsewidth(Tlt, tlt_pos)
with picamera.PiCamera() as camera:
with picamera.array.PiRGBArray(camera) as stream:
camera.resolution = (Width_x, Width_y)
while True:
camera.capture(stream, 'bgr', use_video_port = True) # stream.arrayにBGRの順で映像データを格納
gray = cv2.cvtColor(stream.array, cv2.COLOR_BGR2GRAY) # 映像データをグレースケール画像grayに変換
# === grayから顔を探す
facerect = cascade.detectMultiScale(gray, scaleFactor = 1.3, minNeighbors = 2, minSize = (30,30), maxSize = (150,150))
if len(facerect) > 0: # 顔が見つかったら、複数見つかった顔のうち、画像中心に最も近いものを探す
mindist = Width_x+Width_y
minindx = 0
indx = 0
for rect in facerect:
dist = math.fabs(rect[0] + rect[2]/2 - Cent_x) + math.fabs(rect[1] + rect[3]/2 - Cent_y)
if dist < mindist: # より近距離の顔が見つかった?
mindist = dist
minindx = indx
indx += 1
# === 現在の顔の位置(枠の中心)
face_x = facerect[minindx][0] + facerect[minindx][2]/2
face_y = facerect[minindx][1] + facerect[minindx][3]/2
# === 元の画像(system.array)上の、顔がある位置に赤い四角を描画
cv2.rectangle(stream.array, tuple(facerect[minindx][0:2]), tuple(facerect[minindx][0:2]+facerect[minindx][2:4]), (0,0,255), thickness=2)
dx = int(((Cent_x - face_x) * Ratio_x)/10)*10 # 左右中央からのずれ。(移動方向が逆なら項を入れ替)※
dy = int(((face_y - Cent_y) * Ratio_y)/10)*10 # 上下中央からのずれ。(移動方向が逆なら項を入れ替)※
# === サーボモーターの移動許容範囲でモータを回転
if Pan_min <= pan_pos + dx <= Pan_max:
pan_pos += dx
pi.set_servo_pulsewidth(Pan, pan_pos)
if Tlt_min <= tlt_pos + dy <= Tlt_max:
tlt_pos += dy
pi.set_servo_pulsewidth(Tlt, tlt_pos)
# === system.array(画像)をウインドウに表示
cv2.imshow('frame', stream.array)
# === "q"を入力でアプリケーション終了
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# === streamをリセット
stream.seek(0)
stream.truncate()
cv2.destroyAllWindows() # 顔認識スケッチを終了
|
![]() |
| 顔認識テスト中 |
![]() |
||
![]() |
![]() |
![]() |
| 車工具・オーニングハンドル | 外部電源ケーブル・工具 | 折り畳みテーブル |
![]() |
![]() |
![]() |
| 炊飯器・紙食器・・ | キッチン用品・非常食・・ | 飲料水・歯磨き用品 |
![]() |
![]() |
![]() |
| 電子レンジ | マルチシェード(百均収納袋) | 湯沸かし・コーヒー・紙コップ |
![]() |
![]() ダッシュ照明 |
![]() |
| 昼 | 夜(LEDライト点灯) | |
![]() |
![]() 収納ラック |
![]() |
| 改造前(網) | 改造後(縦・横書類ケース) | |
![]() |
![]() |
![]() |