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

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

Pythonで48bitカラー画像を読み込んでRGBの単色16bit画像に分解する

Pythonで48bitカラー画像を諧調を維持したまま取り扱うにはどうしたらよいか?
imageioモジュールを使う。PillowOpencvは対応していないらしい。

※画像フォーマットはTiffです。PNGでもできると説明している記事がありましたが、今回は試していません。
python - Resizing a 48bit PNG retaining its 48bits, without dropping it to a 24bit file - Stack Overflow

サンプルプログラム

概要

48bitカラー画像をRGBに分解して、16bitの単色画像(計3枚)として保存するプログラムです。GIMPやImageJでも同じことができます。
そもそも48bitカラーはモニターが対応していないと恩恵がないのですが、このサンプルプログラムのように単色化もしくはグレースケール化して画素値(数値)を取り扱う分には有用だと思っています。例えば、24bitカラーよりも照明の微小な変化を拾うことができます。

テスト画像として、左から右に向かって赤→緑→青というように変化する画像をGIMPで作成しました。48bitカラーのTiff形式です。
python_48bit_color

これをRGBに分解して保存すると、ちゃんと赤緑青の領域で分かれてるのがわかります。深さも16bitで狙い通りです。
python_48bit_color
python_48bit_color
※解像度が何故か1dpiになっているのかわかりません…

ソースコード

#python 3.8.1
#imageio 2.9.0

import imageio

img = imageio.imread('48bit.tif')

imageio.imwrite('16bit_Red.tif', img[:,:,0])
imageio.imwrite('16bit_Green.tif', img[:,:,1])
imageio.imwrite('16bit_Blue.tif', img[:,:,2])

Python IDLE を Raspbian 10 (Buster) にインストールする

手元にあったラズパイ(Raspberry Pi 2 Model B)のPythonを3.5.3 ⇒ 3.8.1にバージョンアップしたのですが、IDLEやThonnyに紐づいているバージョンが3.5.3のまま変わらず。しかし、Linuxはど素人のため自身で打開策を考えることができず…ネットでそれっぽい情報を見つけては試す、見つけては試すを繰り返すうちにOSが起動しなくなりました。

私の中での結論は「次のRaspbianを待つ」になりました。
Raspberry Pi Forumにも"system default python3 version" を変えるのは良くないというコメントがありました。
Whats the best way to install 3.8? - Raspberry Pi Forums
How do I install IDLE for Python 3.7.2? - Raspberry Pi Forums


結論を出したとはいえ、ラズパイが動かないままなのは困るのでOSをゼロからインストールしました。
手順を忘備録として残しておきます。

Raspberry Pi Imager 公式のイメージライター

www.itmedia.co.jp

バージョン確認

バイスのバージョン
$ cat /proc/device-tree/model

Raspberry Pi 2 Model B Rev 1.1

Raspbianのバージョン
$ cat /etc/debian_version

⇒ 10.7

Python3のバージョン
$ python3 -V

Python 3.7.3

Python IDLEインストール

Python IDLEが入っていないので別途インストールする必要があります。
インストールすると、IDLEにはPython 3.7.3が紐づきます。

$ sudo apt update
$ sudo apt install python3 idle3
/* -----codeの行番号----- */