【Python】処理時間の計測

処理時間の計測には、timeモジュールのtime()関数 もしくは perf_counter()関数を時間計測する2か所で実行し、取得した値の差を計算することで、処理時間(秒単位)が取得できます。

 

サンプルプログラム

import time

print("time")
for j in range(5):
    start = time.time()
    time.sleep(1)
    print(time.time() - start)

print("perf_counter")
for j in range(5):
    start = time.perf_counter()
    time.sleep(1)
    print(time.perf_counter() - start)

実行結果

 

time()関数 と perf_counter()関数 とでは、perf_counter()関数の方が高精度らしいのですが、上記のプログラムでは、差がよく分からず。。

注意点

Visual Studio や Visual Studio Codeを使っている場合、デバッグの開始デバッグなしで開始の2種類の実行方法があります。

(Visual Studioの場合)

(Visual Studio Codeの場合)

 

デバッグのあり/なしで処理時間に大きな差が出るので、処理時間を計測する場合は、デバッグなしで実行を選択して実行してください。

 

例えば、以下のようなプログラムを実行し、処理時間を比較すると、

import time

sum = 0
start = time.perf_counter()

for i in range(10000000):
    sum += 1
    
print(time.perf_counter() - start)

処理時間(秒)

デバッグの開始 デバッグなしで実行
Visual Studio 5.0749325 0.8275141
Visual Studio Code 1.6109771 0.7664662999999999

 

Pythonはビルドするわけでは無いので、デバッグあり/なし は処理時間に関係ないと思っていたのですが、特にVisual Studioでは処理時間が大きく異なるので、注意が必要ですね。

【Python】画像データ(NumPy,Pillow(PIL))の相互変換

Pythonで画像処理をしていると、画像データの扱いは各ライブラリによって、NumPyのndarrayかPillowのPIL.Imageのどちらかになる場合が多いかと思います。

そこで NumPyとPillowの画像データの相互変換をまとめておきます。

 

NumPy -> Pillowへの変換

NumPy からPillowへの変換は Pillowの fromarray関数を用います。

from PIL import Image

pil_image = Image.fromarray(numpy_image)

Pillow -> NumPyへの変換

PillowからNumPyへの変換は NumPyの array関数を用います。

import numpy as np

numpy_image = np.array(pil_image)

array関数と似たものにasarray関数がありますが、このasarrayで変換されたNumPyの配列(ndarray)は読み取り専用となり、値の参照はできますが、値を設定することはできません。

import numpy as np

numpy_image = np.asarray(pil_image) # numpy_imageは読み取り専用となる

変換サンプル

NumPyとPillowの画像データを相互変換したサンプルを示します。

import numpy as np
from PIL import Image

# Pillow でモノクロ画像を読み込む
pil_image_mono = Image.open("image_mono.bmp")
print(type(pil_image_mono))     # <class 'PIL.BmpImagePlugin.BmpImageFile'>
print(pil_image_mono.mode)      # L
print(pil_image_mono.size)      # (400, 300)

# Pillow でカラー画像を読み込む
pil_image_color = Image.open("image_color.bmp")
print(type(pil_image_color))    # <class 'PIL.BmpImagePlugin.BmpImageFile'>
print(pil_image_color.mode)     # RGB
print(pil_image_color.size)     # (400, 300)

# Pillow -> NumPyへ変換(モノクロ画像)
ndarray_mono = np.array(pil_image_mono)
print(type(ndarray_mono))       # <class 'numpy.ndarray'>
print(ndarray_mono.dtype)       # uint8
print(ndarray_mono.shape)       # (300, 400)

# Pillow -> NumPyへ変換(カラー画像)
ndarray_color = np.array(pil_image_color)
print(type(ndarray_color))      # <class 'numpy.ndarray'>
print(ndarray_color.dtype)      # uint8
print(ndarray_color.shape)      # (300, 400, 3)

# NumPy -> Pillowへ変換(モノクロ画像)
pil_image_mono = Image.fromarray(ndarray_mono)
print(type(pil_image_mono))     # <class 'PIL.Image.Image'>
print(pil_image_mono.mode)      # L
print(pil_image_mono.size)      # (400, 300)

# NumPy -> Pillowへ変換(カラー画像)
pil_image_color = Image.fromarray(ndarray_color)
print(type(pil_image_color))    # <class 'PIL.Image.Image'>
print(pil_image_color.mode)     # RGB
print(pil_image_color.size)     # (400, 300)

 

ここで注意しておきたいのが、

Pillowのモノクロ画像をNumPyへ変換したときは
[画像の高さ, 画像の幅]
の順の二次元配列となります。

Pillowのカラー画像をNumPyへ変換したときは
[画像の高さ, 画像の幅, 色(R, B, Gの順)]
の順の三次元配列となります。

NumPyのカラー画像をPillowへ変換する場合は、カラーデータの並びが R,G,B である必要があります。
OpenCVの画像データもNumPyのndarrayで扱われますが、OpenCVの場合、カラーデータの並びが
B,G,Rとなるため、OpenCVからPillowの画像データへ変換する場合は、cvtColor関数を使って、R,G,Bに変換しておく必要があります。

コード例

image_color = cv2.cvtColor(image_color, cv2.COLOR_BGR2RGB)

(参考)

matplotlibで画像データ(OpenCV,pillow,list)を表示する

【Python/NumPy】カラー画像データをRGBからBGRへ変換

【Python】画像データがNumPyかPillowか調べる方法

【OpenCV/Python】adaptiveThresholdの処理アルゴリズム

自動でしきい値を決めて二値化してくれる画像処理と言えば、大津の二値化ですが、OpenCVにはadaptiveThreshold(適応的しきい値処理)という良さげな処理があります。

この adaptiveThreshold は画像全体に影や照明のムラがある場合に、効果を発揮します。

 

以下に大津の二値化とadaptiveThreshold の処理例を示します。

 

使用したプログラム

import cv2

img = cv2.imread("image.jpg")
# カラー→モノクロ変換
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 元画像の表示
cv2.imshow("src image", img)

# 大津の二値化
_, dst1 = cv2.threshold(
    img, 0, 255, cv2.THRESH_OTSU)
cv2.imshow("THRESH_OTSU", dst1)

# 適応的しきい値処理
dst2 = cv2.adaptiveThreshold(
    img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
    cv2.THRESH_BINARY, 51, 20)
cv2.imshow("adaptiveThreshold", dst2)

cv2.waitKey(0)

元画像

大津の二値化

adaptiveThreshold

 

 

元画像の左側に影のある例を示しています。

今回の画像は、文字の部分を黒く、それ以外の部分を白く二値化することを想定しているのですが、大津の二値化では、自動でしきい値は決めてくれるものの、画像全体に輝度値のムラがある場合は、うまく二値化してくれません。それに比べて adaptiveThreshold ではある程度狙った通りに二値化されています。

 

Pythonですが、この関数定義は以下のようになっています。

adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
src 入力画像
maxValue 二値化後の輝度値
adaptiveMethod 適応的しきい値処理で使用するアルゴリズム
cv2.ADAPTIVE_THRESH_MEAN_C
もしくは
cv2.ADAPTIVE_THRESH_GAUSSIAN_C
thresholdType 二値化の種類
cv2.THRESH_BINARY
もしくは
cv2.THRESH_BINARY_INV
blockSize しきい値計算のための近傍サイズ
C 平均あるいは加重平均から引かれる値
戻り値 処理後画像

(参考)

http://opencv.jp/opencv-2svn/c/imgproc_miscellaneous_image_transformations.html

 

だいたい上記のような説明されている場合が多いのですが、よく分からないですよね?!

ただ、やっている事自体は意外と簡単です。

実際にOpenCV内部で行われている処理と異なると思いますが、処理の意味合い的には以下の通りになります。

 

1.adaptiveMethodの設定に従って、平均化(blur)もしくはガウシアンフィルタ(GaussianBlur)で入力画像をぼかします。この時のカーネルのサイズが blockSize x blockSize となります。

 

2.元画像とぼかした画像の差分処理を行います。

 

3.差分画像を指定したしきい値( C ) で二値化し、白黒反転します。

 

すると、adaptiveThreshold で処理した二値化画像が取得できます。
重要なのは、処理の途中に平均化 もしくは ガウシアンフィルタで二値化したい部分をぼかしている部分です。そのため、二値化したい部分の大きさ(今回の例では文字の線幅)に対して十分大きな blockSize を指定する必要があります。

blockSizeを変えながら処理をすると、

blockSize = 5 のとき

blockSize = 21 のとき

blockSize = 51 のとき

 

このようにblockSizeが小さいと、文字の輪郭が二値化され、blockSizeを大きくすると、太い文字も文字全体が二値化されます。

処理の目的的にはトップハットボトムハットに似ています。

(参考)

膨張・収縮・オープニング・クロージング

 

実際の用途的には、画像にムラがあるときに、小さなゴミやキズなどの検出に用いられます。
逆に大きな領域を二値化する場合には adaptiveThreshold は不向きなのでご注意下さい。

 

ちなみに買ったチョコビ

【Python】rangeの構文(開始,終了,ステップ数)

Pythonを勉強して、for文を覚えると何となくrangeを使っていましたが、rangeの構文をまとめておきたいと思います。

 

終了の値を指定する方法

range(終了の値未満)

実行結果

開始と終了の値を指定する方法

range(開始の値以上, 終了の値未満)

実行結果

開始と終了の値、ステップ数を指定する方法

range(開始の値以上, 終了の値未満, ステップ数)

実行結果

 

※rangeの値には整数のみ設定可能です。

【Python/Pillow(PIL)】画像のビット数、チャンネル数を調べる

画像のビット数(8や24など)やチャンネル数(色の数、Lの場合は1、RGBの場合は3など)は画像処理をするときに、画像データを直接参照する場合などに必要になってきます。

jpegファイル(*.jpg)を開いたときには bits という値が拾え、1画素、1色あたり8bitであることがわかります。

しかしながら、他の形式のファイル(少なくとも bmp, pmg, gif)では、この bits の値がありません。

 

そこで、どのファイルでも画像のビット数やチャンネル数を調べられるようにするには、mode の値を調べるようにします。
modeの取得は、以下のように行います。

from PIL import Image

# Pillow で画像を読み込む
pil_image = Image.open("image_color.bmp")
# modeの表示
print("モード:", pil_image.mode)

モードの種類は以下の通りです。

mode 説明
1 1-bit pixels, black and white, stored with one pixel per byte
L 8-bit pixels, black and white
P 8-bit pixels, mapped to any other mode using a color palette
RGB 3×8-bit pixels, true color
RGBA 4×8-bit pixels, true color with transparency mask
CMYK 4×8-bit pixels, color separation
YCbCr 3×8-bit pixels, color video format
LAB 3×8-bit pixels, the L*a*b color space
HSV 3×8-bit pixels, Hue, Saturation, Value color space
I 32-bit signed integer pixels
F 32-bit floating point pixels

(参考)

https://pillow.readthedocs.io/en/stable/handbook/concepts.html

 

画像のビット数やチャンネル数を調べるのに、この mode を取得して、条件分岐でビット数やチャンネル数を上記の表から取得すると正確に求まります。

ただ、実際に使われるのは L, RGB, RGBA の3つぐらいなので、1画素8bit限定として考えると、チャンネル数が求まればビット数も求まります。

チャンネル数を直接取得できる方法は無さそうなので、 getbands() というメソッドを使って行います。

この getbands() は、例えば mode = ‘RGB’ のとき、各チャンネルの色の名前の(‘R’, ‘G’, ‘B’)というタプルを返すメソッドになります。そのため、このタプルの長さを取得すればチャンネル数も求まります。

 

サンプルプログラム

from PIL import Image

# Pillow でモノクロ画像を読み込む
pil_image_mono = Image.open("image_mono.bmp")
print("■■ モノクロ画像 ■■")
print("モード:\t\t", pil_image_mono.mode)
print("バンド:\t\t", pil_image_mono.getbands())
print("チャンネル数:\t", len(pil_image_mono.getbands()))

# Pillow でカラー画像を読み込む
pil_image_color = Image.open("image_color.bmp")
print("■■ カラー画像 ■■")
print("モード:\t\t", pil_image_color.mode)
print("バンド:\t\t", pil_image_color.getbands())
print("チャンネル数:\t", len(pil_image_color.getbands()))

実行結果

 

参考

https://pillow.readthedocs.io/en/stable/reference/Image.html?highlight=getbands#PIL.Image.Image.getbands

matplotlibで画像データ(OpenCV,pillow,list)を表示する

matplotlibを使って画像を表示すると、下図のように画像の画像の座標軸が表示され、右下にはマウスポインタの座標および、その位置の画像の輝度値が表示されるので便利です。

さらに矢印アイコンで、画像の移動、虫眼鏡アイコンで画像の領域を選択すると、その領域が拡大表示されます。

 

Pillow画像の表示

上図ではPillowで画像を開いて、matplotlibで表示しているのですが、そのプログラムは以下の通りです。

import matplotlib.pyplot as plt
from PIL import Image

# Pillow でカラー画像を読み込む
pil_image_color = Image.open("Parrots.bmp")
# matplotlibで表示
plt.imshow(pil_image_color)    
plt.show()

ただし、モノクロ画像を以下のようなプログラムで画像を表示すると、変な色合いになります。

import matplotlib.pyplot as plt
from PIL import Image

# Pillow でモノクロ画像を読み込む
pil_image_color = Image.open("Text.bmp")
# matplotlibで表示
plt.imshow(pil_image_color)    
plt.show()

 

モノクロのグレースケールで表示するには、imshowの引数にグレースケールの cmap(カラーマップ、カラーパレット) “gray” を指定して、以下のようにします。

import matplotlib.pyplot as plt
from PIL import Image

# Pillow でモノクロ画像を読み込む
pil_image_color = Image.open("Text.bmp", cmap = "gray")
# matplotlibで表示
plt.imshow(pil_image_color)    
plt.show()

 

このカラーマップ(cmap)にはデフォルトで “viridis” というものが設定されていて、下図のような色味になっています。

 

カラーマップは他にも hsv や rainbow, jet など様々用意されており、例えば、rainbowを使うと、下図のようになります。

 

カラーマップは色々用意されているので、モノクロ画像を疑似カラーで表示したい場合は、gray以外のものを使用するのも便利だと思います。

カラーマップの詳細については、以下のページで確認できます。

https://matplotlib.org/stable/gallery/color/colormap_reference.html

 

OpenCV画像の表示

matplotlibのimshow関数に指定する画像データはPillowの画像以外にも、OpenCVの画像データ(実態はnumpyのndarray)も指定可能です。

以下のようなプログラムを実行すると

import matplotlib.pyplot as plt

import cv2

# OpenCVでモノクロ画像を読み込む
cv_image_mono = cv2.imread("Text.bmp", cv2.IMREAD_UNCHANGED)
print(type(cv_image_mono))
print(cv_image_mono.shape)
# matplotlibで表示
plt.imshow(cv_image_mono, cmap = "gray")    
plt.show()

下図のようにモノクロ画像が表示されます。

さらにコマンドライン上には、OpenCVで読み込んだ画像の型が <class ‘numpy.ndarray>と表示されていることが確認でき、OpenCVの画像データがnumpyのndarrayそのものだという事がわかります。

画像データのサイズを shapeで表示していますが、モノクロ画像データの時は、二次元配列となっています。

 

同様にして、OpenCVのカラー画像を表示すると、

import matplotlib.pyplot as plt

import cv2

# OpenCVでカラー画像を読み込む
cv_image_color = cv2.imread("Parrots.bmp", cv2.IMREAD_UNCHANGED)
print(type(cv_image_color))
print(cv_image_color.shape)
# matplotlibで表示
plt.imshow(cv_image_color)    
plt.show()

プログラムの実行結果は下図のようになります。

OpenCVのカラー画像をmatplotlibで表示すると、モノクロの時のように、また、変な色になってしまいます。

これは、OpenCVのカラー画像のデータの並びが B, G, R, B, G, R ・・・となっているのですが、matplotlibへは R, G, B, R, G, B ・・・の並びのデータで渡す必要があり、OpenCVのカラー画像をmatplotlibで表示するには、RGBのデータの並びを逆にする必要があります。

また、 shapeの結果が (256, 256, 3) と表示されているように、カラー画像の場合、三次元配列である事が確認できます。

 

BGRのデータをRGBのデータへ変換するには、OpenCVのcvtColor関数を使い

cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGR2RGB)

のように、2番目の引数にcv2.COLOR_BGR2RGBを指定し、RGBの並びを逆にします。

import matplotlib.pyplot as plt

import cv2

# OpenCVでカラー画像を読み込む
cv_image_color = cv2.imread("Parrots.bmp", cv2.IMREAD_UNCHANGED)

# BGR から RGB へ変換
cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGR2RGB)
#cv_image_color = cv_image_color[:, :, ::-1] # numpyのndarrayに対してスライスを使って反転
# matplotlibで表示
plt.imshow(cv_image_color, cmap = "gray")    
plt.show()

 

OpenCVの画像データはnumpyのndarrayなので、スライスを使って、BGRからRGBへ変換することも可能です。

cv_image_color = cv_image_color[:, :, ::-1]

ただし、この場合、カラー画像データが32bitの場合、OpenCVのカラーデータの並びはB, G, R, A となっているため、このまま並びを反転すると A, R, G, B となり、matplotlibで表示すると、変な色になってしまいます。

32bitのカラー画像をmatplotlibへ渡すには、R, G, B, A の順になっている必要があるため、numpyで並びを逆にしたい場合は

cv_image_color = cv_image_color[:, :, [2, 1, 0, 3]]

とする必要があり面倒なので、OpenCVを使っている場合、cvtColorを使った方がよさそうです。

ちなみに、OpenCVで32bitのカラー画像の並びを反転させるには、本来であれば、

cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGRA2RGBA)

もしくは

cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGRA2RGB)

とすべきなのですが、

cv_image_color = cv2.cvtColor(cv_image_color, cv2.COLOR_BGR2RGB)

としても、 cv2.COLOR_BGRA2RGBと同じ動き(32bitBGRAから24bitRGBへの変換)となっていました。

 

listデータの表示

matplotlibのimshowで表示する画像データは、ここ↓のページ

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html

を見ても分かるように、配列かPIL Imageを渡す事ができます。

この配列の値は 0~1のfloatの値か、もしくは 0~255のintの値であれば、表示することが可能です。

という事は、Deep Learningでは画像データの0~255を0~1へ変換することが多いので、便利そうです。

また、配列はnumpyのndarrayだけでなく、Pythonのlistも渡す事ができ、以下にサンプルを示します。

import matplotlib.pyplot as plt

list_data = [list(range(256)) for i in range(256)]
plt.imshow(list_data, cmap = "gray")    
plt.show()

 

参考

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html

https://matplotlib.org/stable/gallery/color/colormap_reference.html

【PyTorch】Visual Studioでインストールする方法

私はC#をメインで使っているので、Pythonのプログラムを組む時も慣れたVisual Studioで行っています。

さらに、Visual Studio 2019であればPythonの環境(バージョン)ごとにインストールするのも簡単なのですが、PyTorchをインストールするときは、少々、ハマったので、そのメモです。。

 

PythonモジュールのインストールはPython環境で、インストールしたい環境(バージョン)を選択し、 PyPIとインストールされたパッケージの検索 の部分に torch と入力して、Enterを押せば、インストールできるかも?知れないのですが、インストールされるPyTorchのバージョンやGPUの対応などに不安があったので、別の方法でインストールを行いました。

 

まず、PyTorchのページへ移動し、少し下に表示されているバージョンやOSの選択表示部分で、自分のインストールしたい環境を選択します。

私の場合は以下のように選択しました。

 

すると、Run this Commandの部分に

 

pip3 install torch==1.8.1+cu111 torchvision==0.9.1+cu111 torchaudio===0.8.1 -f https://download.pytorch.org/whl/torch_stable.html

 

と表示されているので、この torch以降の部分を選択し、コピー(Ctrl+C)します。

コピーしたテキストを、 PyPIとインストールされたパッケージの検索 の部分に貼り付け(Ctrl+V)ると、 次のコマンドを実行する のリンクが表示されるので、この部分をクリックすると、PyTorchのインストールが始まります。

 

 

これで、特に問題が無かった人は以下のように torch, torchaudio, torchvision の3つがインストールされると思います。

 

しかし、私はそう簡単にはいきませんでした。。

 

そもそも自分のPCにインストールされているCUDAのバージョンがわからなかったのですが、CUDAのバージョンを調べるにはコマンドプロンプトから、

nvcc -V

と入力すると、CUDAのバージョンを調べることができます。

 

私の場合、CUDAのバージョンが古かったので、CUDAもインストールしました。

(参考)

CUDAの入手、ダウンロード、インストール方法

 

さらに、pip install 中に、このような↓メッセージが表示されたのですが、

 

ここで今すぐ昇格をクリックして、インストールを続けても、下記のようなエラーが表示されて、インストールできませんでした。

----- 'torch==1.8.1+cu111 torchvision==0.9.1+cu111 torchaudio===0.8.1 -f https://download.pytorch.org/whl/torch_stable.html' をインストールしています -----
Looking in links: https://download.pytorch.org/whl/torch_stable.html
Collecting torch==1.8.1+cu111
  Using cached https://download.pytorch.org/whl/cu111/torch-1.8.1%2Bcu111-cp39-cp39-win_amd64.whl (3055.6 MB)
Collecting torchvision==0.9.1+cu111
  Using cached https://download.pytorch.org/whl/cu111/torchvision-0.9.1%2Bcu111-cp39-cp39-win_amd64.whl (1.9 MB)
Collecting torchaudio===0.8.1
  Using cached torchaudio-0.8.1-cp39-none-win_amd64.whl (109 kB)
Requirement already satisfied: typing-extensions in c:\users\akira\appdata\local\packages\pythonsoftwarefoundation.python.3.9_qbz5n2kfra8p0\localcache\local-packages\python39\site-packages (from torch==1.8.1+cu111) (3.7.4.3)
Requirement already satisfied: numpy in c:\users\akira\appdata\local\packages\pythonsoftwarefoundation.python.3.9_qbz5n2kfra8p0\localcache\local-packages\python39\site-packages (from torch==1.8.1+cu111) (1.20.2)
Requirement already satisfied: pillow>=4.1.1 in c:\users\akira\appdata\local\packages\pythonsoftwarefoundation.python.3.9_qbz5n2kfra8p0\localcache\local-packages\python39\site-packages (from torchvision==0.9.1+cu111) (8.2.0)
Installing collected packages: torch, torchvision, torchaudio
ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory: 'C:\\Users\\akira\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python39\\site-packages\\caffe2\\python\\serialized_test\\data\\operator_test\\collect_and_distribute_fpn_rpn_proposals_op_test.test_collect_and_dist.zip'
----- 'torch==1.8.1+cu111 torchvision==0.9.1+cu111 torchaudio===0.8.1 -f https://download.pytorch.org/whl/torch_stable.html' をインストールできませんでした -----

このエラーの

ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory:

の部分がいまいち意味が分からず、インストール中に管理者権限のメッセージが表示されていたので、インストール先のフォルダの権限が悪いのか?と思ったのですが、フォルダの権限の変更方法も分からず。。

結局、Pythonをアンインストールして、再インストールすることで、PyTorchもインストールできました。

 

ちなみに、Pythonのインストールは、下記のページ

https://www.python.org/downloads/

より、Windowsの場合であれば Download Python X.X.X の部分をクリックし、ファイルをダウンロードし、ファイルをダブルクリックしてインストールします。

インストール時に Add Python 3.9 to PATH にチェックを入れ  Customize installation をクリックしてインストールします。

次に表示されたウィンドウでインストール先のディレクトリが指定できるので、アクセス制限のなさそうな場所を指定して、 install をクリックします。

最終的に以下のようなウィンドウが表示されれば、Pythonのインストールは完了です。

ここまでやって、無事、PyTorchをインストールすることが出来ました!!