matplotlibでRGBヒストグラムを描画し、画像に埋め込んでPythonによりストリーミング配信する

前回の記事では、Rspberry Pi 5 + USBカメラで撮影した画像を、PythonのFlaskパッケージを利用することによりHTTPでストリーミング配信できることを紹介しました。今回は、 **撮影した画像に、その画像のRGBヒストグラムを重ねて出力する** プログラムの例を紹介します。本プログラムにより、以下のような表示を得ることができます。

![Screencast-from-2024-12-03-19-53-05.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3919880/58229321-f58e-2d98-8255-46e908e116c2.gif)

https://qiita.com/xtrizeShino/items/aa9ffa6d875674d163d9

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

ヒストグラム付きのストリーミング映像を配信するWebサーバーのPythonコードをGitHubへpushしました。下記の手順でお試しいただけます。実装は、「 **opencv-web-sandbox/opencv-web-sandbox/web_root/views/streams.py** 」に埋め込んでいます。Pythonでapp.pyを実行した後はRaspberry Piの「 **[IPアドレス]:5000/streams/stream** 」へアクセスすることにより、ヒストグラムを含んだWebカメラの映像のストリーミング配信を見ることができます。

```bash
# ソースコードをGitHubより取得する
$ git clone https://github.com/xtrizeShino/opencv-web-sandbox.git
$ cd opencv-web-sandbox

# v0.1.0のタグのソースコードを取得
$ git checkout -b v0.2.0 refs/tags/v0.2.0

# 作業ディレクトリへ移動する
$ cd opencv-web-sandbox

# uvの仮想環境をセットアップする
$ uv sync

# 仮想環境に入る
$ . .venv/bin/activate

# app.pyを実行
(opencv-web-sandbox) $ python app.py
# * Serving Flask app 'web_root'
# * Debug mode : off
# * Running on akk addresses (0.0.0.0)
# * Running on http://127.0.0.1:5000
# * Running on http://192.168.10.140:5000
# Press CTRL+C to quit

############
###【終了するにはCtrl+c】
############
```

パッケージ管理ツール「uv」については以下をご参照ください。

https://qiita.com/xtrizeShino/items/74c9d3f5991da4afb06b

#### ソースコードの概略

OpenCVは、cv2.calcHistによりヒストグラムを生成することができます。ただし、撮影した画像はRGBの3要素から構成されていますので、まず、撮影した画像をRGBチャンネルに分解します。分解したそれぞれの要素からヒストグラムを生成し、matplotlib.pyplotを利用してグラフを作成しました。

作成したグラフはio.BytesIO()を利用して、描画データを画像データへと変換し、元画像と同じ形式であるnumpy.array型に変換しました。最後にちょうど良い埋め込みサイズへとリサイズしています。こうして作られた画像データを、imageproc_resize_and_histのcv2.warpAffineにより、元画像に結合します。

```python
# generate Histgram Image from input image
def generate_hist_image(image):
    # divide Image to RGB channels
    b, g, r = image[:,:,0], image[:,:,1], image[:,:, 2]
    # generate Histgram as each RGB
    hist_b = cv2.calcHist([b],[0],None,[256],[0,256])
    hist_g = cv2.calcHist([g],[0],None,[256],[0,256])
    hist_r = cv2.calcHist([r],[0],None,[256],[0,256])
    # write Plot
    plt.plot(hist_r, color='r', label="r")
    plt.plot(hist_g, color='g', label="g")
    plt.plot(hist_b, color='b', label="b")
    # set xlim, ylim
    plt.xlim(0, 255)
    # convert Histgram to numpy array
    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    buf.seek(0)
    enc = np.array(Image.open(buf).convert('RGB'))
    plt.gca().clear()
    # resize
    enc = cv2.resize(enc, (320, 240))
    return enc

# Callback function for Image Proc            
def imageproc_resize_and_hist(image):
    # resize Image
    image = cv2.resize(image, (1024, 768))
    h, w = image.shape[:2]
    # put Histgram on Image
    enc = generate_hist_image(image)
    # set offset
    dx=10
    dy=10
    # set Matrix for affine
    M = np.array([[1, 0, dx],[0, 1, dy]], dtype=float)
    # put Image by warpAffine
    image = cv2.warpAffine(enc, M, (w, h), image, borderMode=cv2.BORDER_TRANSPARENT)
    # output image
    return image
```

簡単でしたね！何かの際にお役立てください！

### 参考にさせていただきました記事

https://www.delftstack.com/ja/howto/matplotlib/differences-between-cla-clf-and-close-method-in-matplotlib/#google_vignette

https://biotech-lab.org/articles/13415