メインコンテンツへスキップ
見出し画像

Raspberry Piでやってみた(画像処理):OpenCVによる画像処理の実装

    1.概要

     Rasberry Piでできることの一つにカメラを用いた撮影があります。環境構築も完了してカメラ動作も確認出来たら次はAIで遊びたくなります。

     今回はOpenCVが使えるように環境構築を実施していきたいと思います。

    1-1.Rasberry Piの環境構築

     1章の紹介記事をベースにOpenCV使用前にRasberry Piの環境構築を実施しておきます。概要は下記の通りであり詳細は記事をご確認ください。

    【仕様】

    • 本体:Rasberry Pi 4

    • Rasberry Pi OS:Debian Bullseys 64bit(Release:2023/5/3)

    • カメラモジュール:Raspberry Pi カメラモジュール V3

    • ラズパイシステムのPython Version:3.9.2

    • その他:Conda環境のためMiniforge追加

    【Rasberry Piの環境構築】

    1. Raspberry Pi OS(64-bit) :BullseyeをRasberry Pi Imagerで書き込み

    2. ラズパイ初期起動時の設定、インターフェースの有効化

    3. システム更新”sudo apt-get update && sudo apt-get upgrade”を実施

    4. 任意:リモートアクセスの準備

      • ファイルのやり取りができるためVNC Viewerを使用

    5. 任意:VS Code、Miniforgeをインストール

    6. 任意:ソフトウェア(vim, git)を追加

    7. 任意:Linuxコマンドパッケージ(Zip, aptitude)を追加

    8. カメラモジュールの環境構築

      1. 今回はRasberry Pi4×カメラモジュールV3の環境で作成

      2. 動作確認は事前に実施すること

    [Terminal※Webカメラ動作確認用]
    libcamera-hello

    1-2.画像処理ライブラリの紹介

     画像処理ができるPythonライブラリ+αは下記の通りです。

    • OpenCV:OpenCVはコンピュータビジョンのためのオープンソースライブラリであり画像処理、顔認識、物体検出などができます。

    • YOLO(You Only Look Once):リアルタイムでの物体検出を行うためのアルゴリズム

    • OpenPose:人体の姿勢を検出するためのライブラリ

    • MediaPipe:Googleが開発したマルチプラットフォーム向けの機械学習ソリューションであり、手のトラッキングや顔検出などの機能があります。

    • OAK-D OpenCV DepthAI:深度情報を持つ3Dビジョンを実現するためのデバイス

    2.参考:Linuxコマンド

     参考用に使用するLinuxコマンドを参考用として記載します。

    2-1.Linuxコマンド

    1. apt:「Advanced Package Tool」の略で、DebianベースのLinuxディストリビューション(例: Ubuntu, Raspberry Pi OS)でソフトウェアパッケージを管理するためのツールです。aptを使用することで、ソフトウェアのインストール、アップデート、削除などの操作を行うことができます。また、apt-cacheコマンドを使用して、利用可能なパッケージの情報を検索することもできます。

    2. apt-get:DebianベースのLinuxディストリビューション(例: Ubuntu)で使用されるパッケージ管理ツールです。これを使用してソフトウェアのインストール、アップデート、削除などを行います。

      • install:ソフトウェアをインストール

    3. bash:シェルスクリプトを実行するコマンド

      • -b:バッチモードでインストール+確認プロンプトを表示しない。つまりインストールが自動的に進行します。

      • -u:既存のインストールを更新。つまり既にMinicondaがあるなら、そのインストールを最新のバージョンに更新する。

      • -p:インストール先のディレクトリを指定

    4. cd:"change directory"の略。ターミナル上の作業場所を移動する

      • ~:ホームディレクトリを示す。”cd ~”で立ち上げ時のdirに移動

    5. cp:ファイルやディレクトリをコピーするコマンド

    6. df:ディスクの使用量を表示(-hオプションで体裁を綺麗にする)

    7. free:メモリ使用量を表示(-mオプションでMB単位で出力)

    8. ln:ファイルのリンクを作成するためのコマンド

    9. lsb_release:Linux Standard Base (LSB) に関する情報を表示するコマンドであり、-aオプションですべての情報を表示

    10. ls:作業ディレクトリ内のファイルを表示

      1. -lt:作成時間も含めて表示

    11. mkdir:”make directory”の略。新しいフォルダを作成

      • -p:指定したディレクトリが無い場合は作成する。また必要な親ディレクトリも合わせて作成

    12. nano:nanoエディタで指定したファイルを開く

    13. patch:ソースコードにパッチを適用するためのコマンド

      • -p1:パッチファイルのディレクトリ構造をどの程度削除するかを指定->1レベルのディレクトリ構造が削除されます。

      • -i:入力として使用するパッチファイルを指定

    14. rm:"remove"の略で、ファイルやディレクトリを削除するコマンド

      • -r(--recursive):ディレクトリとその内容を再帰的に削除する。ディレクトリを削除する場合はこのオプションが必要

      • -f(--force):エラーメッセージ、警告や確認を表示せずに強制的にファイルやディレクトリを削除

    15. shutdown:コンピューターの電源をオフ/再起動するためのコマンド

      • -rオプション:再起動

    16. sudo:「SuperUser DO」の略で特定のコマンドをシステム管理者(rootユーザー)として実行するためのコマンドです。sudoを使用することで、一時的に高い権限を持つrootユーザーとしてコマンドを実行できます。

    17. tar:ファイルやディレクトリをアーカイブ化、またはアーカイブを展開するためのコマンド(zipみたいに圧縮・展開する)

      • -x:展開

      • -z:gzipで圧縮されたアーカイブを処理

      • -v:詳細モードで動作->処理されているファイルのリストが表示

      • -f:アーカイブファイル名を指定

      • -C:展開先のディレクトリを指定->ホームディレクトリに展開

    18. update:aptのサブコマンドの一つでソフトウェアのリポジトリから最新のパッケージ情報を取得して、ローカルのパッケージデータベースを更新

    19. unzip:ダウンロードしたzipファイルを展開

    20. wget:インターネットからファイルをダウンロード

      • -O:DLしたファイルの保存先と名前を指定

    2-2.Rasberry Pi用コマンド

    1. raspi-config:Raspberry Piのシステム設定ツールを開く

    2. rpi-update:Raspberry Piのファームウェアをアップデート

    2-3.コマンド使用例

     実際のコマンド使用例は下記の通りです。

    1. ls -lt:作成時間も含めて作業ディレクトリ内のファイルを表示

    2. sudo apt update:インストール可能なソフトウェアのパッケージのリストを最新の状態に更新

    3. sudo apt upgrade:インストールされているソフトウェアを最新のバージョンに更新

    4. sudo shutdown -h now:今すぐシャットダウン(電源OFF)

    5. shutdown -r now:今すぐ再起動を実施する

      • ”sudo reboot now”でも再起動可能

    3.環境構築:全般

     公式Docsを参照しながら環境構築を実施していきます。

    3-1.環境構築方法:ソースからビルド/パッケージ

     OpenCVをLinuX環境にインストールする方法は2種類あり、「ソースからビルド」と「パッケージマネージャ」があります。

     それぞれの特徴は下記の通りです。

    【ソースからビルド:Build from sources】

    • カスタマイズ性が高く、最新Ver.を利用できる

    • コンパイルとインストールに時間がかかる(数時間)

    • 環境構築が難しい

    【パッケージマネジャー:Packages by OpenCV core team】

    • カスタマイズが限定的でありバージョンが古い可能性がある

      • 過去の事例では最新がVer4.0でもpipだとVer3.xになる

    • ビルドに必要なリソースが不要

    • コマンド1つでインストールできるため非常に簡単

      • "pip"でコンパイル済みのOpenCVをインストール可能

    3-2.パッケージマネジャーによる環境構築

     パッケージマネジャーならpipを用いるだけで実装できます。

    [terminal]
    pip install opencv-python

    【参考:contrib版】
     追加機能も含めたcontrib版もインストール可能です。ただし、初学者だとエラー時の対応が難しいと思うので基本的には標準を実装を推奨します。 

    4.環境構築:ソースからビルド

     本章ではパッケージマネジャーを使用せず1から環境構築したい人向けの章です。バージョンや細かい部分は気にせず早く使用したい方は"pip install opencv-python"で使用できるため本章は飛ばしても問題ありません。

     仮想環境からでは環境構築が難しすぎてできなかったため、今回はシステム環境で実行しました。私はMiniforgeの仮想環境から”conda deactivate”を実行してシステム環境で環境構築を実施しました。

    画像

    4-1.環境の確認

     環境構築時の環境設定のためにフォルダ構成とPythonのVersion(フォルダ)を確認しておきます。
    ※cmakeで途中までコンパイルした時の画像のため初期時とは異なる可能性があります。

    [Terminal]
    ls /usr/
    ls /usr/local/
    ls /usr/local/lib/
    ls /usr/local/lib/python3.9
    
    python --version
    conda deactivate
    python --version
    python3 --version
    画像

     私の環境では下記が確認できました。

    1. Pythonフォルダは"/usr/local/lib/"にあり2.7と3.9が混在している

      • システム環境下ではpython3でコマンド実行しないとエラーの元

    2. Minicondaの仮想環境(base)におけるPythonは3.10

    4-2.Rasberry Pi環境の調整

     まず初めにRasberry Piを最大限使用できるように設定確認/変更していきます。概要は下記の通りです。

    1. ディスク使用量確認

      • 必要であればExpand FilesystemでSDカードのスペース拡張

    2. システムアップデート

    3. スワップファイルの設定

    4. スワップファイルの再起動/確認

     4-2-1.ディスク使用量確認

     "df-h"コマンドでディスク使用量を確認できます。rootのサイズが購入したSDカード容量(下図は64GB)より過小である場合は一部メモリが認識されていない可能性があるため「EXPAND FILESYSTEM」で拡張します。
     私の環境では特に不要なので次に行きます。

    [Terminal]
    df -h
    画像

    【EXPAND FILESYSTEMの方法】
    Expand Filesystemにより、Raspberry PiのOS(Raspbianなど)がインストールされているSDカードの利用可能なスペースを最大限に拡張できます。
     方法はターミナルで”sudo raspi-config”を実行し「Advance Options->Expand Filesystem」を選択します。ウィンドウを閉じ再起動を実施します。

     起動後に"df -h"で確認すると条件によりメモリが増えます。

    [Terminal]
    sudo raspi-config
    画像

     4-2-2.システムアップデート

     念のためにシステムをアップデートしておきます。

    [Terminal]
    sudo apt-get update && sudo apt-get upgrade

     4-2-3.スワップファイルの設定

    スワップファイルとは、ハードディスクやSSDの一部を仮想メモリとして使用するためのファイルです。物理メモリ(RAM)が不足した場合、スワップファイルを使用してメモリを拡張します。
     大規模なプログラム(例: OpenCVのコンパイル)を実行する際、物理メモリだけでは不足することがあるため、スワップファイルを利用することで物理メモリを超えたデータをディスクに一時的に保存し、プログラムの実行をサポートできます。

     Rasberry Pi 4 8GBを使用しているためおそらく問題ないと思いますが、他のRasberry Piを使用することも考慮してやり方を記載します。

     まずは下記コマンドでnanoエディタを用いてswapfileを開きます。次にswapfile内の”CONF_SWAPSIZE”に対してデフォルトの値をコメントアウトして、その下に高めの値を追加しました。

    [Terminal]
    sudo nano /etc/dphys-swapfile
    [/etc/dphys-swapfile]
    # CONF_SWAPSIZE=100
    CONF_SWAPSIZE=2000
    画像

     nanoエディタのショートカットは下記の通りです。

    • 切取り:Ctrl+K

    • 貼り付け:Ctrl+U

    • 終了:Ctrl+X(変更時は保存を問われるためy+Enterで上書き)

     4-2-4.スワップファイルの再起動/確認

     ファイル変更後はdphys-swapfileを再起動(stop->start)します。"free -m"コマンドで確認すると再起動後にswapサイズの変更が確認できます。

    [Terminal]
    sudo /etc/init.d/dphys-swapfile stop
    sudo /etc/init.d/dphys-swapfile start
    free -m
    画像

    4-3.パッケージのインストール

     Pythonでいう”pip install”のように、Rasberry Pi(LinuX)にもパッケージを追加可能です。今回使用していくパッケージを追加していきます。

    [Terminal]
    sudo apt-get install build-essential cmake pkg-config
    sudo apt-get install libpng-dev libjpeg-dev libopenexr-dev libtiff-dev libwebp-dev
    sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
    sudo apt-get install libxvidcore-dev libx264-dev
    sudo apt-get install libgtk2.0-dev libgtk-3-dev
    sudo apt-get install libatlas-base-dev gfortran
    sudo apt-get install python2.7-dev python3-dev

    【パッケージの紹介】

    1. build-essential:C/C++言語でプログラムをビルド(コンパイルとリンク)するために必要なツールを提供

      • gcc, g++, makeなど、基本的なビルドツールを含む

      • ソースコードから実行可能なバイナリを生成するのに必要

      • make:ソースコードから実行可能なプログラムをビルドするためのツール。Makefileにビルドのルールを記述し、makeコマンドでこれを解釈してビルドする

    2. cmake:ソフトウェアのビルドプロセスを自動化するためのツール

      • CMakeLists.txtにビルドのルールを記述し、cmakeコマンドでビルド

    3. pkg-config:インストールされているライブラリを検出し、その設定をコンパイラに伝えるツール

      • gccなどのコンパイラに、どのライブラリをどこで見つけるか(パス)、どのヘッダーファイルを含むかなどを教える

    4. libjpeg-dev, libtiff5-dev, libjasper-dev, libpng-dev:

      • 画像ファイル(JPEG, TIFF, Jasper, PNGなど)を扱うためのライブラリ

      • OpenCVがこれらを利用して多くの画像フォーマットをサポートする

      • libjasper-devは見つからないため削除

      • libpng12-devはlibpng-devに変更されたとのこと

    5. libavcodec-dev, libavformat-dev, libswscaledev, libv4l-dev:

      • ビデオとオーディオを処理するためのライブラリ

      • ビデオ/オーディオのエンコード/デコード、フォーマットの変換、ビデオキャプチャなど対応可能

    6. libxvidcore-dev, libx264-dev:ビデオコーデックを提供するライブラリ

      • ビデオファイルの圧縮(エンコード)/展開(デコード)で使用

    7. libgtk2.0-dev, libgtk-3-dev:GUI作成用のライブラリ

      • GTKはウィンドウ、ボタン、テキストボックス等のウィジェットを提供

    8. libatlas-base-dev:ATLASは高性能な数値計算を提供

    9. gfortran:Fortran言語のコンパイラ

    10. python2.7-dev, python3-dev:Python言語の開発ヘッダーファイルとライブラリを提供

    【備忘録】

    1. libjasper-devは現在インストールができない。

      • JPEG関係のライブラリだが無くてもエラーが出ないらしい

    2. libpng12-devは現在インストールができない。

      • libpng-devで代替

    4-4.OpenCVソースコードを取得

     OpenCVのソースコードとして下記2種を取得します。

    • 標準ライブラリ:標準的に使用する機能のみを集めたモジュール

    • Contrib Libraries:"Extra modules"とも呼ばれており、追加機能/APIを含んだモジュールである。最新機能が試せる反面、不安定な動作もある。

     まずは好きな場所に作業ディレクトリを作成します。私はDesktopにopencvフォルダを作成し、そこを作業ディレクトリに変更(cd)しました。

    [Terminal]
    cd Desktop
    mkdir opencv
    cd opencv
    画像

    【注意点:指定するVersionに関して】
     特に3.x系と4.x系で大きな変更(APIなど)があるかもしれないため自分が参考にしたい本や記事のVersionと合わせておくのがベターと思います。

     4-4-1.標準版ソースコード

     今回はVersionを指定(4.8.1)してダウンロードします。wgetに-Oオプションを付けることでDLしたファイル名を"opencv.zip"に指定しました。

    [Terminal]
    wget -O opencv.zip https://github.com/opencv/opencv/archive/refs/tags/4.8.1.zip
    unzip opencv.zip

    unzipまで実行すると指定Versionのフォルダが生成されました。

    画像

    【OpenCVのソースコード確認方法】
     ソースコードのURLは公式GitHubのtagsまたはReleaseへ移動し「Source code (zip)」を右クリックしてリンクをコピーすれば確認可能です。

    画像

     4-4-2.Contrib Libraries

    OpenCV Contrib Librariesとは、コミュニティから提供されたOpenCVのメインリポジトリには含まれていない追加のモジュールを集めたものであり、OpenCVとは別にインストールする必要があります。
     前項と同様の手順で同じVersionの物をDL/unzipしました。

    [Terminal]
    wget -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/refs/tags/4.8.1.zip
    unzip opencv_contrib.zip
    画像

    4-5.OpenCVのビルド

     ソースコードをコンピュータが直接実行できる形式(機械語)に変換(コンパイル)し、ソースコードを実行可能なソフトウェア製品に変換(ビルド)する必要があります。
     まずはビルドの準備のため作業ディレクトリを変更します。

    [Terminal]
    cd opencv-4.8.1/
    mkdir build
    cd build/

     次にコンパイル前にビルド設定をcmakeを使用して行います。実行完了するとbuildフォルダ内に複数のファイルが生成されます。
    ※”-D OPENCV_EXTRA_MODULES_PATH”のPathは作業環境+DLしたOpenCVのVersionや名前に応じて適宜設定のこと

    [Terminal]
    cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D INSTALL_C_EXAMPLES=OFF \
    -D INSTALL_PYTHON_EXAMPLES=ON \
    -D OPENCV_EXTRA_MODULES_PATH=~/Desktop/opencv/opencv_contrib-4.8.1/modules \
    -D BUILD_EXAMPLES=ON \
    -D ENABLE_NEON=ON ..
    画像

     上記のビルド設定に基づきmakeでコンパイル/リンクを実施します。-j4オプションは4つのコアを使用してビルドを行うことを意味します。Rasberry Pi4のコア数は4のため最大に設定しています。

    [Terminal]
    sudo make -j4

     終了するとCMkaerFilesなどが生成されます。実行からコンパイルが終了するまで2.5h程度かかりました。

    【参考:コンパイルのやり直し():make clean】
     
    途中で停止したなどで再度コンパイルしたい場合、そのままmakeすると変更があったものだけ再コンパイルしますが、make cleanですべてのオブジェクトを削除できます。

    [Terminal]
    sudo make clean

    【参考:ビルド設定のコマンド説明】
     ビルド設定のためにcmakeで使用したコマンドの意味を記載しました。

    1. -D CMAKE_BUILD_TYPE=RELEASE:ビルドタイプを指定

      • RELEASEは最適化を有効にしてデバッグ情報を無効にする設定

      • これにより生成されるバイナリは実行速度が速くなりますが、デバッグが難しくなる

    2. -D CMAKE_INSTALL_PREFIX=/usr/local:インストール先のディレクトリを指定

      • ビルドが完了したバイナリ、ライブラリ、ヘッダーファイルなどが/usr/localディレクトリにインストールされる

    3. -D INSTALL_C_EXAMPLES=OFF: C言語のサンプルコードのインストールオプションをオフ

      • オプションOFF:C言語のサンプルはインストールされない

    4. -D INSTALL_PYTHON_EXAMPLES=ON:Pythonのサンプルコードのインストールオプションをオン

      • Pythonで書かれたサンプルコードがインストールされる

    5. -D OPENCV_EXTRA_MODULES_PATH=~/Desktop/opencv/ opencv_contrib-4.8.1/modules:OpenCVの追加モジュールのパスを指定

      • opencv_contribリポジトリをビルドに含めるためのパスを指定

    6. -D BUILD_EXAMPLES=ON:サンプルコードをビルドするかを指定

      • ON:サンプルコードもビルドされる

    7. -D ENABLE_NEON=ON:NEONを有効化

      • NEONは、ARMアーキテクチャで使用可能なSIMD(Single Instruction, Multiple Data)アーキテクチャを指す。

      • これを有効にするとARMプラットフォーム(Raspberry Piなど)でのパフォーマンスが向上する

    8. ..:CMakeに対して設定ファイル(CMakeLists.txt)が上位ディレクトリにあることを指示

    4-6.インストール

     次にソフトウェアをコンピュータ上で実行可能な状態にします。具体的にはコンパイルされたプログラム(バイナリ)をシステムの適切なディレクトリにコピーし、必要な設定を行います。

    1. sudo make install:Makefile(スクリプトの一種)の記述に従ってビルドやインストールを実施

      • install:makeコマンドで指定でき、Makefileに記述されたインストールの手順に従って、ビルドされたバイナリファイルやライブラリをシステムの適切な場所にコピーします。

    2. sudo ldconfig:プログラムが実行時に外部ライブラリをどこから読み込むかを管理

    [Terminal]
    sudo make install
    sudo ldconfig

    4-7.Pythonバインディング

     ビルドが終わってもOpenCVはimport できません。確認のためPython対話モードで"import cv2"を実施してもエラーが出ます。理由としてはビルドしたOpenCVとPythonがうまくつながっていないためです。

    画像

     Pythonバインディングとはプログラミング言語間で関数やオブジェクトを共有するためのインターフェースです。Pythonバインディングすることで、PythonからC++で書かれたOpenCVを直接呼び出すことが出来ます。

     4-7-1.Soファイルの参照先確認

     前節のビルドが完了するとsoファイル(cv2.cpython-39-aarch64-linux-gnu.so)が生成されます。これの場所をlsコマンドで探していきます。

    [Terminal]
    ls /usr/
    ls /usr/local/
    ls /usr/local/lib/
    ls /usr/local/lib/python3.9
    ls /usr/local/lib/python3.9/site-packages/
    ls /usr/local/lib/python3.9/site-packages/cv2

     本手順×同じPythonのVersionであれば”ls /usr/local/lib/python3.9/site-packages/python3.9”内にsoファイルが確認できるはずです。

    画像

     4-7-2.Pythonバインディングの実行

     Soファイルの場所を確認出来たら下記手順でPythonバインディングを実施します。

    1. ls:作業ディレクトリをSoファイルのディレクトリに変更

      • 変更後にlsコマンドでsoファイルがあることを再度確認した方が良い

      • cmakeコマンドの”-D CMAKE_INSTALL_PREFIX=/usr/local”でインストール先のディレクトリを指定しているためこのPathとなる

    2. cp:Soファイルをコピー

      • cv2.cpython-35m-arm-linux-gnueabihf.soをcv2.soという名でコピー

    3. ls:作業ディレクトリをpythonのdist-packagesに変更

    4. ls:シンボリックリンクの作成

      • "ln -s"コマンドを使用して2でコピーした"cv2.so"に対してpythonフォルダ内のcv2.soがシンボリックリンクとなります。

    [Terminal]
    cd  /usr/local/lib/python3.9/site-packages/cv2/python-3.9/
    sudo cp cv2.cpython-35m-arm-linux-gnueabihf.so cv2.so
    cd  /usr/local/lib/python3.9/dist-packages/
    sudo ln -s /usr/local/lib/python3.9/site-packages/cv2/python-3.9/cv2.so cv2.so
    画像

     これで設定は完了です。

    4-8.スワップファイルの再設定

     メモリ不足対策で増加させたスワップファイルを元に戻しCtrl+Xで上書き保存で閉じます。
     dphys-swapfileを再起動(stop/start)して設定変更の適用を確認します。

    [Terminal]
    sudo nano /etc/dphys-swapfile
    
    sudo /etc/init.d/dphys-swapfile stop
    sudo /etc/init.d/dphys-swapfile start
    free -m
    画像

    4-9.OpenCVの動作確認

     Pythonを対話モードで起動してOpenCV(cv2)がインストールされていることを確認します。Versionが確認できれば終了となります。

    [Terminal]
    python3
    import cv2
    cv2.__version__
    [OUT]
    4.8.1

    【Python対話モードの注意点】
     Rasberry Pi OSにはPython2系と3系が入っています。よって意図的にpython3系を指定(pythonではなくpython3)しないと下記のようなエラーが発生します。

    画像

    5.コラム:OpenCV×カメラモジュールの相性

    5-1.状況説明

     紹介した環境構築を実行することで下記を確認できており、通常であればWebカメラを起動する下記スクリプトは動作するはずです。

    1. OpenCV:Pythonでimport cv2でエラーなく実行できる

    2. カメラ接続:libcamera-helloで動画撮影できることを確認

    [pythonスクリプト]
    import cv2
    import numpy as np
    
    cap = cv2.VideoCapture(0) #Webカメラを開く
    
    while True:
        ret , frame = cap.read() # カメラから画像を取得
        frame =cv2.resize(frame, (500,300)) # 画像サイズを変更
        cv2.imshow('OpenCV Web Camera', frame) # 画面に表示
        k = cv2.waitKey(1) # 1msec待つ
        if k ==27 or k ==13:
            break # ESC or Enterを押したらループ終了
            
    cap.release()
    cv2.destroyAllWindows()

     私の環境では更なるトラブルとして、カメラはオープンできているけど読み取れないトラブル(cap.read()が値を返さない)があります。

    [Terminal]
    python3
    import cv2
    cap = cv2.VideoCapture(0)
    cap.isOpened()
    cap.read()
    [OUT]
    True
    (False, None)
    画像

     原因としてはおそらく新しいカメラモジュールとOpenCVの相性が良くない可能性があります。次章では対策としてPiCamera2を使用していきます。

    5-2.カメラ情報の確認方法

     カメラの接続状態を確認します。下記確認しましたが、対策案がでなかったOpenCVの”cv2.VideoCapture(0)※”の使用はあきらめました。
    ※番外編ではUSBカメラを使った事例を紹介しており、その場合は使用可

    1. vcgencmd get_camera:カメラの接続情報確認

      • 接続を確認(※libcameraは特殊なため注意)

    2. groups:カメラアクセスの権限確認

      • videoグループが存在している

    3. ls /dev/video*:カメラデバイスの確認

    4. cv2.VideoCapture(0, cv2.CAP_V4L2):バックエンドで実行

      • ハードウェアアクセラレーションだとエラーの可能性がある

      • V4L2バックエンドを明示的に使用

      • 念のため0でなく別の数値でも実行してみた(※効果無し)

    [Terminal]
    vcgencmd get_camera
    groups
    ls /dev/video*
    画像
    [Terminal]
    import cv2
    cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
    cap.read()
    画像

    6.初級編:Webカメラで利用

    6-1.設計思想

     現在の環境仕様より、結論は「Picamera2を利用してカメラ情報を取得してOpenCVに出力」させます。環境の状態は下記の通りです。

    1. カメラモジュールv3を使用:OpenCVと相性が悪いのかわからないがcv2.VideoCapture(0)で画像データが取得できない

    2. Rasberry Pi OS 64bit:Picameraライブラリは使用できないため、Picamera2を使用

    3. OSがDebian Bullseys:raspi-configのInterface Optionにカメラがなく、レガシーカメラが設定できない。またレガシー設定のままだとカメラモジュールv3が使用できない

    6-2.PiCamera2×OpenCVでの撮影

     まずはWebカメラとして使用してみます。スクリプトは下記の通りであり、要点は以下の通りです。

    1. カメラモジュールv3からの画像はPiCamera2を用いてNumpy配列で取得

    2. OpenCVのRGBの取り扱いが通常と異なるためcv2.cvtColorで次元を変更

    3. ウィンドウはEscかEnterキーで抜けられるように設定

    [webcam.py]
    import cv2
    import picamera2
    import numpy as np
    from PIL import Image
    import time
    
    #PiCamera2の設定・初期化
    camera = picamera2.Picamera2()
    camera.start() #カメラの起動
    
    # OpenCVウィンドウ設定
    cv2.namedWindow("PiCamera Preview") #タイトルを指定
    
    # カメラから画像を取得
    while True:
        imgRGB_array = camera.capture_array("main") #画像をNumpy配列で取得
        imgBGR_array = cv2.cvtColor(imgRGB_array, cv2.COLOR_RGB2BGR) #RGBからBGRに変換
    
        cv2.imshow("PiCamera Preview", imgBGR_array) #ウィンドウに表示
    
        k = cv2.waitKey(1) #キー入力を1ms待つ
        if k == 27 or k == 13:
            break #ESCまたはEnterキーが押されたら終了
    
    camera.stop() #カメラを解放
    cv2.destroyAllWindows() #ウィンドウを破棄
    [Terminal]
    python3 webcam.py
    
    [OUT]
    画像

    6-3.番外編:USB-Webカメラの利用

     「OpenCVは使えるけどPicamera2を覚えるのがめんどくさい」という方は、Rasberry Piの「カメラモジュールではなくUSBカメラを使用」する手法があります。この手法が初学者にとって一番楽だと思います。

     今回はテスト用のため、ロジクールの安いUSBカメラを選定しました。USBはUSB3(青色)に接続します。

    画像

     事前にWebカメラのUSB接続前後で”ls /dev/video*”コマンドを実行し、どのvideoが所定のデバイスファイルか当たりを付けます。
     次に”v4l2-ctl --list-devices”で接続されているVideo4Linuxデバイスの一覧を表示し、カメラの番号を確認します。本コマンドはv4l2-utilsパッケージのため、インストールしていない人は事前に”sudo apt install v4l-utils”が必要となります。

    [Terminal]
    ls /dev/video*
    v4l2-ctl --list-devices
    画像
    [OUT]
    bcm2835-codec-decode (platform:bcm2835-codec):
    	/dev/video10
    	/dev/video11
    	/dev/video12
    	/dev/video18
    	/dev/video31
    	/dev/media3
    
    bcm2835-isp (platform:bcm2835-isp):
    	/dev/video13
    	/dev/video14
    	/dev/video15
    	/dev/video16
    	/dev/video20
    	/dev/video21
    	/dev/video22
    	/dev/video23
    	/dev/media0
    	/dev/media2
    
    unicam (platform:fe801000.csi):
    	/dev/video2
    	/dev/video3
    	/dev/media1
    
    rpivid (platform:rpivid):
    	/dev/video19
    	/dev/media4
    
    UVC Camera (046d:0825) (usb-0000:01:00.0-1.2):
    	/dev/video0
    	/dev/video1
    	/dev/media5

     結果としてUSBカメラはvideo0に該当すると判断できます。

     "cv2.VideoCapture(0)"としてOpenCVのスクリプトを作成しました。結果としてPiCameraを使用しなくても、OpenCVからカメラを認識できることを確認しました。

    [Terminal]
    import cv2
    
    cap = cv2.VideoCapture(0) # 0番目のカメラを開く
    if not cap.isOpened():
        print("Could not open camera")
        exit()
    
    while True:
        ret, frame = cap.read()
        if not ret:
            print("Failed to grab frame")
            continue
    
        frame = cv2.resize(frame, (500, 300)) # 画像サイズを変更
        cv2.imshow('OpenCV Web Camera', frame) # 画面に表示
        k = cv2.waitKey(1) # 1msec待つ
        if k == 27 or k == 13: # ESC or Enterを押したらループ終了
            break
    
    cap.release()
    cv2.destroyAllWindows()
    [Terminal]
    python3 wcam.py
    画像

    7.応用編1:仮想環境下でOpenCV使用

     今まではシステム環境で環境構築してきましたが、仮想環境でも環境を作りたいニーズは発生するはずです。しかし実装しようとするとかなりしんどいため参考までに紹介します。

    • システム環境:Rasberry Pi本来の環境

    • 仮想環境:システム環境とは分離された別環境

      • Miniforgeをインストールして仮想環境を作成

    画像

    7-1.仮想環境下での課題

     前述の通り本環境下では直接"cv2.VideoCapture(0)"でカメラから画像をキャプチャできないためPiCamera2を使用しました。システム環境ではすんなり?いったのですが、仮想環境下ではうまくいきませんでした。

     PiCamera2とは別手法で仮想環境からカメラにアクセスする必要があります。今回は「subprocessを使用してターミナル上からlibcameraを使用」で対応しました。

    7-2.設計思想:subprocessでlibcameraにアクセス

     今回の設計思想としては下記の通りです。エラーで詰まったところも含めて記載しました。用語・APIの詳細は別記事をご確認ください。

    1. カメラモジュールへのアクセスはlibcameraを使用

    2. libcameraの使用は、subprocessを用いてコード内から制御

      • commandを変数で一括にまとめて処理

    3. libcameraコマンドはlibcamera-vidを使用

      • libcamera-jpegだと10sくらいでいったん止まる(原因不明)

      • --timeout:0にすることで動画(無限時間)として処理可能

      • --nopreview:設定するとlibcameraのウィンドウ表示を省略

      • --framerate:設定しないとカクカクになる。公式だと50fpsくらいまでは出る??

      • --codec:YUV420かMJPEGを指定

    4. libcamera-vidのコーデックはYUV420ではなくMJPEGを選択

      • YUV420(デフォルト)だと砂嵐の画面になり撮影できない

    7-3.実装編

     実装は以下の通りです。正直細かい部分が理解できていないため、説明は省略します。

    • cv2.imdecode()で既にBGR(OpenCVが扱う状態)になっているためRGBへの変換は不要

    [wcam_env.py]
    import cv2
    import numpy as np
    import subprocess
    
    # 解像度の設定
    width = 640
    height = 480
    
    # libcamera-vidコマンドの設定
    command = [
        "libcamera-vid",
        "--timeout", "0",  # 無限に動画を取得
        "--nopreview",  # プレビューを無効
        "--width", str(width),  # 幅の設定
        "--height", str(height),  # 高さの設定
        "--framerate", "30",  # フレームレートの設定
        "--codec", "mjpeg",  # MJPEGコーデックを使用
        "-o", "-"  # 標準出力にフレームを出力
    ]
    
    # libcamera-vidプロセスを開始
    process = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=10**8)
    
    # OpenCVウィンドウの設定
    cv2.namedWindow("PiCamera Preview", cv2.WINDOW_AUTOSIZE)
    
    # フレームを格納するためのメモリバッファ
    frame_buffer = bytearray()
    
    try:
        while True:
            # 標準出力からデータを読み込む
            data = process.stdout.read(4096)
            if not data:
                break
    
            # バッファにデータを追加
            frame_buffer += data
    
            # JPEGのフレームが終わる0xFF 0xD9(EOIマーカー)を探す
            a = frame_buffer.find(b'\xff\xd8')  # SOIマーカー
            b = frame_buffer.find(b'\xff\xd9')  # EOIマーカー
    
            if a != -1 and b != -1 and b > a:
                jpg = frame_buffer[a:b+2]  # JPEGフレームの取り出し
                frame_buffer = frame_buffer[b+2:]  # 次のフレームのためにバッファをクリア
    
                # JPEGフレームをデコードして表示
                frame = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
                cv2.imshow("PiCamera Preview", frame)
    
                # 1ミリ秒待って、ESCかEnterが押されたら終了
                if cv2.waitKey(1) & 0xFF in [27, ord('q')]:
                    break
    
    finally:
        # プロセスとウィンドウのクリーンアップ
        process.terminate()
        cv2.destroyAllWindows()
    [Terminal]
    python3 wcam_env.py
    画像

    8.応用編2:AIによる顔検出

     OpenCVで作成した記事と同じことをできるか確認しました。

    8-1.環境構築:学習済み検出器モデルの取得

     顔を検出する検出器の学習済みモデルを下記手順で実行します。

    1. GitHubのフォルダをgit cloneで作業ディレクトリにコピー

    2. lsコマンドで正常終了であること、およびフォルダ名を確認

    3. cpコマンドで”opencv/data/haarcascades/haarcascade_frontalface_alt.xml ”を作業ディレクトリにコピー

    [Terminal]
    git clone https://github.com/opencv/opencv.git
    ls
    cp opencv/data/haarcascades/haarcascade_frontalface_alt.xml haarcascade_frontalface_alt.xml
    画像

    8-2.顔検出のPythonスクリプト

     顔検出コードを作成します。処理としては下記の通りです。

    1. 前処理(グレースケール化)

    2. cascade.detectMultiScale()で顔を検出して座標を出力

    3. 取得した顔座標にcv2.rectangle()で枠線を引く 

     顔の検出対象は(仕事猫は顔として認識されなかったので)写真素材サイト-ぱくたそのモデル一覧をカメラで認識させてみました。

    [detectface.py]
    import cv2
    import picamera2
    import numpy as np
    import time
    
    # PiCamera2の設定・初期化
    camera = picamera2.Picamera2()
    camera.start()  # カメラの起動
    
    # OpenCVウィンドウ設定
    cv2.namedWindow("PiCamera Preview")  # タイトルを指定
    
    # カスケードファイルから分類器(検出器)を作成
    cascade_file = "haarcascade_frontalface_alt.xml"
    cascade = cv2.CascadeClassifier(cascade_file)
    
    # カメラから画像を取得
    while True:
        imgRGB_array = camera.capture_array("main")  # 画像をNumpy配列で取得
        imgBGR_array = cv2.cvtColor(imgRGB_array, cv2.COLOR_RGB2BGR)  # RGBからBGRに変換
        img_gray = cv2.cvtColor(imgBGR_array, cv2.COLOR_BGR2GRAY)  # グレースケール変換
    
        # 顔認識
        face_list = cascade.detectMultiScale(img_gray, minSize=(30, 30))
    
        # 検出した顔に枠を描画
        for (x, y, w, h) in face_list:
            color_frame = (0, 255, 0)  # 枠の色{緑色:(0,255,0)}
            pen_w = 3  # 枠の太さ
            cv2.rectangle(imgBGR_array, (x, y), (x + w, y + h), color_frame, thickness=pen_w)
    
        cv2.imshow("PiCamera Preview", imgBGR_array)  # ウィンドウに表示
    
        k = cv2.waitKey(1)  # キー入力を1ms待つ
        if k == 27 or k == 13:
            break  # ESCまたはEnterキーが押されたら終了
    
    camera.stop()  # カメラを解放
    cv2.destroyAllWindows()  # ウィンドウを破棄
    [Terminal]
    python3 detectface.py
    画像

    8-3.番外編:仮想環境×libcamera

     同様のことを7章と同様に仮想環境から実施しました。

    [detect_env]
    import cv2
    import numpy as np
    import subprocess
    
    # 解像度の設定
    width = 640
    height = 480
    
    # カスケードファイルから分類器(検出器)を作成
    cascade_file = "haarcascade_frontalface_alt.xml"
    cascade = cv2.CascadeClassifier(cascade_file)
    
    # libcamera-vidコマンドの設定
    command = [
        "libcamera-vid",
        "--timeout", "0",  # 無限に動画を取得
        "--nopreview",  # プレビューを無効
        "--width", str(width),  # 幅の設定
        "--height", str(height),  # 高さの設定
        "--framerate", "50",  # フレームレートの設定
        "--codec", "mjpeg",  # MJPEGコーデックを使用
        "-o", "-"  # 標準出力にフレームを出力
    ]
    
    # libcamera-vidプロセスを開始
    process = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=10**8)
    
    # OpenCVウィンドウの設定
    cv2.namedWindow("PiCamera Preview", cv2.WINDOW_AUTOSIZE)
    
    # フレームを格納するためのメモリバッファ
    frame_buffer = bytearray()
    
    try:
        while True:
            # 標準出力からデータを読み込む
            data = process.stdout.read(4096)
            if not data:
                break
    
            # バッファにデータを追加
            frame_buffer += data
    
            # JPEGのフレームが終わる0xFF 0xD9(EOIマーカー)を探す
            a = frame_buffer.find(b'\xff\xd8')  # SOIマーカー
            b = frame_buffer.find(b'\xff\xd9')  # EOIマーカー
    
            if a != -1 and b != -1 and b > a:
                jpg = frame_buffer[a:b+2]  # JPEGフレームの取り出し
                frame_buffer = frame_buffer[b+2:]  # 次のフレームのためにバッファをクリア
    
                # JPEGフレームをデコードして表示
                frame = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
                
                # 顔認識
                img_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # グレースケール化
                face_list = cascade.detectMultiScale(img_gray, minSize=(30, 30))
    
                # 検出した顔に枠を描画
                for (x, y, w, h) in face_list:
                    color_frame = (0, 255, 0)  # 枠の色{緑色:(0,255,0)}
                    pen_w = 3  # 枠の太さ
                    cv2.rectangle(frame, (x, y), (x + w, y + h), color_frame, thickness=pen_w)
    
                # OpenCVでフレームを表示
                cv2.imshow("PiCamera Preview", frame)
    
                # 1ミリ秒待って、ESCかEnterが押されたら終了
                if cv2.waitKey(1) & 0xFF in [27, ord('q')]:
                    break
    
    finally:
        # プロセスとウィンドウのクリーンアップ
        process.terminate()
        cv2.destroyAllWindows()
    [Terminal]
    python3 detect_env
    画像


    参考資料

    あとがき

     久しぶりの環境構築は辛いし面白くない。Rasberry PiもOSが変わったりカメラモジュールが変わったり、OpenCVもVersion3系と4系が変わったりで昔の情報が結構役に立たないのが多くて辛かった。


     
     

    KIYO

     
     
    普段は製造業で企画/開発/設計しております。記事はプログラミング・機械学習、IoT関係の記事をメインで作成し、なるべく1つの記事で知りたいことを網羅していきます。内容は学術的より実装・アウトプット(ほしくなるもの)を重視して作成しています。 面白そうな仕事があればやりたいです!

    あなたへのおすすめ