先日、M5stackのunitv2で遠隔操作できるネットワークカメラを作成いたしました。↓
これを応用して、動体検知などでカメラが追従する仕組みを作ろうと思い立ったのですが、unitv2を使うとなればuartで他のデバイスにカメラ軸などの値を転送しなければならない為、個人的には敷居が高く、断念いたしました。
そこで、代わりにRaspberry piを使う事にしました。i2cが使えますので、直でサーボモータードライバを動かす事ができます。つまり動体検知のプログラムとサーボモーター駆動のプログラムを1つにまとめて書くことが出来ます。
動体検知のプログラムには、「Open CV」の」ライブラリを使用しました。カラートラッキング、顔認証、動体検知、などなどカメラ画像処理に長けた有名なライブラリですね。Pythonでも使えるので、Pythonで作成する事にしました。
Open CVは初めて触る事になるのですが、やってみた感想は、「便利な反面初学者には少々理解するのに時間がかかりそう」という感じです。ただ、ググれば先人方のフォーマットがたくさんヒットしますので、とりあえずそれを参考にすればなんとか動作させる事ができる事も分かりました。
という事で、今回は先人の知恵を借りながら、「Raspberry Pi で作るOpen CVを使ったカラートラッキング追従カメラ」についてまとめたいと思います。とりあず、カラートラッキングから。
初めに
Open CVのインストール
はじめに、ラズパイ(今回はRaspberry Pi4を使いました)にOpen CVをインストールします。
1 |
sudo pip3 install opencv-python==4.5.1.48 |
最新のものだとエラーが出たので、バージョンを指定しています。
続いてパッケージリストを最新にして、「libatlas3-base」 パッケージをインストールします。
1 |
sudo apt-get install libatlas3-base |
以上でOpenCVのインストールは完了です。
ターミナルでpythonインタプリタを立ち上げ、import cv2 でエラーが出なければOKです。
システムの概要
おおまかな動作の流れとしては、下記のような感じのものを作ります。
raspberry pi4 ⇒ i2c ⇒ pca9685 ⇒ サーボモーター
pythonプログラムを作成し、カメラ画像に合わせたカメラ軸の位置をOpen CVとPCA96852ライブラリで決めてサーボモータドライバへ伝え、サーボモーターを駆動させます。
ちなみにカメラは、ラズパイ純正のカメラモジュールでも、USBカメラでも両方OKです。
Open CVについて
土台となるpythonの記述について
pythonの場合の基本的な記述を載せておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import cv2 import math import numpy as np cv2.namedWindow('src') cv2.namedWindow('dst') cap = cv2.VideoCapture(0) while True: ret, img_src = cap.read() #カメラ画像の読み込み #ここに核となる処置を記述する #例えば垂直反転 img_dst = cv2.flip(img_src, flipCode = 0) cv2.imshow('src', img_src) #入力画像を表示 cv2.imshow('dst', img_dst) #出力画像を表示 ch = cv2.waitKey(1) #キー入力待ち if ch == ord('q'): break cap.release() cv2.destroyAllWindows() |
上記のプログラムでカメラのストリーミングが開始しますので、これが基本的な土台となります。後は間にある部分に行わせたい画像処理のコードを挿入していく形になります。
それでは続いて、カラートラッキング+カメラ追従のプログラムを書きます。
プログラム
Python
以下のプログラムは、以下のサイトを参考にさせていただきました。
pigpioの使い方になれていなかったので、Adafruit_PCA9685に変更しています。
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
import numpy as np import cv2 import time import Adafruit_PCA9685 import math pwm = Adafruit_PCA9685.PCA9685() pwm.set_pwm_freq(60) def move(x_move, y_move): pwm.set_pwm(0, 0, x_move) pwm.set_pwm(1, 0, y_move) #X,Yのサーボで稼働させる範囲。 X_MIN = 150 X_HOME = 375 X_MAX = 600 Y_MIN = 150 Y_HOME = 375 Y_MAX = 600 #カメラの位置初期化 move(X_HOME, Y_HOME) #カメラ準備 cap = cv2.VideoCapture(0) # デフォルト画面サイズ横幅 W = cap.get(cv2.CAP_PROP_FRAME_WIDTH) # デフォルト画面サイズ縦、高さ H = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) # デフォルトfps fps = cap.get(cv2.CAP_PROP_FPS) #デフォルトの画面サイズとfpsを確認 #print('W,H,fps: ', W , H , fps) #メモ W,H,fps: 640.0 480.0 30.0 #画面サイズ横幅 cap.set(3, 320) #画面サイズ縦、高さ cap.set(4, 320) #fps cap.set(5, 15) #白色 color = (255, 255, 255) time.sleep(1) # 追跡する枠の座標とサイズ、320*320の真ん中なら110,110から100ずつ x, y, w, h = 110, 110, 100, 100 #はじめに指定した枠 track_window = (x, y, w, h) # フレームの取得 ret,frame = cap.read() # 追跡する枠を決定 roi = frame[y:y+h, x:x+w] #①test1画面箇所。初期取得全体画面 cv2.imshow('test2', frame) #②test2画面箇所。追跡する枠 cv2.imshow('test1', roi) #追跡する枠の内部を切り抜いてBGR→HSV変換 HSVだと操作しやすい hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) #③test3画面箇所。追跡する枠のHSV変換後 cv2.imshow('test3', hsv_roi) #マスク画像の作成。暗い光源化での追跡失敗を防ぐため、暗い画素値を無視する処理 img_mask = cv2.inRange(hsv_roi, np.array((0., 60.,32.)), np.array((180.,255.,255.))) #④test4画面箇所。追跡する枠のマスク後 cv2.imshow('test4', img_mask) #確認のための待ち。 cv2.waitKey(0) # 正規化するためのヒストグラムの生成 roi_hist = cv2.calcHist([hsv_roi], [0], img_mask, [180], [0,180]) #試したい奴2 #roi_hist = cv2.calcHist([roi], [0], img_mask, [180], [0,180]) # ノルム正規化 cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX) # Setup the termination criteria, either 10 iteration or move by atleast 1 pt term_crit = ( cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 1 ) #初期位置 now_degree_x, now_degree_y, move_degree_x, move_degree_y = X_HOME, Y_HOME, 0, 0 while(True): #画面取得 ret, frame = cap.read() if ret == True: #フレームをHSV変換する。BGR→HSV変換 HSVだと操作しやすい hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) #上で計算したヒストグラムを特徴量として、画像の類似度を求める dst = cv2.calcBackProject([hsv],[0],roi_hist,[0,180], 1) #物体検出する ret, track_window = cv2.meanShift(dst, track_window, term_crit) #物体検出で取得した座標を元のフレームで囲う x,y,w,h = track_window #物体検出した枠を長方形で囲む?色は白色 img_dst = cv2.rectangle(frame, (x,y), (x+w, y+h), (255,255,255), 3) #ここら辺までは画面サイズに従ってそう。 img_x = int(x+w/2) img_y = int(y+h/2) #ここから動かすところ。320/2で160。0.1箇所を大きくするとカメラが大きく 動く move_degree_x = now_degree_x - (img_x-160)*0.1 move_degree_y = now_degree_y + (img_y-160)*0.1 move_degree_x = math.floor(move_degree_x) move_degree_y = math.floor(move_degree_y) #実際にカメラを動かすところ move(move_degree_x, move_degree_y) now_degree_x = move_degree_x now_degree_y = move_degree_y #物体検出した中心点を丸で表す?色は緑色 cv2.circle(img_dst, (img_x, img_y), 10, (0,180,0), -1) #画面への描写 cv2.imshow('SHOW MEANSHIFT IMAGE', img_dst) k = cv2.waitKey(1) if k == ord('q'): break else: break cap.release() cv2.destroyAllWindows() |
プログラムの説明につきましては、上記サイト管理者様がコメントアウトにて丁寧に説明を入れてくれてますので、非常に分かりやすいです。
プログラム起動後、初めに画面中央に映った物体を色認証します。4つの小さい画面が起動するので、それを閉じれば、認証した色の物体を追従するようになります。
動作確認
こんな感じで動いてくれます。↓
次回は顔認証、顔認証追従に挑戦したいと思います。
こんにちは。
ロボティクスの大学生です。
いつも拝見させていただいております。
早速ですが、「Raspberry Pi で作るOpen CVを使ったカラートラッキング追従カメラ」の内容について質問がござます。
この記事では、画像処理と同時にサーボモータも制御しているかと思いますが、同プログラムでは動かせるサーボモータは2個に限るのでしょうか?
現在、実験でロボットアームを使用していて、この記事を参考にして作動させたのですが、実際サーボモータ3つ以上では動きません。
どこかプログラムを書き換える必要があるのか、あるいはそのままでも動かすことができるのでしょうか?
書き換える場合は、内容を教えていただけると幸いです。
よろしくお願いいたします。
Itakuraさま
はじめまして、管理人です。
ロボティクスの大学生からコメントを頂けるなんてとても光栄です!!(^▽^)
と同時にとても緊張しております(;^ω^)
恐らく、いや確実に私の方が教えを乞う立場で恐縮なのですが、一応ご質問の内容に関しまして分かる範囲で回答させて頂きます。
サーボモータを増やすには、以下のプログラム追加が必要かと思われます。
・関数moveに追加したチャンネルのサーボモータを記述 pwm.set_pwm(チャンネル番号, パルスのスタート, パルスの終了)
関数moveには引数にx,y軸の移動距離を渡しているので、あらたな方向の値、例えばZ軸を追加したいとすれば、
コメントアウト部のここから動かすところ、及び実際にカメラを動かすところ、の2か所にZ軸に関する記述を
追加する必要があるかと思います。
すでに対応済みなら申し訳ありませんが、、以上となります。
どうぞよろしくお願いいたします。
こんにちは。
この度は、お忙しい中、質問に返信していただきありがとうございます。
管理人さんのアドバイス通り打ち込み、さらに試行錯誤した結果、3つ目以降のサーボモータも動きました!
私の場合、2つの方向に動かせればよかったので、move関数にチャンネルを一つ追加し、少しいじれば大丈夫だったようです。
わたしもロボティクスの大学生とはいえ、大した経験もしていないので、今回はとても助かりました。
ありがとうございました!
とんでもございません、こちらこそありがとうございます!
少しでもお役に立てたなら幸いです!
初めまして
工学部の大学生です。現在ラズパイにおける暗闇での物体追尾について研究しています。
質問があります!Raspberry Pi で作るOpen CVを使ったカラートラッキング追従カメラを記事通りの手順で進めたのですが、垂直方向はうまく動作し追従してくれたのに対して水平方向のサーボモータがうまく動作しませんでした。(永遠に回り続けたり、動かなかったり…)
一応サンプルファイルなどで動作確認をしたのでサーボモータ自体に問題はないとは思うのですが…
何か考えられる原因が分かれば教えていただきたいです。
使用したのは、
RaspberryPi 4Model BでOSはBusterです。電源は別で5Vのものを繋いでます。
Bluesman様
お返事おくれてしまい申し訳ありません、管理人です。
早速当記事のプログラムを検証した結果、こちらでも同じ症状が発生いたしました。申し訳ありません。
雑で申し訳ありませんが、応急措置として下記の修正プログラム追加をお願いします。
1.move関数を下記に変更
def movex(x_move):
pwm.set_pwm(1, 0, x_move)
def movey(y_move):
pwm.set_pwm(2, 0, y_move)
2,カメラ位置の初期化も変更
movex(X_HOME)
movey(Y_HOME)
3.関数呼び出しの記述move(move_degree_xmove_degree_y)を下記に変更
if move_degree_x > X_MIN and move_degree_x < X_MAX: movex(move_degree_x) if move_degree_y > Y_MIN and move_degree_y < Y_MAX: movey(move_degree_y) これで一応X軸は動くと思われます。(手元にY軸の環境が構築出来ていなくて、Y軸は試せていません、すいません(-_-;))
こんにちは
修正していただいたプログラムを動かしてみたつもりなのですが、エラーが出てしまいました。
Traceback (most recent call last):
File “/usr/lib/python3.7/ast.py”, line 35, in parse
return compile(source, filename, mode, PyCF_ONLY_AST)
File “/home/pi/Adafruit_Python_PCA9685/examples/colortrucking.py”, line 11
pwm.set_pwm(1, 0, x_move)
^
IndentationError: expected an indented block
お忙しい中何度もすみません。何卒宜しくお願い致します。
こんばんは、管理人です。
インデントは合ってますでしょうか?
お手数ですがご確認をお願いいたします。
質問させてください。 pigpioやwiringPiでのPWM操作はやったことがあるのですが、PCA9685 は使用したことがありません。 サーボモーターとのラズベリーパイとの接続方法をお教えいただけますでしょうか?
失礼しました。 前回の記事に載っていました。ありがとうございます。