エンジニアに飼われた悲しきハムスターの末路


こんにちわ！B2のすめしです。
本記事は、[FUN Advent Calendar 2022 part2](https://adventar.org/calendars/7426)の12/7の記事です。
アドカレ初めて＆＆プログラムよわよわなのでお手柔らかにお願いします。

# はじめに
　物騒なタイトルをしていますが、動物虐待ではありません。「かわいい弊ハムスターをいつでも見たい！」ということで開発したペットカメラの記事になります。技術的な話は後半にあるので、「前座はいらねぇ！早く見せろという方は[ここ](#実際のコード)まで飛ばしてください。」

## ハムスターについて
![puti_welcome.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175917/6613f320-99fe-c367-163f-ee6702502548.jpeg)

　１１月３日にブルーサファイアハムスターのぷち（♂）をお迎えしました。ハムスター買いたいねなどと話しており、ペットショップに向かうといろんな種類のハムスターがいました。種類はブルーサファイアハムスターに決定し、２匹いたうちのどちらを選ぶか決める段階まで行ったのですが、どちらも顔が見えなかったため顔を見せてもらいました。すると！１匹目の子が！とても！！かわいかったため！その子をお迎えすることにしました。

## 構想
　ハムスターをお迎えしてすぐ「ペットカメラが欲しい！」と思ったところで、「せっかくなら作ろう！」とエンジニア（まだまだひよっこですが...）の性が出てしまったので、作ることに決定。ちょうど余っていたRaspberryPiZeroWHを使って作ることにしました。

　RaspberryPiにUSBカメラを付けて、取得した画像をDiscord.pyで送る簡単なプログラムを書く予定でした。が、しかし、様々な壁にぶち当たり最終的にはこうなりました。

![puticamera.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175917/055051a8-5a62-02a5-6106-a9cfcbed78d5.jpeg)

　RaspberryPiにコンテナを立ててその中で上記のプログラムを書くことにしました。

## 様々な壁たち
- Rasbianに必要なライブラリをインストールできない
OpenCV、Discord.pyを実行するに当たり、依存パッケージをインストールする必要があるのですが、Ubuntuのパッケージリポジトリ？にあるパッケージをインストールせねばならないのになぜかソースリストにそのリポジトリを追加できない現象に悩まされRasbian上で動かすことを断念しました。
- armのアーキテクチャ問題
　この現象にこの開発中に２回見舞われる事になりました。
　１回目は、Rasbian上で動かすことを諦めたとき「Ubuntuでやればいいじゃん！」と思いつきUbuntuをインストールしました。しかし、Ubuntuを起動しようとしても４色のウォーターマーク画面でフリーズし、その先に進むことはありませんでした。解決方法として、GPUチェックを飛ばす、電圧が低下してないか確認するの２点があり、確認しましたが解決しなかったのでRasbianに戻しました。
　２回目は、Docker内のイメージでUbuntuを採用し、x86環境で実行した際には現れなかったエラーです。そこには、「armv7のイメージがarmv6のマシンで動くわけねぇだろ！（意訳）」と書いてありました。調べた結果、Ubuntuはarmv7以降のイメージしか提供していなかったため、RaspberryPiZeroでは動かないことがわかりました。結局、Debianのarmv5イメージを採用することで事なきを得ました。
　この勘違いを生んだ要因が二つあり、
  - 高校時代に何もわからないながらRaspberryPi「3B+」にubuntuをインストールしたことがあったこと。（3B+はarmv7）
  - RaspberryPiの公式イメージにUbuntuがあったこと。（Zeroについて記述はなく、記述のあったZero2(armv8)はzero(armv6)と異なるアーキテクチャだったため勘違いが加速した。）
- あとは些細なDockerとLinuxの沼にハマりまくっていた。

## 実際のコード
### Docker
　FROM debianのあとにバージョンなどを記載しないことで最適なアーキテクチャを指定してくれる。（今回の場合はarmv5）
コメントアウトしているところは結局コンテナに入ってから実行しないと正常に動作しなかったため、メモの意味で残している。
```Dockerfile:Dockerfile
FROM debian
USER root

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

RUN apt-get update -y && apt-get upgrade -y && \
	apt-get install -y nano \
	python3 \
	python3-dev \
	python3-pip \
	python3-setuptools \
	libopencv-dev \
	libgl1-mesa-dev \
	libglib2.0-0 \
	libsm6 \
	libxrender1 \
	libxext6 \
	libffi-dev \
	libnacl-dev && \
#	python3 -m pip install --upgrade pip \
#	setuptools && \
#	python3 -m pip install numpy \
#	opencv-python \
#	discord.py && \
	mkdir /home/workspace 
```

　カメラをコンテナに認識させる、working_dirを移動させる、ディレクトリを同期させる、といったコマンドをdocker runで書くのは面倒なのでdocker-composeで書きました。便利です。

```yaml:dokcer-compose.yml
version: "3"
services:
  puti:
    restart: always
    build: .
    container_name: "puti_camera"
    working_dir: "/home/workspace"
    tty: true
    volumes:
      - ./workspace:/home/workspace
    devices:
      - /dev/video0:/dev/video0:mwr
```
### Python
　OpenCVで画像を取得し、jpgとして出力するほぼ最小構成のコードです。
画像取得部分と、出力部分を関数化することによって、モジュールとして呼び出せるようにしました。
```python:puti_camera.py
import cv2

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH,1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,720)
def get_img():
    ret,frame = cap.read()
    print(ret)
    cv2.imwrite("image.jpg",frame)
    cap.release
```

　取得した画像をDiscordに送るbotを制御するコード。
これもほぼ最小構成というか、ほぼチュートリアルのコードと変わっていません。

```python:puti_discord.py
import discord
import discord_token
import puti_camera as pc

intents = discord.Intents.all()
intents.messages = True
intents.message_content = True
client = discord.Client(intents=intents)

@client.event
async def on_ready():
    print(f'We have logged in as {client.user}')


@client.event
async def on_message(message):
    if message.author == client.user:
        return

    if message.content.startswith('?puti'):
        await message.channel.send('Please wait a minite...')
        pc.get_img()
        await message.channel.send(file=discord.File('./image.jpg'))

client.run(discord_token.get_token())
```
### 結果
　Discordに画像が送られてきた！
![puti_out.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175917/5b3ba1bb-d5ec-3590-5676-c829a0fcb38b.jpeg)


### 学んだこと
- armのアーキテクチャについて　&&　x86以外でやることの難しさ
- Discord.py、OpenCVの簡単な使い方
- Docker周りの仕様をより深く知れた
(ZeroじゃなければDockerの特徴である、環境の再現性を実感するいい機会になったんだけどな...)

## まとめ
　とにかく実行部分のコードよりもインフラの面で苦労したところが多かったが、学んだことがとても多かったので作ってよかったなと思っています。あと、「もう一台カメラを設置する＋暗いと何も映らないので写真取るときにうっすら電気をつける＋温湿度計をつける」をやりたいなと思っています。
　最後に、やる気を復活させてくれてありがとう、ぷち。

## 最後に
　明日（12/8）はAtriaさんとNickさんですね！楽しみにしてます！