Pythonで自動化!複数画像から輝度値を取得してテキストに出力する
- 画像から輝度値を抽出したいが、大量にあるので自動化したい。
filedialog.askopenfilenames
を使ってファイルダイアログで複数画像を一括で選択できるようにする。
解説
filedialog.askopenfilenamesの使い方
filetypes
で開くファイルの種類を指定、initialdir
でデフォルトパスを指定するだけ。ちなみに...namesのsを抜くとファイルが1個しか選択できない。複数選択したい場合はsを忘れないこと。files
はタプル型になり、選択したファイルのパスが格納される。なので、このタプルをfor文でアンパックしてあげれば一括処理ができる。
files = filedialog.askopenfilenames(filetypes = ***, initialdir = ***)
サンプルプログラム
デスクトップに保存されている赤、青、緑の3枚の画像から一括で輝度値を取得してテキストに出力するプログラムを作成した。
- 下の画像はファイルダイアログで画像を選択するところ。
- 下の画像は出力結果。※見やすいように後から編集で適当にスペースを入れてある。
ソースコード
# 35_GetIntensity_001.py # python 3.8.1 # opencv-python 4.1.2.30 # coding: utf-8 import os import datetime import cv2 import tkinter as tk from tkinter import filedialog def getBGR(path): imgBGR = cv2.imread(path, cv2.IMREAD_COLOR) #開いた画像からRGBを抽出する。 b = imgBGR.T[0].flatten().mean() g = imgBGR.T[1].flatten().mean() r = imgBGR.T[2].flatten().mean() return b, g, r def main(): root = tk.Tk() root.withdraw() #出力するファイルの名前を定義する。タイムスタンプ付き。 now = datetime.datetime.now() fo = 'ImageIntensityList_{:%Y%m%d%H%M%S}.txt'.format(now) #ファイルダイアログのデフォルトパスをデスクトップにする。 desktop_path = os.getenv("HOMEDRIVE") + os.getenv("HOMEPATH") + "\\Desktop" #対象ファイルのタイプをTiffに限定する。 target_format = [('TIF image', '*.tif')] #ファイルダイアログオブジェクトの作成。複数ファイル選択できる。 files = filedialog.askopenfilenames(filetypes = target_format, initialdir = desktop_path) #ファイルダイアログで選択したすべてのファイルを処理する。 with open(os.path.join(desktop_path, fo), mode='a') as f: f.write('File path,B,G,R,Intensity\n') for file in files: b, g, r = getBGR(file) i = 0.298912 * r + 0.586611 * g + 0.114478 * b #RGBから輝度値を算出 line = '{0},{1:.2f},{2:.2f},{3:.2f},{4:.2f}\n'.format(file, b, g, r, i) f.write(line) if __name__ == '__main__': main()
Pythonのlist.sort()とsorted(list)の違いは新たにリストが作られるかどうか
- list.sort()とsorted(list)の使い分けは?
- 対象となるリストを作り替えてもいい場合は list.sort() を、対象となるリストを残しておきたい場合は sorted(list) を使う
解説
list.sort()
sortメソッドは対象リストに直接変更を加えるソートを行う。これを「インプレイスでソートする」と言う。以下コード内の#1と#2が示すように、ソート前後でオブジェクトIDが変わっていないことがわかる。
data = ["Green", "Blue", "Red", "Yellow", "Orange", "Brown", "Black"] print("Object ID:", id(data)) #1 data.sort() print("Result :", data) print("Object ID:", id(data)) #2
Object ID: 86722536 #1 Result : ['Black', 'Blue', 'Brown', 'Green', 'Orange', 'Red', 'Yellow'] Object ID: 86722536 #2
sorted(list)
sorted関数は対象リストとは別の新しいリストを作り、ソートした要素を入れる。以下コード内の#3と#4が示すように、sort関数から返ってくるとオブジェクトIDが変わっていることからも分かる。
data = ["Green", "Blue", "Red", "Yellow", "Orange", "Brown", "Black"] print("Object ID:", id(data)) #3 print("Result :", sorted(data)) print("Object ID:", id(sorted(data))) #4
Object ID: 86722504 #3 Result : ['Black', 'Blue', 'Brown', 'Green', 'Orange', 'Red', 'Yellow'] Object ID: 86721800 #4
Tips ~Pythonのソートは安定している~
『安定』とは、ソート基準が同等のデータはソート前の順番が保持される、ということを意味する。細かいことだが、これを知らないとkeyパラメータに複数条件を入れる…なんて無駄なことをやってしまいかねない。と思ったら、過去に挙げた記事でまんまそれをやってしまっていた。
greenhornprofessional.hatenablog.com
文字数でソートする例
Green, Brown, Blackは5文字なので、同等データとなるがちゃんとソート前の順序が保持されていることがわかる。
data = ["Green", "Blue", "Red", "Yellow", "Orange", "Brown", "Black"] data.sort(key=len) print(data)
['Red', 'Blue', 'Green', 'Brown', 'Black', 'Yellow', 'Orange']
Pythonでシリアル通信!Arduinoに文字列を送って、Arduinoから文字列を受け取る!
シリアル通信でLチカしたプログラムをアップデート。Arduinoに特定の文字列を送ると、それに対応した返答を返すというもの。
RS232Cを備える測定器をリモートコントロールするための練習プログラム。
greenhornprofessional.hatenablog.com
結果
Pyhtonから送りたい文字列を標準入力で入力すると、Arduinoから意図したとおりの返答を受け取れている。
送受信は30~40msで完了している。この数字が妥当なのか?などの深堀はまた今度。
また、送信する文字列の末尾にセミコロンをつけていない場合は時間が増える。これはArduino側 Serial.readStringUnitl()
がセミコロンを待つようにしているためで、これのタイムアウト(恐らく1sec)が影響している。
<= abc; 計測開始 送信完了:0.008sec => Input is abc => Return is ABC 受信完了:0.037sec <= abc 計測開始 送信完了:0.010sec => Input is abc => Return is ABC 受信完了:1.020sec <= ddd; 計測開始 送信完了:0.009sec => Input is ddd => N/A 受信完了:0.033sec
プログラム
Arduino側
void setup(){ Serial.begin(115200); } void loop(){ if(Serial.available()>0){ String input = Serial.readStringUntil(';'); Serial.print("Input is "); Serial.println(input); if(strcmp(input.c_str(), "abc") == 0){ Serial.println("Return is ABC"); }else if(strcmp(input.c_str(), "def") == 0){ Serial.println("Return is DEF"); }else{ Serial.println("N/A"); } } }
Python側
#SimpleSerial_003.py import serial from time import sleep import time def decoder(byte): str_array = [] str_array = byte.decode().rstrip('\r\n').split('\r\n') for i in str_array: print("=>",i) def main(): ser = serial.Serial('COM3', 115200, timeout=0.1) sleep(2) command = input() print("<=", command) t0 = time.time() print("計測開始") ser.write(bytes(command, encoding='ascii')) ser.flush() t1 = time.time() print("送信完了:{:.3f}sec".format(t1 - t0)) while True: if ser.in_waiting > 0: data = ser.read_all() break decoder(data) t2 = time.time() print("受信完了:{:.3f}sec".format(t2 - t1)) ser.close() if __name__ == '__main__': main()
コメント:今回のプログラムではser.flush()
は時間に寄与せず。
参考サイト
Serial.readStringUntil()
を紹介している
【prog】Arduino・シリアルで文字列受信するには:ゆうがたの「特にコレと言って」 - ブロマガ
文字列を比較するためのstrcmp
の紹介。コレ結構重要。
【C言語入門】文字列を比較する方法(strcmp、strncmp) | 侍エンジニアブログ
strからchar*に変換する方法。コレもかなり重要。
【C++入門】string型⇔char*型に変換する方法まとめ | 侍エンジニアブログ
Pythonでシリアル通信してArduinoをLチカさせる
とある測定器をRS232Cでリモートコントロールしたくて、Pythonのシリアル通信の仕方を学ぶ。まずは簡単なところからということで、Pythonから送られてきた数字(トリガー)をArduino側で識別してそれぞれに対してLチカの挙動を変える、というプログラムを作ってみた。
結果
0 : 消灯、1 : 点灯、2 : 5回点滅、その他 : 10回点滅、という条件分岐がちゃんとできている。
以下のPythonプログラムは"2"の動作のみ。
プログラム
Arduino側
void setup(){ Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); } void loop(){ void flicker(int, int); int input = Serial.read(); if(input != -1){ Serial.println(input); switch(input){ case '0': digitalWrite(LED_BUILTIN, LOW); break; case '1': digitalWrite(LED_BUILTIN, HIGH); break; case '2': flicker(5, 500); break; default: flicker(10, 100); } while(Serial.available())Serial.read(); } } void flicker(int n, int intval){ for(int i=0; i<n; i++){ digitalWrite(LED_BUILTIN, HIGH); delay(intval); digitalWrite(LED_BUILTIN, LOW); delay(intval); } }
Python側
#SimpleSerial_001.py import serial from time import sleep ser = serial.Serial('COM3', 115200, timeout=0.1) sleep(2) ser.write(bytes('2', encoding='ascii')) ser.close()
Pythonでフリー公開されているDLLを使ってみる
ctypes
を使ってDLLの呼び出し方を学ぶ。今回はフリーで公開されているDLLを使ってみる。前回よりも難しかった。
greenhornprofessional.hatenablog.com
使用させてもらったDLLは以下。文字列として書かれた式と変数の値を与えると式通りに計算してくる、というもの。
文字列よりの計算の詳細情報 : Vector ソフトを探す!
結果
式 x+y+zと、変数 x=10, y=2, z=0.1 を与えると、12.1が返ってきた。ちゃんと動いている。
今回はたまたま出来たというのが正直なところ。C/C++を勉強しないとこれよりも複雑なDLLは使えない。
プロトタイプに標準データ型以外の引数が書かれていると絶望的になる…
プログラム
# 31_CallCalcDll_001.py # python 3.8.1 # coding: utf-8 from ctypes import * p_ans = POINTER(c_double) p_siki = POINTER(c_char) lib = cdll.LoadLibrary('CALC11') lib.Num_Form_Calc.argtypes = (p_siki, c_double, c_double, c_double, p_ans) lib.Num_Form_Calc.restype = c_int siki = b"x+y+z" x = 10 y = 2 z = 0.1 ans = c_double() i = c_int() i = lib.Num_Form_Calc(siki, x, y, z, ans) print(i) print(ans) print(ans.value)
0 c_double(12.1) 12.1
Pythonでダミーファイルを大量生産する
ソフトウェアの動作確認のために大量の画像ファイルが必要になった。中身はどうでもよく、とにかく数が必要。
1つの元画像をコピーして連番をつける、というプログラムをちゃちゃっと作った。
プログラム
# 32_CopyAndRename_001.py # python 3.8.1 # coding: utf-8 import os import shutil num = 10 #Specify how many copies do you want. _dir = 'C:\Test' #Work directory src = 'lenna.jpg' #Original file name src_path = os.path.join(_dir, src) #Make the path like C:\aaa\bbb\ccc.jpg if os.path.isfile(src_path): #Check if the original file exists. for i in range(1, num+1): copy = str(i).zfill(3) + '.jpg' #Define sequential name like 001.jpg, 002.jpg, ... copy_path = os.path.join(_dir, copy) #Make a path for copy like C:\aaa\bbb\001.jpg. shutil.copyfile(src_path, copy_path) #Execute copy. print("Complete!") else: print("No file found.")
Pythonでdllを使う Windows API - MessageBox
マシンビジョンカメラをコントロールするソフトが作りたい。でもPython向けのAPIが公開されていない。調べてみたらctypes
でdllを使うことができるらしい。
とりあえず、WindowsのMessage Boxの呼び出し方を紹介してくれているサイトがあったので、参考にさせてもらった。
結果
以下のメッセージボックスを呼び出すことができた。メッセージボックスを表示させるだけならTkinter
よりもお手軽。
プログラム
# 30_WinMessageBox_001.py # python 3.8.1 # coding: utf-8 import ctypes mbox = ctypes.windll.user32 i = mbox.MessageBoxW( 0, "[テスト] 予期せぬエラーが発生しました", "Error Message", 0x00000002 | 0x00000010) if i == 3: print("中止を選択しました") elif i == 4: print("再試行を選択しました") else: #i == 5 print("無視を選択しました")