Python独習!

習得したPython知識をペイフォワード

PythonでBASLERカメラのエミュレーターを設定する

basler-python-pypylon
Source of photo: https://github.com/basler/pypylon As of Sep 22, 2021

概要

システム環境変数に"PYLON_CAMEMU=1"と設定してあげると、カメラエミュレーションが有効になり、実際にカメラがPCに接続されていなくてもpypylonで作ったスクリプトを動かくすことができる。
Camera Emulation | Basler Product Documentation

しかし、システム環境変数をその都度書き換えるのは面倒だし、誤って他の変数を書き換えてしまう恐れもある。もっと手軽にカメラエミュレーションを有効にする方法がある。
その方法は、次の1行をスクリプトに入れるだけ。

os.environ["PYLON_CAMEMU"] = "1"

※実際にOSのシステム環境変数を書き換えるわけではなく、このスクリプトの中でだけ効果がある、らしい

サンプルプログラム

結果

エミュレーションが有効になっていると、おなじみの縞々が表示される。
basler-python-pypylon

ソースコード

1行目と8行目以外は、GitHubから引用している。
Source of script: https://github.com/basler/pypylon/blob/master/samples/guiimagewindow.py As of Sep 22, 2021

import os
import time

from pypylon import pylon
from pypylon import genicam


os.environ["PYLON_CAMEMU"] = "1"

try:
    imageWindow = pylon.PylonImageWindow()
    imageWindow.Create(1)

    # Create an instant camera object with the camera device found first.
    camera = pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateFirstDevice())

    # Print the model name of the camera.
    print("Using device ", camera.GetDeviceInfo().GetModelName())

    # Start the grabbing of c_countOfImagesToGrab images.
    # The camera device is parameterized with a default configuration which
    # sets up free-running continuous acquisition.
    camera.StartGrabbingMax(10000, pylon.GrabStrategy_LatestImageOnly)

    while camera.IsGrabbing():
        # Wait for an image and then retrieve it. A timeout of 5000 ms is used.
        grabResult = camera.RetrieveResult(5000, pylon.TimeoutHandling_ThrowException)

        # Image grabbed successfully?
        if grabResult.GrabSucceeded():
            imageWindow.SetImage(grabResult)
            imageWindow.Show()
        else:
            print("Error: ",
                  grabResult.ErrorCode)  # grabResult.ErrorDescription does not work properly in python could throw UnicodeDecodeError
        grabResult.Release()
        time.sleep(0.05)

        if not imageWindow.IsVisible():
            camera.StopGrabbing()

    # camera has to be closed manually
    camera.Close()
    # imageWindow has to be closed manually
    imageWindow.Close()

except genicam.GenericException as e:
    # Error handling.
    print("An exception occurred.")
    print(e.GetDescription())

PythonでUSBカメラのスナップショットを大量に取得する!

USBカメラから自動で大量に画像を取得するプログラムを作った。

用途

機械学習用に大量に画像データを取得するときなど。

プログラムの概要

  • 実行すると、フォルダダイアログが立ち上がるので保存先を指定する。保存先に自動的に新規フォルダが作成される。名前にその時の時刻が含まれる。
  • そのまま指定した枚数の撮像&保存が始まる。
  • 枚数の指定は以下の"10"の部分。上限を1000にしてある。
acquire_save(10, specify_destination())
  • While文の中のSleepを調整することで、撮像の間隔を変えられる。

ソースコード

#python 3.8.1
#opencv-contrib-python 4.2.0.32

"""
This script acquires and saves a large amount of images from a camera.
"""

import sys
import os
from time import sleep
from datetime import datetime
import tkinter as tk
from tkinter import filedialog

import cv2


class MyException(Exception):
    pass


def specify_destination():

    #Define name of a folder where images will be stored. 
    now = datetime.now()
    new_folder_name = 'Images_{:%Y%m%d%H%M%S}'.format(now)

    try:
        print("Specify a directory to make a new folder to store images.")

        #Let users specify a directory where a new folder will be made.    
        idir = os.getenv("HOMEDRIVE") + os.getenv("HOMEPATH") + "\\Desktop"
        dst = filedialog.askdirectory(initialdir = idir)

        #If users canceled when specifying the directory, raise MyException.
        if dst == "":
            raise MyException("Cancel clicked!")

        #Then, make the folder for storing images.
        dst_path = os.path.join(dst, new_folder_name)
        os.mkdir(dst_path) 

    except MyException as e:
        print(e)
        sys.exit()

    except Exception:
        print("Unexpected error occurred.")
        sys.exit()

    else:
        print("The folder was successfully created.")
       
    return dst_path


def acquire_save(num_acquisition, save_path):
    try:     
        #Make a camera instance.
        cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
 
        #Check if the camera instance is sure to be opened.
        if not cap.isOpened():
            raise MyException("Camera Not Found!")

        #Check if num_acquisition is integer.
        if not isinstance(num_acquisition, int):
            raise MyException("Num of acquisitoin is wrong.")

        #Check if num_acquisition is less than 1000.
        if num_acquisition > 1000:
            raise MyException("Too much number you requested!")

        #Check if save_path is sure to exist.
        if not os.path.isdir(save_path):
            raise MyException("The directory does not exist.")

        print("Start to acquire and save images repeatedly.")    

        num = num_acquisition
        while num > 0:
            print(num)
            sleep(0.1)

            _, frame = cap.read()

            #Define name of images including sequential number.
            file_name = "sample_{:04}.jpg".format(num)
            cv2.imwrite(os.path.join(save_path, file_name), frame)

            num -= 1
    
    except MyException as e:
        print(e)
        sys.exit()

    except Exception:
        print("Unexpected error occurred.")
        sys.exit()

    else:
        print("Successfully completed!")

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


if __name__ == '__main__':
    root = tk.Tk()
    root.withdraw()
    acquire_save(10, specify_destination())

ちょっとした気づき

except句にsys.exit()が含まれていても、ちゃんとfinally句は実行されるようだ。

Pythonでスクリプト実行中に対話モードに入りたい

スクリプトを実行しているときに、ある特定のところで止めて対話モード(Python Shell)に入るにはどうすればよい?
code.InteractiveConsoleを使うと良い。

概要

スクリプトはひとたび実行すると終わるまで手出しできない。手出しというのは、変数の値を見たり、中間結果を使って検算したり。初めて使うモジュールを利用するときはこの「手出し」をしたい場面が多々ある、と思う。
そこで見つけたのが、次の一行をスクリプトの止めたいところに書いておくだけ、というもの。この行が実行されると変数が保持されたままの状態でPython Shellがつかるようになる。

#Python3.8.1
import code
code.InteractiveConsole(globals()).interact()

参考サイト

まんま同じ内容だが、Python3.8でも使えますよ、というアップデートの意味を込めて自分の記事に使わせていただいた。
sucrose.hatenablog.com

ラズパイ4でスライドショーを自動起動させる!

Raspberry PiにかまけてPython学習が進んでいませんが、今回もハマったことの忘備録として残しておきます。

やりたいこと

流行りのデジタルサイネージをやってみる。壁掛けモニターにラズパイを指すだけの簡単な構成で実現したい。
raspberry_pi_4_digital_signage

実現方法はおおまかに、表示する内容を予めPDF形式で用意しておき、ラズパイに移す。そして、PDFをスライドショーとして流すためにOkularを使う。あとラズパイが起動したのと同時にスライドショーが流れるように自動起動の設定をする。
以下でそれぞれ詳細に説明していく。
パーミッション関係はまだよく分かっていないので記載を割愛する。とりあえず『すべて許可』にしておけば動く。

環境

バイスバージョン : Raspberry Pi 4 Model B Rev
Raspbianのバージョン : 10.7

PDFファイルをラズパイに送る

詳細は↓↓の記事を参考に。
greenhornprofessional.hatenablog.com

スライドショー表示できるようにする

Okularのインストール

Okularは数あるPDFビューワーソフトの内の一つ。
以下のコマンドでインストールする。

sudo apt-get update
sudo apt-get install okular

Okularの起動

以下のコマンドで起動する。もしくは、スライドショーで流したいPDFファイルを右クリックしてコンテキストメニューからOkularを選択する。

okular

Okularの設定

PDFの1ページ目から5秒おきにページ進めていき、最終ページの次はまた1ページ目に戻るというようにループさせる。

  1. >設定 >Okularを設定 >全般 >外観 >"ヒントと情報メッセージを表示する" ⇒ OFF
  2. >設定 >Okularを設定 >全般 >外観 >"Display document title in titlebar if available" ⇒ OFF
  3. >設定 >Okularを設定 >プレゼンテーション >ナビゲーション >"一定時間ごとに進む" ⇒ 5秒
  4. >設定 >Okularを設定 >プレゼンテーション >ナビゲーション >"最後まで行ったら最初のページに戻る" ⇒ ON
  5. >設定 >Okularを設定 >プレゼンテーション >外観 >"進捗インジケータを表示する" ⇒ OFF

XScreenSaverのインストール

XScreenSaverはディスプレイをコントロールするソフト。ディスプレイが省エネモードに入って消えないようにする。
以下のコマンドでインストールする。

sudo apt-get update
sudo apt-get install xscreensaver

XScreenSaverの設定

ラズパイのスタートメニュー >設定 >スクリーンセイバー >表示モード >モード >"セーバーを無効にする" を選択

スライドショーが自動起動するようにする

ラズパイが起動するのと同時にスライドショーも自動で起動されるようにする。

シェルスクリプトの作成

Okularの起動からスライドショー実行までをスクリプトで記述する。スクリプトファイルの保存場所は /opt が良い、らしい。

以下のコマンドでスクリプトファイルを新規作成する。

sudo touch /opt/slideshow.sh

テキストエディタで開く。

sudo mousepad /opt/slideshow.sh

スクリプトを以下の通り記述して保存する。
※/home/pi/share に対象となるPDFファイル(ここではslide.pdf)が置かれているものとする。

#!/bin/bash

export DISPLAY=:0.0

export XAUTHORITY="/home/pi/.Xauthority"

okular --presentation /home/pi/share/slide.pdf

サービス設定ファイルの作成

上記のスクリプトファイルを自動実行させるための設定ファイルを作成する。同じく、これの保存場所は /opt が良い、らしい。

以下のコマンドで設定ファイルを新規作成する。

sudo touch /opt/autoslideshow.service

テキストエディタで開く。

sudo mousepad /opt/autoslideshow.service

設定ファイルを以下の通り記述して保存する。

[Unit]
Description=run slideshow.sh

[Service]
ExecStart=/opt/slideshow.sh

Type=simple

[Install]
WantedBy=multi-user.target

自動実行サービスとして登録

以下のコマンドで登録する。

sudo systemctl enable /opt/autoslideshow.service

登録を解除したい時は以下のコマンドで。

sudo systemctl disable /opt/autoslideshow.service

動作確認

ラズパイを再起動して、スライドショーが自動実行されれば成功!
上手く行かないときは以下のサイトを参考のこと。
Raspberry PiでプログラムをOS起動時に実行させる [Systemd] - チカラの技術
Python - raspiでsystemdを使っての自動実行ができない|teratail
QXcbConnection: Could not connect to display | Qt Forum

ラズパイ4とWin10パソコンでフォルダを共有する!

Raspberry Pi上でPythonを使って色んなことをしてみたい。そのためにちょっとずつRaspberry Piの環境を整えています。その中で、Windowsパソコンに保管されているデータをどうやったらRaspberry Piに送ることができるか?ちょっとハマったので忘備録を残しておきます。

やりたいこと

パソコン(Windows10)で作成したOpencvのカスケード分類器(.xmlファイル)をRaspberry Piに移したい。
OpenCVのカスケード分類器を自作して画像認識

さらっと調べたところ、LANケーブルで接続してフォルダ共有するのが簡単そう。以下のイラストはその全容のイメージ。
raspberrypi_samba_windows10

環境

バイスバージョン : Raspberry Pi 4 Model B Rev
Raspbianのバージョン : 10.7

手順

  1. 共有フォルダを作成
  2. Sambaをインストール
  3. ディレクトリのパーミッション設定
  4. 共有設定
  5. ipアドレス設定
  6. 確認

共有フォルダを作成

パソコンと共有するディレクトリを作成する。ここではpiの直下にshareという名前のディレクトリを作成し、そこを共有すべく次のステップから色々設定していく。

sudo mkdir /home/pi/share

Sambaをインストール

SambaとはLinux系OSをWindows Networkに参加させるためのソフトのこと。これをインストールする。
Samba | Linux技術者認定 LinuC | LPI-Japan

sudo apt-get update
sudo apt-get install samba

ディレクトリのパーミッション設定

正直なところパーミッションのスコープを理解できていない。とりあえず動かしたかったので「全員にフル開放」で設定した。あとでちゃんと理解して修正する。

  • etcに移動してから、
sudo chmod 777 samba
  • piに移動してから、
sudo chmod 777 share

共有設定

ここも同じくpublicやguestのスコープを理解できていない。とりあえず動かしたかったので「全員にフル開放」で設定した。あとでちゃんと理解して修正する。
/etc/samba/cmb.confを開き、末尾に以下を追記する。

[share]
comment = share
path = /home/pi/share
public = yes
read only = no
browsable = yes
force user = pi
writable = yes
guest ok = yes
guest only = yes

ipアドレス設定

ifconfig

  ⇒ (例) アドレス : 192.168.0.1 マスク : 255.255.0.0

   (例) アドレス : 192.168.0.2 マスク : 255.255.0.0

確認

パソコンのエクスプローラーのアドレスバーにshareディレクトリのパスを入力して、shareが覗けるか確認する。

(例) \\192.168.0.1\share


PythonでWebカメラの映像から円(硬貨)をリアルタイム検出する!

画像から円形の物体を検出したい
opencvHoughCirclesを使うと良い。ただし、公式ドキュメントが「半径が安定しない」と注意事項を挙げている。半径の測定には向かない。

サンプルプログラム

Webカメラで複数の硬貨を撮影し、円検出する。撮像→検出のサイクルを繰り返すことでリアルタイム処理っぽくなっている。

Webカメラ

Baffalo製のBSW32KM01Hという10年も前のUSBカメラを使用した。最近のUSBカメラならもっときれいに映るかも。

結果

すべての硬貨を検出(緑色枠で囲うことが)できた。硬貨の置き方を変えても検出が追従できているし、それに伴い照明の当たり方が変わっても問題がない。ただし、HoughCirclesのパラメーターの合わせ込みが難しいのが難点。また、撮影する距離や角度が変わると劇的に検出しなくなるため、安定した撮像システムにしか馴染まない手法だと思う。



ソースコード

import sys
#from time import sleep
import cv2

#Webカメラのインスタンス作成
#引数はカメラ番号。0番はラップトップ内蔵カメラ。
cap = cv2.VideoCapture(1)

#Webカメラのインスタンス作成に失敗したらプログラム終了
if not cap.isOpened():
    print("Camera Not Found")
    sys.exit()

#キャプチャと円検出を繰り返す
while True:
#    sleep(1)
    #キャプチャ取得
    _, frame = cap.read()
    img = frame.copy()

    #前処理
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.medianBlur(gray ,7)
    gray = cv2.equalizeHist(gray)

    #円検出
    circles = cv2.HoughCircles(gray,
                               cv2.HOUGH_GRADIENT,
                               dp=4,
                               minDist=50,
                               param1=400,
                               param2=100,
                               minRadius=30,
                               maxRadius=50)

    #円の検出に成功したらオリジナルのimgに円を描画
    if circles is not None:
        for i in circles[0].astype('uint16'):
            cv2.circle(img,(i[0],i[1]),i[2],(0,255,0),2)
    cv2.imshow('Detect', img)

    k = cv2.waitKey(5) & 0xFF
    if k == 27:
        break

cv2.destroyAllWindows()
cap.release()

VS Codeの Module 'cv2' has no 'imshow' member の解消

Pythonの開発環境として Visual Studio Code を使っていると、opencvに関する警告が出る。
python-vscode-opencv

無視しても問題ないが煩わしいので解消したい。その方法を忘備録として残しておく。

環境

Python 3.8.1
opencv-python 4.1.2.30
Pylint 2.6.0
Visual Studio Code 1.54.2

手順

  1. File > Preference > Settings を開く
  2. 検索バーに "Python.linting.pylintArgs" と入力する
  3. ヒットした候補の中から Python > Linting: Pylint Argsを選択する
  4. [Add Item]ボタンをクリックするとテキストボックスが出てくるので、そこに --extension-pkg-whitelist=cv2 と入力する
    python-vscode-opencv
  5. [OK]ボタンを押すと登録される
    python-vscode-opencv
/* -----codeの行番号----- */