Raspiに接続したUSBカメラの画像をFlaskで飛ばしてWin10PCのOpenCVで再生する

# Raspi4のOS作成方法
最新の方法は、ラズパイ公式サイトの「OSインストールページ」の「Raspberry Pi Imager」を使う方法です。

参考HP
https://taku-info.com/raspi-osreinstall/
https://www.indoorcorgielec.com/resources/raspberry-pi/raspberry-pi-os%e3%81%ae%e3%82%a4%e3%83%b3%e3%82%b9%e3%83%88%e3%83%bc%e3%83%ab/

# pythonのバージョンを3.7.3に変える
参考HP
https://www.ingenious.jp/articles/howto/raspberry-pi-howto/python-3-change/

インストール直後の初期設定では、デフォルトで使用されるPythonは、Python2系になっています。
デフォルトで使用するPythonをPython3系に変更する方法について記載されてます。


# Raspi側のプログラム

参考HP
https://github.com/EbenKouao/pi-camera-stream-flask
https://qiita.com/Gyutan/items/1f81afacc7cac0b07526
https://qiita.com/naoppy/items/74bdfa8216c7223f584b

### ライブラリーのインストール
```
sudo apt-get update 
sudo apt-get upgrade

sudo apt-get install libatlas-base-dev
sudo apt-get install libjasper-dev
sudo apt-get install libqtgui4 
sudo apt-get install libqt4-test
sudo apt-get install libhdf5-dev

sudo pip3 install flask
sudo pip3 install numpy
sudo pip3 install imutils

```
参考HPでは下記コマンドでopenCVをインストールしているがRaspi4ではインストールに失敗する

```
sudo pip3 install opencv-contrib-python
sudo pip3 install opencv-python
```

そこで、他の方法でopenCVをインストールする
参考HP
https://qiita.com/naoppy/items/8a2178383859178e6d7e

```
pip3 install opencv-contrib-python==4.1.0.25
```


### USBカメラを認識させるためのライブラリーをインストールする

USBカメラ認識について書かれてます
https://qiita.com/iwatake2222/items/d6645880c5bb91ce8a85

カメラ制御にはVideo4Linux2(V4L2)というAPIを使用します
V4L2を使用するためには、事前にコマンドでモジュールを組込んでおく必要があります。
これを実行するとカメラがビデオデバイスとして認識されます(/dev/video0)

```
sudo modprobe bcm2835-v4l2
```

lsusbコマンドでのカメラ確認

```
$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 046d:c534 Logitech, Inc. Unifying Receiver
Bus 001 Device 003: ID 046d:0825 Logitech, Inc. HD Pro Webcam C920
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
```
3行目にC920が認識されている

デバイスファイルの確認

```
$ ls /dev/video*
/dev/video0  /dev/video10  /dev/video12  /dev/video14 /dev/video16
/dev/video1  /dev/video11  /dev/video13  /dev/video15  /dev/video18
```

Raspi4では、1つのwebカメラに対して2つデバイスファイル(video0とvideo1)が作られる仕様になっている。

USBカメラの詳細を調べるツールu4l-utilsをインストールする

```
sudo apt-get install v4l-utils
```
v4l-utilsを使ってUSBカメラを調べる
https://leico.github.io/TechnicalNote/Linux/webcam-usage

```
$ v4l2-ctl --list-devices
bcm2835-codec-decode (platform:bcm2835-codec):
        /dev/video10
        /dev/video11
        /dev/video12
        /dev/video18

bcm2835-isp (platform:bcm2835-isp):
        /dev/video13
        /dev/video14
        /dev/video15
        /dev/video16

HD Pro webcam C920 (usb-0000:01:00.0-1.2):
        /dev/video0
        /dev/video1
```


### プログラム

参考プログラムをgithubにあげたのでここから落としてください

```
git clone https://github.com/fuji244tys/streamer
```

USBカメラの動画をopenCVで取得する

```camera.py

import cv2

class VideoCamera(object):
    def __init__(self):
        self.video = cv2.VideoCapture(-1)

        # (-1)はRaspiにつながったカメラを探します

    def __del__(self):
        self.video.release()

    def get_frame(self):
        success, image = self.video.read()
        ret, jpeg = cv2.imencode('.jpg', image)
        return jpeg.tobytes()

        # read()は、二つの値を返すので、success, imageの2つ変数で受けています。
        # OpencVはデフォルトでは raw imagesなので JPEGに変換
        # ファイルに保存する場合はimwriteを使用、メモリ上に格納したい時はimencodeを使用
        # cv2.imencode() は numpy.ndarray() を返すので .tobytes() で bytes 型に変換

```
Flaskでビデオストリーミング

```main.py

from flask import Flask, render_template, Response
from camera import VideoCamera

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

def gen(camera):
    while True:
        frame = camera.get_frame()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')

@app.route('/video_feed')
def video_feed():
    return Response(gen(VideoCamera()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

```

### プログラムの実行方法

```
pi@raspberrypi:~ $ cd streamer
pi@raspberrypi:~/streamer $
pi@raspberrypi:~/streamer $ python3 main.py

```



# Win10PC側のプログラム

## RaspiのIPアドレスを調べる方法

参考HP
https://edu.isc.chubu.ac.jp/naga/mri/mri7.html

Flaskのプログラムは別のPCからのアクセスを許可しているので、Flask を実行しているRaspiのIPアドレスがわかれば、同じネットワークに 接続している他のPCからアクセスできる。
IP アドレスは次のコマンドで確認できる。
無線 LAN ルータに接続 している場合は、一般的に割り当てられるアドレスは 192.168.???.??? になる。


```
pi@raspi4A:~/pi-camera-stream-flask $ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether dc:a6:32:bf:84:5a brd ff:ff:ff:ff:ff:ff
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether dc:a6:32:bf:84:5b brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.14/24 brd 192.168.0.255 scope global dynamic noprefixroute wlan0
       valid_lft 79846sec preferred_lft 69046sec
    inet6 fe80::b61d:48f9:cb17:2b3d/64 scope link 
       valid_lft forever preferred_lft forever

```

無線LANで繋がってるRaspiのIPアドレスは　192.168.0.14　となる

## 画像受信プログラム
RaspiのIPアドレスを設定すると画像を受信できた。
たまに画像を取れないことが発生したので、取れないときに再起動させるようにしたら連続して動画表示できるようになった。


```stream.py
#-------------------------------------------------------------------------------
# Name:         streaming.py
# Purpose:      Win10_PC recieve video from Raspi4 using openCV
#               video streamer be maked by Flask run on Raspi4+USBcamera
#
# Author:       fujioka
#
# Created:     2021/10/20
# Copyright:   (c) T.F. 2021
#-------------------------------------------------------------------------------
import cv2
import time

url = "http://192.168.0.14:5000/video_feed"

#================================================
# func
#================================================
def stream():
    try:
        print('start:' + time.strftime('%Y/%m/%d %H:%M:%S'))
        video = cv2.VideoCapture(url)
        t2=0
        while(video.isOpened()):
            t1 = time.time()
            ret, frame = video.read()
            #------------------------------------------------------------------
            if ret is False:
                print('read error:' + time.strftime('%Y/%m/%d %H:%M:%S'))
                video.release()
                cv2.destroyAllWindows()
                time.sleep(1) #second
                stream()
                break
            #------------------------------------------------------------------
            #cv2.rectangle(frame, pt1, pt2, color, thickness=1
            #                   , lineType=cv2.LINE_8, shift=0)
            cv2.rectangle(frame , (0, 2), (140, 30), (255, 255, 0), -1)
            cv2.putText(frame   , '{:>5.1f} ms'.format((t1 - t2)*1000)
                                , (10, 25)
                                , cv2.FONT_HERSHEY_SIMPLEX
                                , 0.8
                                , (0, 0, 255)
                                , thickness=2)

            cv2.imshow('frame', frame)
            t2 = t1

            #------------------------------------------------------------------
            key = cv2.waitKey(1)
            if key == 27:
                video.release()
                cv2.destroyAllWindows()
                break

    except KeyboardInterrupt:
        pass

    finally:
        video.release()
        cv2.destroyAllWindows()


#================================================
# main
#================================================
stream()

```

動画表示画面
![stream2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492810/2a423265-5625-388f-b532-d6daef72eeb0.jpeg)


# 物体検出

RaspiにつないだUSBカメラからの画像をWin10PCで受信できるようになった。次はこの画像に物体検出を行う。

参考HP
Pytorchで物体検出Yolov5を動作させる
https://qiita.com/fujioka244kogacity/items/e5acf8d9bb728e7d1bcc

ここのdetect.pyの--source 指定を0からurlに変えると良い

```
(yolo5) C:\Users\fujio>cd yolov5
(yolo5) C:\Users\fujio\yolov5>python detect.py --source 0 --weight best.pt
```

```
(yolo5) C:\Users\fujio>cd yolov5
(yolo5) C:\Users\fujio\yolov5>python detect.py --source "http://192.168.0.14:5000/video_feed"  --weight best.pt
```
![stream3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492810/ef767164-3dcf-1ce6-c426-d6e82e3e8db5.jpeg)