# OpenCVライブラリを使ったコンピュータビジョン
## 必要なパッケージのインストール
```bash
# OpenCVの使用例
sudo apt install ros-noetic-opencv-apps
```
## シミュレーションの実行
以下の機能を試すためにはシミュレートされたカメラを使用します。Turtlebot3ロボットのシミュレーションを実行してください。
```bash
roslaunch turtlebot3_gazebo turtlebot3_autorace.launch
```
ロボットを操作するには、turtlebot3_teleopパッケージを使ってください。
または、`rqt`も使えます。(-s パラメータで直接プラグインを開くことができます。)
```bash
rqt -s RobotSteering
```
トピックを確認し、ロボットのカメラからのビデオが公開されているトピックを見つけましょう。
```bash
rostopic list
```
`rqt_image_view`を使用して画像を表示してみてください。
## OpenCVライブラリ
- コンピュータビジョン用のライブラリ*
- オープンソース
- C++のAPI、Java、Pythonなどをサポートしている
- pythonのライブラリの名前は cv2
- 画像処理、パターン認識などの機能
### OpenCVの使用例
#### エッジ検出
`opencv_apps`パッケージには、OpenCV関数を使用するアプリケーションの例が含まれています。
多くの場合、簡単に実行できるためlaunchファイルもあります。
例えば、エッジ検出用の `edge_detection.launch`があります。
edge_detection.launchの重要なパラメータ:
- `image` - 画像/ビデオを含むトピック
- `edge_type` - フィルタータイプ: 0 = Sobel、1 = Laplace、2 = Canny
例えば、以下のように[Canny法によるエッジ検出](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_canny/py_canny.html)を実行できます。
```bash
roslaunch opencv_apps edge_detection.launch image:=/camera/image edge_type:=2
```
ロボットを運転して、検出器がどのように機能するかを確認してください。
#### 角度検出
次に、1つの一般的な角度検出器を使ってみましょう。
[Shi-Tomasiのコーナー検出](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_feature2d/py_shi_tomasi/py_shi_tomasi.html)
```bash
roslaunch opencv_apps goodfeature_track.launch image:=/camera/image
```
どのポイントが角度として検出され、検出がどれほど安定しているかに注意してください。
ロボットが近くにあるか遠くにあるかにかかわらず、同じポイントが常に検出されますか?
#### 他の特徴の検出(任意)
OpenCVには、多くの特徴検出器(SIFT、SURF、FASTなど)の実装が含まれています。
なお、**ディープニューラルネットワークを使用すればより良い結果が得られるため、このタイプの手動でコーディングされた特徴は現在めったに使用されません。**
`find-object-2d`パッケージをインストールすれば、様々な検出器がどのように機能するかを試すことができます。
```bash
# sudo apt install ros-melodic-find-object-2d
rosrun find_object_2d find_object_2d image:=/camera/image
```
メニューから、 *View* -> *Parameters* を表示して、特徴を変更できます。
##### 物体の検出
`find-object-2d`アプリケーションでは、上記の特徴に基づいた物体検出を行うこともできます。
ただ、物体認識にはニューラルネットワークを使うのがいいです。
#### 色に基づいて画像をフィルタリングする
画像にフィルター(マスク)を適用して、特定の色の部分のみを残すことができます。これは、簡単な物体抽出に使用できます。
黄色のみの検出例(赤>200、緑>200、青<50の色)
```bash
roslaunch opencv_apps rgb_color_filter.launch image:=/camera/image r_limit_min:=200 r_limit_max:=255 b_limit_min:=0 b_limit_max:=50 g_limit_min:=200 g_limit_max:=255
```
#### 人や物体の検出(任意)
OpenCVには、古典的な手法を使用する人物検出器(people_detect.launchファイル)と顔検出器(face_detection.launchファイル)も含まれています。
最後に、OpenCVではディープニューラルネットワークを使用した物体検出も実装されています。
(使ってみたい場合は、[ROSのdnn_detectパッケージ](https://wiki.ros.org/dnn_detect)をインストールしてください。)
---
参考:[OpenCVチュートリアル](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_tutorials.html)
---
# OpenCV + ROS(ロボットビジョン)
## ROSでの画像の操作
(C++のROS APIではより多くの画像通信フォーマをサポートする`image_transport`パッケージがあります。)
基本的な使用例:
```python
import rospy
import cv2
# ...
def callbackImage(msg):
# ...
def main():
rospy.init_node('image_processing')
# 元の画像を購読する(メッセージタイプは sensor_msgsのImage)
sub = rospy.Subscriber("/input_image", Image, callbackImage)
# 処理された画像の発行元
pub = rospy.Publisher("/processed_image", Image, queue_size=5);
# ...
```
---
## OpenCV・ROSの画像変換
OpenCVとROSが使う画像変数の形式は異なります
基本的な画像形式(pythonを使う場合)
- OpenCV内(numpyの配列)→ `numpy.ndarray`
- ROS内(ROSメッセージ)→ `sensor_msgs.msg.Image`
一緒に使うには変換を行う必要があります
- ROSパッケージ*CvBridge*を使用します
[公式チュートリアル](https://wiki.ros.org/cv_bridge/Tutorials/ConvertingBetweenROSImagesAndOpenCVImagesPython)
### 変換
まずはCvBridgeタイプの変数を作成
```python
from cv_bridge import CvBridge, CvBridgeError
bridge = CvBridge()
```
**ROS → OpenCV**
(8ビットBGR形式への変換)
```python
try:
cv_image = bridge.imgmsg_to_cv2(ros_image, "bgr8")
except CvBridgeError as e:
rospy.logerr(e)
```
**OpenCV → ROS**
```python
try:
ros_image = bridge.cv2_to_imgmsg(cv_image, "bgr8")
except CvBridgeError as e:
rospy.logerr(e)
```
---
## 画像を表示
処理した画像を表示すると便利なときがあります。
cv2の`imshow`関数が使えます。
```python
cv2.imshow("画像タイトル", cv_image)
```
詳細: http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_gui/py_table_of_contents_gui/py_table_of_contents_gui.html#py-table-of-content-gui
---
## 試してみましょう
### 準備
(0)以下のアーカイブをダウンロードし、ROSのワークスペース(~/catkin_ws/src/)に解凍してください。
https://www.robot.soc.i.kyoto-u.ac.jp/roboprog/materials/ros_cv.zip
(1)シミュレーションを実行します。
```bash
roslaunch turtlebot3_gazebo turtlebot3_autorace.launch
```
(2)以下を実行して、シミュレーション内に赤いボールを挿入します。
```bash
rosrun gazebo_ros spawn_model -file ~/catkin_ws/src/ros_cv/urdf/ball.urdf -urdf -model ball0 -x 0 -y 0
```
### パッケージの確認
(3a)ros_cvパッケージのsrcフォルダーにあるノードのコードros_cv_test.py を確認してください。シミュレートされたロボットのカメラ画像に行動するように変更してください。
(見る必要はありませんが、C++を使った実装の例も入っています。)
(3b)実行して、動作確認を行ってください。もともとのカメラ画像に丸が入った画像が表示されるはずです。
### 色抽出の実装
(4a)赤色検出を実装しましょう。
- Python用のOpenCVライブラリ(cv2)の inRange 関数が使用できます。
例えば、以下は黄色を検出する例です。
```python
# BGR(青緑赤)という順番を使っている場合
low = (0, 200, 200)
high = (50, 255, 255)
masked_image = cv2.inRange(org_image, low, high)
```
(4b)inRange関数の結果を表示して見てください。
- 赤いボールのところだけが白くなって、残りはすべて黒くなっている、という画像を得るのが目標です。
- 影がありますので、ボールの下の部分が取得できない可能性があります。
### ボールまでの角度
(5)カメラから見たボールへの方向はどのように計算できますか?
- カメラのピンホールモデルを使います
C はカメラの中心、S は画像の側面、T は物体(例えば、検出したボールの中心)
- カメラのパラメータは、トピック `camera_info`から確認できます。重要なパラメータは要素 K に入っています。
- 形式は `K:[fx 0 u0 0 fy v0 0 0 1]`
- fx と fy は焦点の長さです(一般、f = fx = fy)
- u0 と v0 は画像の中心の座標です
- 物体の3次元位置が (x,y,z) としましょう。また、物体が画像の(u,v)ピクセルに写っています。
その場合、以下の式が使えます。
→ 横の位置: `x/z = (u - u0) / fx`
→ 縦の位置: `y/z = (v - v0) / fy`
- ロボットの移動のためには、横の向きだけで十分です。(カメラからみた向きとロボットからみた向きがだいたい同じだとします。)
考えてみてください:横の向きをどのように計算できますか?
---
→ [課題(メイン)](v4_task.html)
← [実習その1](v4_1.html)
↑ [ホームページ](index.html)