サーバー間で画像のやり取りをする方法

# 今回の内容
どうも、べちお です。

今回の内容ですが、 **サーバー間で画像の送受信** をすることが目的です。具体的には ラズパイで取得した画像を 別のサーバーへ送受信するシステムを実装しようとしました。あまりネットにも情報が少ないかと思いましたのでメモを兼ねてやり方を共有したいと思います。

# 実装環境(ディレクトリ構成)
内容をわかりやすくするために、実際の構成とは少し異なっていることをご了承ください。(必要最低限しか記載しておりません)

**Raspberry Pi**
> raspi/
> ├ app.py
> └ cam.py

**PC**
> app/
> └ app.py

# 結論
取得した画像を文字列に変換し、json形式で記述し、APIとしてサーバー間でやり取りを行います。

# 全体フロー
## 想定しているフロー
```
PCがAPIサーバ（ラズパイ）にアクセス(画像の要求)
            ↓
ラズパイカメラが起動　＋　画像の取得　+ PCに画像を返す(画像→文字列)
            ↓
PCで画像の復元(文字列→画像)　＋　画像の保存　＋　Webアプリに画像を表示
```

## 1. 画像の取得・文字列へ変換
変換までのフローは以下の通りです。
画像の取得　→　画像をエンコード　→　base64にエンコード　→　文字列に変換

これを実際のコードに表すと以下のようになります。
```
# 画像の取得
_, img = cam.read()
        ↓
# 画像をエンコード
_, buffer = cv2.imencode('.jpg', img)
        ↓
# base64にエンコード + 文字列に変換
img_as_text = base64.b64encode(buffer).decode('utf-8')
```

以下が全体のコードとなります。

```:cam.py
import cv2
import base64

###############################################
# OpenCVの設定
###############################################
cam = cv2.VideoCapture(0)
# Windowサイズの指定（横）
cam.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
# Windowサイズの指定（縦）
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
###############################################

def capture():
    cam = cv2.VideoCapture(0)

    # Windowサイズの指定（横）
    cam.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
    # Windowサイズの指定（縦）
    cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)

    print("capute on")

    ############################################################################
    # カメラ映像からスクリーンショットの取得
    _, img = cam.read()
    
    # スクリーンショットの取得
    cv2.imwrite('./tmp/screen.jpg', img)
    print("imrite")
    ############################################################################

    # 画像の保存
    cv2.imwrite('./new_smile-1.jpg', img)
    # print("error_check_point-3")

    # img は cv2.imread などで得られる画像を読み込んだnumpy.ndarray
    _, buffer = cv2.imencode('.jpg', img)
    # base64のバイナリ文字列をdecodeで文字列に変換
    img_as_text = base64.b64encode(buffer).decode('utf-8')

    return img_as_text

# 終了キー押下後、後処理
cam.release()
cv2.destroyAllWindows()

```
## 2. ラスパイサーバー側のバックエンドの処理
PCからラズパイサーバーにアクセスした際に画像をPCに返す処理を、以下に記載します。
```:app(ラズパイ)
import uvicorn
from fastapi import FastAPI
import cam

app = FastAPI()

@app.get("/")
def root():
    cam.capture()
    return {"img_as_text":img_as_text}

if __name__ == "__main__":
    uvicorn.run("app:app", port=8000)

```
## 3. PCサイド
画像の復元までのフローは以下の通りです。
jsonから画像の文字列を取得　→　文字列をbase64にデコード　→　配列に変換　→　配列をデコード　→　画像の復元

これを実際のコードに表すと以下のようになります。
```
# jsonから画像の文字列を取得
img_as_text = analysis["img_as_text"]
        ↓
# 文字列をbase64にデコード
img_binary = base64.b64decode(img_as_text.encode('utf-8'))
        ↓
# 配列に変換
img_array = np.frombuffer(img_binary, dtype=np.uint8)
        ↓
# 配列をデコード
img_from_text = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
        ↓
# 画像の復元
cv2.imwrite('./images/screen.jpg', img_from_text)
```

以下が全体のコードとなります。
```:app.py(PC)
from flask import Flask
from flask import render_template
import requests
import cv2
import numpy as np
import base64

app = Flask(__name__, static_folder='./images')


@app.route('/',methods=["POST","GET"])
def index():
    try:
        response = requests.get('ラズパイへのアクセス先')
        print(response)
        analysis = response.json()

        img_as_text = analysis["img_as_text"]
        # バイナリ文字列に変換
        img_binary = base64.b64decode(img_as_text.encode('utf-8'))
        # 配列に変換
        img_array = np.frombuffer(img_binary, dtype=np.uint8)
        img_from_text = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
        # 画像の復元
        cv2.imwrite('./images/screen.jpg', img_from_text)
        filepath = './images/screen.jpg'

    except Exception as e:
        print(e)
    
    return render_template("index.html",filepath=filepath)

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=8015)
```

# まとめ

サーバー間で画像の送受信をする方法をメモとして記載しました。
拙いコードではありますが、誰かの助けになると嬉しいです。
また、これ以上にもっと良い方法がある場合は教えていただけると幸いです。