【OpenCV-Python】filter2D(任意カーネルフィルタ)

OpenCVで任意のカーネルを指定してフィルタ処理を行うには、filter2D()関数を用います。

カーネルそのものは、numpyの二次元配列で指定します。

 

構文

filter2D( src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]] ) -> dst

引数

src 処理を行う画像データを指定します。
ddepth 出力データの1画素1chあたりのビット深度を指定します。
-1 を指定すると、入力データと同じビット深度になります。
設定できる組み合わせは下記を参照ください。
kernel kernelの値を二次元配列で指定します。
カラーなどの複数チャンネルのデータでは、すべて同じカーネルが適用されます。
anchor kernelの基準位置を指定します。
anchorを指定しないか、(-1, -1)を指定すると、基準位置はカーネルの中心位置となります。
delta kernelで処理された後にdeltaの値が加えられます。
borderType ボーダー処理の種類をBorderTypesのenumで指定します。

ddepthに指定可能な組み合わせ

入力データのビット深度 出力データのビット深度
cv2.CV_8U -1, cv2.CV_16S, cv2.CV_32F, cv2.CV_64F
cv2.CV_16U, cv2.CV_16S -1, cv2.CV_16S, cv2.CV_32F, cv2.CV_64F
cv2.CV_32F -1, cv2.CV_32F
cv2.CV64F -1, cv2.CV_64F


戻り値

dst フィルタ処理された画像データ
ビット深度はddpethで指定された値になります。

サンプルプログラム

カーネルの値を自分で指定したガウシアンフィルタ処理を行うサンプルプログラムを以下に示します。

import cv2
import numpy as np

# 処理前画像の読込
src = cv2.imread("Mandrill.bmp", cv2.IMREAD_UNCHANGED)

# 3x3のガウシアンフィルタのカーネル
kernel = np.array([
    [1, 2, 1],
    [2, 4, 2],
    [1, 2, 1]
    ], dtype = np.float32)
kernel /= 16

# 任意カーネルフィルタの実施
dst = cv2.filter2D(src, -1, kernel)

# 任意カーネルフィルタ処理後の画像表示
cv2.imshow("Src Image", src )
cv2.imshow("filter2D", dst)
cv2.waitKey()

実行結果

 

出力先のビット深度に符号なしの値を指定すると、kernelにより計算された値がビット深度の範囲を超える場合、値はクリップされます。

例えば、出力先のビット数が8bitのとき、ビット深度の範囲は0~255なので、計算結果が負の場合は0に、255を超える場合は255に補正されます。

 

評価プログラム

import cv2
import numpy as np

src = np.array([
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0]
    ], dtype = np.uint8)

# ソーベルフィルタのカーネル
kernel = np.array([
    [-1, 0, 1],
    [-2, 0, 2],
    [-1, 0, 1]
    ])

# 任意カーネルフィルタ
dst = cv2.filter2D(src, -1, kernel)

print("-----src------------------------------")
print(src)
print("-----dst------------------------------")
print(dst)

実行結果

—–src——————————
[[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]]
—–dst——————————
[[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]
[ 0 0 255 255 0 0 0 0 0]]

 

出力先のビット深度を符号ありの16bitにしたときのサンプルは以下の通りです。

import cv2
import numpy as np

src = np.array([
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0],
    [0, 0, 0, 255, 255, 255, 0, 0, 0]
    ], dtype = np.uint8)

# ソーベルフィルタのカーネル
kernel = np.array([
    [-1, 0, 1],
    [-2, 0, 2],
    [-1, 0, 1]
    ])

# 任意カーネルフィルタ
dst = cv2.filter2D(src, cv2.CV_16S, kernel)

print("-----src------------------------------")
print(src)
print("-----dst------------------------------")
print(dst)

実行結果

—–src——————————
[[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]
[ 0 0 0 255 255 255 0 0 0]]
—–dst——————————
[[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]
[ 0 0 1020 1020 0 -1020 -1020 0 0]]

 

参照ページ

https://docs.opencv.org/4.8.0/d4/d86/group__imgproc__filter.html#ga27c049795ce870216ddfb366086b5a04

https://docs.opencv.org/4.8.0/d4/d86/group__imgproc__filter.html#filter_depths

【OpenCV-Python】medianBlur(メディアンフィルタ)

OpenCVで画像のメディアンフィルタを行うには、medianBlur()関数を用います。

メディアンフィルタは、ごま塩ノイズやスパイクノイズなどと言われる、小さい点々のノイズを除去するのに効果的です。

また、他の平滑化(blur)やガウシアンフィルタ(GaussianBlur)などと比べて、画像のエッジ部分がぼやけることがありません。

元画像 メディアンフィルタ後

メディアンフィルタ処理の構文

medianBlur( src, ksize[, dst] ) -> dst

引数

src メディアンフィルタ処理を行う画像データを指定します。
ksize カーネルのサイズを正の奇数で指定します。1, 3, 5…
カーネルは幅と高さが同じサイズになります。

戻り値

dst メディアン処理された画像データ

サンプルプログラム

メディアンフィルタ処理のサンプルプログラムを以下に示します。

import cv2

src = cv2.imread("Pepper.bmp", cv2.IMREAD_UNCHANGED)

# メディアンフィルタ
dst = cv2.medianBlur(src, 5)

# 二値化処理後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("medianBlur", dst)
cv2.waitKey()

実行結果

参照ページ

https://docs.opencv.org/4.8.0/d4/d86/group__imgproc__filter.html#ga564869aa33e58769b4469101aac458f9

メディアンフィルタ

【OpenCV-Python】GaussianBlur(ガウシアンフィルタ)

OpenCVで画像のガウシアンフィルタ処理を行うには、GaussianBlur()関数を用います。

ガウシアンフィルタは、「ガウス関数のσの値を変えると、平滑化の効果を変えられる」という説明が多いかと思いますが、ガウシアンフィルタには、それよりも大事な、高周波成分を除去できるという効果があります。

高周波成分を除去するには、σの値は何でもいい訳ではないのですが、OpenCVでは、この高周波成分を除去できるσの値をカーネルのサイズから自動計算で計算させることもできます。

 

ガウシアンフィルタの構文

GaussianBlur( src, ksize, sigmaX[, dst[, sigmaY[, borderType]]] ) -> dst

引数

src ガウシアンフィルタ処理を行う画像データを指定します。
データタイプはuint8, uint16, int16, float32, float64に対応しています。
ksize ガウシアンフィルタのカーネルのサイズを(幅, 高さ)で指定します。
幅、高さの値は正の奇数の値である必要があります。
また、幅、高さに0を指定すると、σの値(sigmaX, sigmaY)から自動で計算します。
sigmaX X方向のσの値を指定します。
-1などのマイナスの値を指定するとカーネルのサイズから自動でσの値を計算します。
σ = 0.3 * (ksize – 1) * 0.5 – 1) * 0.8
sigmaY Y方向のσの値を指定します。
指定しないか、0を指定すると、sigmaXと同じσの値を用います。
sigmaXとsigmaYの両方が0の場合は、それぞれ、カーネルの幅と高さからσの値を自動で計算します。
borderType ボーダー処理の種類をBorderTypesのenumで指定します。


戻り値

dst ガウシアンフィルタ処理された画像データ

サンプルプログラム

ガウシアンフィルタ処理の一般的に行うシンプルなサンプルプログラムを以下に示します。

import cv2

src = cv2.imread("Text.bmp", cv2.IMREAD_UNCHANGED)

# ガウシアンフィルタ
dst = cv2.GaussianBlur(src, (3, 3), -1)

# ガウシアンフィルタ後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("GaussianBlur", dst)
cv2.waitKey()

実行結果

 

各種パラメータを設定したガウシアンフィルタ処理の例は以下の通りです。

import cv2

src = cv2.imread("Text.bmp", cv2.IMREAD_UNCHANGED)

# ガウシアンフィルタ
dst1 = cv2.GaussianBlur(
    src,    # 入力画像
    (7, 7), # カーネルのサイズ(幅、高さ)
    1.4,    # X方向のσの値(-1など負の値を指定するとカーネルサイズから自動計算)
    0,      # Y方向のσの値(0だとX方向と同じ)
    borderType = cv2.BORDER_DEFAULT  # ボーダー処理の種類
    )
# ガウシアンフィルタ(カーネルサイズを自動計算)
dst2 = cv2.GaussianBlur(
    src,    # 入力画像
    (0, 0), # カーネルのサイズを0にしてσの値からカーネルサイズを自動計算
    2       # X方向のσの値
    )

# 二値化処理後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("GaussianBlur1", dst1)
cv2.imshow("GaussianBlur2", dst2)
cv2.waitKey()

実行結果

 

ガウシアンフィルタのσの値について

高周波成分を除去するためには、σの値を大きくすれば良いという訳ではなく、最適な値があります。

OpenCVでは、sigmaX, sigmaYの値に負の値を指定するとカーネルのサイズ(幅、高さ)から下記の計算式から最適なσの値を計算してくれます。

σ = 0.3 * (ksize – 1) * 0.5 – 1) * 0.8

実際に、ガウシアンフィルタで用いられるカーネルの値は、getGaussianKernel()関数から取得することができます。

いくつか例を示すと

ksize= 3のとき
0.25, 0.5, 0.25

ksize= 5のとき
0.0625, 0.25, 0.375, 0.25, 0.0625

ksize= 7のとき
0.03125, 0.109375, 0.21875, 0.2825, 0.21875, 0.109375, 0.03125

となります。

 

ガウシアンフィルタのσの値と高周波成分の除去効果について

二次元の画像において、もっとも高周波な画像は、下図のような1画素おきの市松模様の画像となります。

この画像において、σの値を0.6, 0.8(自動計算), 3 と変えながらガウシアンフィルタ処理を行うとどのように変化するのか?を見てみたいと思います。

 

サンプルプログラム

import numpy as np
import cv2

def create_test_pattern():
    # テストパターンの作成
    data = np.array([
        [0, 255],
        [255, 0]
        ], dtype= np.uint8)
    img = np.tile(data, (16, 16))
    return img

# テストパターンの作成
src = create_test_pattern()

cv2.namedWindow("Src Image", cv2.WINDOW_NORMAL)
cv2.namedWindow("Gauss 0.3", cv2.WINDOW_NORMAL)
cv2.namedWindow("Gauss -1", cv2.WINDOW_NORMAL)
cv2.namedWindow("Gauss 3", cv2.WINDOW_NORMAL)

# σを変えながらガウシアン処理
gauss1 = cv2.GaussianBlur(src, (3, 3), 0.6) # σ=0.6
gauss2 = cv2.GaussianBlur(src, (3, 3), -1)  # σは自動計算(σ=0.8)
gauss3 = cv2.GaussianBlur(src, (3, 3), 3)   # σ=3

# ガウシアンフィルタ後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("Gauss 0.3", gauss1)
cv2.imshow("Gauss -1", gauss2)
cv2.imshow("Gauss 3", gauss3)
cv2.waitKey()

実行結果

左からσ=0.6,  σ=0.8(自動計算),  σ=3

 

このように、σの値は、単に大きい方が高周波成分を除去できるという訳ではなく、最適なσの値があり、このσの値は、OpenCVが自動で計算してくれる値を用いた方が高周波成分を除去できる事が確認できます。

 

参照ページ

https://docs.opencv.org/4.8.0/d4/d86/group__imgproc__filter.html#gaabe8c836e97159a9193fb0b11ac52cf1

https://docs.opencv.org/4.8.0/d4/d86/group__imgproc__filter.html#gac05a120c1ae92a6060dd0db190a61afa

ガウシアンフィルタの処理アルゴリズムとその効果

移動平均フィルタ VS ガウシアンフィルタ

【OpenCV-Python】blur(平滑化、移動平均)

OpenCVで画像の平滑化を行うには、blur()関数を用います。

ここで言う平滑化は、単純な移動平均フィルタとなります。

移動平均フィルタの処理については、下記ページを参照ください。

平滑化(移動平均)フィルタ

平滑化処理の構文

blur( src, ksize[, dst[, anchor[, borderType]]] ) -> dst

引数

src 平滑化を行う画像データを指定します。
データタイプはuint8, uint16, int16, float32, float64に対応しています。
ksize カーネルのサイズを(幅, 高さ)で指定します。
通常、幅と高さは奇数の値を指定してください。
anchor カーネルの基点となる座標を(x, y)で指定します。
(-1, -1)と指定すると、カーネルの中心座標となります。
指定しないと、初期値でカーネルの中心座標となります。
borderType ボーダー処理の種類をBorderTypesのenumで指定します。

戻り値

dst 平滑化された画像データ

サンプルプログラム

平滑化処理のサンプルプログラムを以下に示します。

import cv2

src = cv2.imread("Text.bmp", cv2.IMREAD_UNCHANGED)

# 平滑化(移動平均)
dst = cv2.blur(src, (5, 5))

# 二値化処理後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("blur", dst)
cv2.waitKey()

実行結果

 

anchorやborderTypeについては、ほぼ、指定することは無いかと思いますが、念のため、それぞれを指定したサンプルを示します。

import cv2

src = cv2.imread("Text.bmp", cv2.IMREAD_UNCHANGED)

# 平滑化(移動平均)
dst = cv2.blur(
    src,                # 入力画像
    (3, 21),            # カーネルのサイズ(幅、高さ)
    anchor = (1, 10),   # カーネルの中心座標
    borderType = cv2.BORDER_DEFAULT  # ボーダー処理の種類
    )

# 二値化処理後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("blur", dst)
cv2.waitKey()

実行結果

参照ページ

https://docs.opencv.org/4.8.0/d4/d86/group__imgproc__filter.html#ga8c45db9afe636703801b0b2e440fce37

平滑化(移動平均)フィルタ

【OpenCV-Python】BorderTypes(画像の外周処理の設定)

外周画像の処理

【OpenCV-Python】BorderTypes(画像の外周処理の設定)

blur(平滑化)やGaussianBlur(ガウシアンフィルタ)など、カーネルを使った画像フィルタ処理では、画像の最外周部分では、カーネルが画像からはみ出してしまうため、はみ出した部分を、どのように補うか?の設定の種類にBoarderTypes というenumが定義されています。

enum定義

cv2.BORDER_CONSTANT 指定した値(i)で補います。
iiiiii|abcdefgh|iiiiiii
cv2.BORDER_REPLICATE 最も外側の値で補います。
aaaaaa|abcdefgh|hhhhhhh
cv2.BORDER_REFLECT 外側の位置を基準に折り返すように補います。
fedcba|abcdefgh|hgfedcb
cv2.BORDER_WRAP 値が向きが変わらず、繰り返されるように補います。
cdefgh|abcdefgh|abcdefg
cv2.BORDER_REFLECT_101 最も外側の値の位置を中心に折り返すように補います。
gfedcb|abcdefgh|gfedcba
cv2.BORDER_TRANSPARENT 透過(はみ出した部分を別の画像で補います)
uvwxyz|abcdefgh|ijklmno
cv2.BORDER_REFLECT101 cv2.BORDER_REFLECT_101と同じ
cv2.BORDER_DEFAULT cv2.BORDER_REFLECT_101と同じ
cv2.BORDER_ISOLATED 画像の外側を見ない(処理しない)

 

サンプルプログラム

Boarderの処理は、blurなどの関数内部で行われる事が多いので、ボーダー付きの画像をわざわざ自分で作る事は少ないのですが、copyMakeBoarder()関数によって、ボーダー付きの画像を生成する事が出来るので、そのサンプルを下記に示します。

import cv2

src = cv2.imread("Parrots.bmp")

bd = 64 # 枠線の太さ

border0 = cv2.copyMakeBorder(src, bd, bd, bd, bd, cv2.BORDER_CONSTANT, value = [0, 0, 255])
border1 = cv2.copyMakeBorder(src, bd, bd, bd, bd, cv2.BORDER_REPLICATE)
border2 = cv2.copyMakeBorder(src, bd, bd, bd, bd, cv2.BORDER_REFLECT)
border3 = cv2.copyMakeBorder(src, bd, bd, bd, bd, cv2.BORDER_WRAP)
border4 = cv2.copyMakeBorder(src, bd, bd, bd, bd, cv2.BORDER_REFLECT_101)
border6 = cv2.copyMakeBorder(src, bd, bd, bd, bd, cv2.BORDER_REFLECT101)
border7 = cv2.copyMakeBorder(src, bd, bd, bd, bd, cv2.BORDER_DEFAULT)
border8 = cv2.copyMakeBorder(src, bd, bd, bd, bd, cv2.BORDER_ISOLATED)
 
cv2.imshow("Src", src)
cv2.imshow("BORDER_CONSTANT", border0)
cv2.imshow("BORDER_REPLICATE", border1)
cv2.imshow("BORDER_REFLECT", border2)
cv2.imshow("BORDER_WRAP", border3)
cv2.imshow("BORDER_REFLECT_101", border4)
cv2.imshow("BORDER_REFLECT101", border6)
cv2.imshow("BORDER_DEFAULT", border7)
cv2.imshow("BORDER_ISOLATED", border8)

cv2.waitKey()

実行結果

 

個人的にはBORDER_DEFAULTにもなっている、BORDER_REFLECT_101を使う事が多いです。

 

参照ページ

https://docs.opencv.org/4.8.0/d2/de8/group__core__array.html#ga209f2f4869e304c82d07739337eae7c5

外周画像の処理

【OpenCV-Python】threshold(二値化、大津の二値化)

OpenCVで画像の二値化大津の二値化を行うには、threshold()関数を用います。

二値化処理は、欠陥検査などにおいて、ノイズ除去などのフィルタを行った後、二値化処理を行い、面積や形状、位置などから、OK/NGの判定を行う場合などに用いられます。

二値化処理の構文

threshold( src, thresh, maxval, type[, dst] ) -> retval, dst

引数

src 二値化を行う画像データを指定します。
複数チャンネルの8bit、もしくは32bitのfloatのデータです。
大津の二値化の場合は8bit1ch(グレースケール)に限定されます。
thresh 二値化のしきい値を指定します。
THRESH_OTSUのときは無視されます。
maxval 二値化処理後の値を指定します。通常は255を指定
THRESH_BINARY, THRESH_BINARY_INV, THRESH_OTSU のとき有効
type 二値化処理方法を指定します。(下記参照)

typeの設定

cv2.THRESH_BINARY 輝度値がthreshより大きい値をmaxval、それ以外を0にします。
cv2.THRESH_BINARY_INV 輝度値がthreshより大きい値を0、それ以外をmaxvalにします。
cv2.THRESH_TRUNC 輝度値がthreshより大きい値をthresh、それ以外はそのままにします。
cv2.THRESH_TOZERO 輝度値がthreshより大きい値はそのまま、それ以外を0にします。
cv2.THRESH_TOZERO_INV 輝度値がthreshより大きい値を0、それ以外はそのままにします。
cv2.THRESH_OTSU 大津の二値化により、しきい値を求め、
求めたしきい値より大きい値をmaxval、それ以外を0にします。

 

戻り値

retval 二値化に用いたしきい値。
主に大津の二値化の場合のしきい値確認に用います。
dst 二値化された画像データ

サンプルプログラム

二値化処理のサンプルプログラムを以下に示します。

import numpy as np
import cv2

def create_test_pattern():
    # テストパターンの作成
    increase = np.linspace(0, 255, 128, dtype = np.uint8)
    decrease = np.linspace(255, 0, 128, dtype = np.uint8)
    line = np.concatenate([increase, decrease, increase, decrease])
    img = np.tile(line, (128, 1))
    return img

# テストパターンの作成
src =  create_test_pattern()

# 二値化
ret,thresh1 = cv2.threshold(src,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(src,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(src,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(src,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(src,127,255,cv2.THRESH_TOZERO_INV)
ret,thresh6 = cv2.threshold(src,127,255,cv2.THRESH_OTSU)

# 二値化処理後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("THRESH_BINARY", thresh1)
cv2.imshow("THRESH_BINARY_INV", thresh2)
cv2.imshow("THRESH_TRUNC", thresh3)
cv2.imshow("THRESH_TOZERO", thresh4)
cv2.imshow("THRESH_TOZERO_INV", thresh5)
cv2.imshow("THRESH_OTSU", thresh6)
cv2.waitKey()

実行結果

 

大津の二値化に関しては、テストパターンだと、あまり効果を感じないため、自然画で処理した例を示します。

 

大津の二値化のサンプルプログラム

import cv2

src = cv2.imread("Cameraman.bmp", cv2.IMREAD_GRAYSCALE)

# 大津の二値化
ret,thresh = cv2.threshold(src,0,255,cv2.THRESH_OTSU)

# 二値化処理後の画像表示
cv2.imshow("Src Image", src)
cv2.imshow("THRESH_OTSU", thresh)
cv2.waitKey()

実行結果

参照ページ

https://docs.opencv.org/4.x/d7/d1b/group__imgproc__misc.html#gae8a4a146d1ca78c626a53577199e9c57

https://docs.opencv.org/4.x/d7/d1b/group__imgproc__misc.html#gaa9e58d2860d4afa658ef70a9b1115576

https://docs.opencv.org/4.x/d7/d4d/tutorial_py_thresholding.html

二値化

判別分析法(大津の二値化)

【Windows11】Print ScreenキーでのSnipping Toolの起動を無効にする

普段、私はデスクトップ画面をキャプチャするには、Screenpressoを使用しているのですが、Windowsのアップデートにより、Print Screenキーを押した時に Snipping Toolが起動してしまうようになってしまいました。

そこで、Print Screenキーを押した時にSnipping Toolが起動しないように設定したいと思います。

 

まず、スタートボタン右クリックし、設定をクリックします。

次に左側メニューよりアクセシビリティを選択し、右側をスクロールし、やや下の方にある キーボード をクリックします。

キーボードの設定内で、少し下の方にある、PrintScreenキーを使用してSnipping Toolを開く の設定をオフにします。

これで、Print Screenキーを押しても Snipping Toolが起動しなくなります。

しかしながら、Print Screenキーを押してもScreenpressoが起動しないので、Windowsを再起動すると、Print Screenキーを押したときにScreenpressoが起動してくれます。

【OpenCV-Python】バージョンの確認方法

OpenCVで、現在、使用しているバージョンの確認方法は、Pythonのコードで

import cv2
print(cv2.__version__)

のように、versionの前後にアンダーバー(_)を2つで挟んで表示するとバージョンを確認することができます。

私の場合、上記のコードを実行すると、

4.7.0

と表示されました。

 

普段、私はVisual Studioを使ってPythonのコードを書く事が多いのですが、これだと何も気にせず、Python環境のウィンドウで、パッケージ(PyPI)を選択すると、バージョンを確認する事ができます。

【OpenCV-Python】トラックバー(スライダーバー)

OpenCVのGUIで出来る事は少ないのですが、そのうちの一つのトラックバーの表示です。

トラックバーを使うと、二値化などのしきい値の設定に使うと便利です。

 

トラックバーの使用例

二値化のしきい値にトラックバーを使用

サンプルコード

import cv2

# ウィンドウのタイトル
window_title = "Trackbar sample"

# コールバック関数(トラックバーが変更されたときに呼ばれる関数)
def on_trackbar(val):
    if img is not None:
        # 二値化
        ret, dst = cv2.threshold(img, val, 255, cv2.THRESH_BINARY)
        # 画像の表示
        cv2.imshow(window_title, dst)

# ウィンドウの作成
cv2.namedWindow(window_title, cv2.WINDOW_NORMAL)
# トラックバーの作成
cv2.createTrackbar(
    "Threshold",    # トラックバーの名前
    window_title ,  # トラックバーを表示するウィンドウのタイトル
    127,            # 初期値
    255,            # 最大値(最小値は0で固定)
    on_trackbar     # コールバック関数
    )

# 画像の読込
img = cv2.imread("Text.bmp")

# トラックバーの値を取得
track_value = cv2.getTrackbarPos("Threshold", window_title)
# 最初の1回目の処理を取得した値で実行
on_trackbar(track_value)

# キー入力待ち
cv2.waitKey()

createTrackbar()関数

ウィンドウにトラックバーを追加します。

createTrackbar(trackbarName, windowName, value, count, onChange)
引数 説明
trackbarName トラックバーの名前
この文字がトラックバーの左側に表示されます。
windowName トラックバーを追加するウィンドウの名前
value トラックバーの初期値
count トラックバーの最大値
(最小値は設定できず、常に0となります)
onChange コールバック関数
トラックバーが変更された時に呼び出す関数を指定します。

getTrackbarPos()関数

トラックバーの値を取得します。

getTrackbarPos(trackbarname, winname)
引数 説明
trackbarName トラックバーの名前
この文字がトラックバーの左側に表示されます。
windowName トラックバーを追加するウィンドウの名前
戻り値 トラックバーの値

setTrackbarPos()関数

トラックバーの値を設定します。

値を設定すると、コールバック関数も呼び出されます。

setTrackbarPos(trackbarname, winname, pos)
引数 説明
trackbarName トラックバーの名前
この文字がトラックバーの左側に表示されます。
windowName トラックバーを追加するウィンドウの名前
pos 設定するトラックバーの値

参考

https://docs.opencv.org/3.4/da/d6a/tutorial_trackbar.html

関連記事

OpenCVのGUIで出来る事は少ないので、ボタンの表示などをしたい場合は、Tkinterなど別のGUIを使う事をおススメします。

【OpenCV-Python】Tkinter GUI Sample

【Python/tkinter】Scale(トラックバー、スライダー)

【Python.NET】PythonからC#ライブラリ(dll)の使用方法

Python.NET(pythonnet)を使うと、C#というか、.NETで作られたライブラリ(dll)を、何も手を加える事なく、そのままPythonから使う事が出来るようになります。

また、その逆のC#からPythonのモジュールを使う事も可能です。

 

今回は、.NET Frameworkで作られたライブラリ(dll)をPythonから使う方法を紹介します。

 

Python.NET(pythonnet)のインストール

これはpipでインストールできるので、簡単です。

pip install pythonnet

以降、具体的な例を示します。

 

C#ライブラリのサンプル例

今回はC#で作られたライブラリの例として、以下の用な物を用意しました。
DLLファイル名はCSharpDLL.dllとしました。

通常は、既存のDLLファイルをそのまま使う事が出来るので、わざわざC#のDLLを作成する必要はありません。

namespace CSharpDLL
{
    public class Calculation
    {
        // intの足し算
        public int Add(int a, int b)
        {
            return a + b;
        }

        // floatの足し算
        public float Add(float a, float b)
        {
            return a + b;
        }

        // stringの足し算(文字列の結合)
        public string Add(string a, string b)
        {
            return a + b;
        }

        // 結果を引数(outの参照渡し)で返す場合
        public bool Add_ref(int a, int b, out int ans)
        {
            ans = a + b;
            return true;
        }

        // 結果を引数(refの参照渡し)で返す場合
        public bool Add_out(int a, int b, ref int ans)
        {
            ans += a + b;
            return true;
        }
    }
}

PythonからC#のライブラリを使用する方法

まずは、PythonからC#のDLLを使うサンプルを以下に示します。

import clr # Python.net

# dllファイルの参照追加(拡張子なしで指定)
clr.AddReference("CSharpDLL")

# 名前空間の追加
import CSharpDLL as calc

# クラスのインスタンス(newは付かない)
cal = calc.Calculation()

# intの足し算
ret = cal.Add(1, 2)
print(ret, type(ret))

# floatの足し算
ret = cal.Add(1.5, 2.1)
print(ret, type(ret))

# stringの足し算(文字列の結合)
ret = cal.Add("1", "2")
print(ret, type(ret))

# 結果を引数(refの参照渡し)で返す場合
ans_ref = 5
ret, ans = cal.Add_ref(1, 2, ans_ref)
print(ret, ans, ans_ref)

# 結果を引数(outの参照渡し)で返す場合
ans_out = 5
ret, ans = cal.Add_out(1, 2, ans_out)
print(ret, ans, ans_out)

Python.NET(pythonnet)のインポート

import clr

これで、Python.NETのインポートになっています。なぜだか clr ??

使用するC#のDLLファイルの参照設定

clr.AddReference("CSharpDLL")

AddReference関数で、使用するライブラリのファイル名を拡張子なしで指定します。

実際のDLLファイルは、実行するPythonのファイル(.py)から参照できる位置にある必要があります。

例えば、Pythonのファイルと同一フォルダや、環境変数のPATHで指定されているフォルダに使用するDLLファイルを配置してください。

C#ライブラリの名前空間のインポート

使用するライブラリのクラスの名前空間をインポートします。

import CSharpDLL as calc

クラスのインスタンス

cal = calc.Calculation()

C#に慣れていると、ただ、メソッドを呼んでいるように見えますが、上記の書き方でクラスをインスタンスしています。(new とかは付きません)

メソッドの実行例

# intの足し算
ret = cal.Add(1, 2)
print(ret, type(ret))

# floatの足し算
ret = cal.Add(1.5, 2.1)
print(ret, type(ret))

# stringの足し算(文字列の結合)
ret = cal.Add("1", "2")
print(ret, type(ret))

# 結果を引数(refの参照渡し)で返す場合
ans_ref = 5
ret, ans = cal.Add_ref(1, 2, ans_ref)
print(ret, ans, ans_ref)

# 結果を引数(outの参照渡し)で返す場合
ans_out = 5
ret, ans = cal.Add_out(1, 2, ans_out)
print(ret, ans, ans_out)

実行結果

3 <class ‘int’>
3.5999999046325684 <class ‘float’>
12 <class ‘str’>
True 3 5
True 8 5

 

今回は、C#側でAddというメソッドをいくつかのオーバーロードを用意して、Python側から呼び出しています。

Pythonからでも、ほぼ、C#と同じ感覚でメソッドを呼び出す事ができます。

少々異なるのが、C#のメソッドで引数にrefoutを点けて参照渡しにしているメソッドは、Python側の引数に値が戻ってくる事はなく、Python.NET側で勝手にタプルになって、値が戻されます。

 

まとめ

Python.NETを使うと、C#のDLLに手を加える事なく、そのまま使用する事が出来るようになります。

私の仕事では、まだまだ.NET Frameworkを使う事が多く、自社製品もC#の対応は、ほぼ、行っていますが、Pythonの対応は出来ていないというのが現状です。

しかしながら、画像を撮り込んでDeepLearningをしたいとなると、やはりPythonでカメラの撮影をしたくなるので、とりあえず、Python.NETを使うと、何もすること無くC#のライブラリがPythonから使えてしまうのは、とてもありがたいです。

参考

https://pythonnet.github.io/pythonnet/python.html

3点,4点からなる二直線のなす角度を求める

3点,4点からなる二直線のなす角度を求めるというのは、以下の図のように、各点を通る二直線のなす角度を求める方法を紹介したいと思います。

 

 

4点からなる二直線の角度は、2点が重なり合うように、直線を構成している2点を平行移動すれば、3点の場合と同じ様になるので、ここでは、3点からなる二直線のなす角度を求める方法で説明します。

 

普通のやり方

まず、最初に思いつくのが、2直線のそれぞれの線の傾きをアークタンジェントを使って求め、2つの角度の差を求める方法だと思います。

 

$$\theta_{1}=tan^{-1}(\frac{A_{y}-B_{y}}{A_{x}-B_{x}})$$

$$\theta_{2}=tan^{-1}(\frac{C_{y}-B_{y}}{C_{x}-B_{x}})$$

$$\theta = \theta_{2}-\theta_{1}$$

 

ただし、直線が垂直の場合、アークタンジェントの計算時の分母が0になり、0除算になってしまうため、プログラム上はアークタンジェントの関数は atan() を使うのではなく、 atan2()を使った方がいいです。

atan2()関数を使うと、戻り値が-180°~+180°となりますが、この180°の位置をまたぐ角度の算出には注意が必要です。

 

他にも、2直線のなす角度を求める方法として、内積、外積を使った方法があるので、これを紹介します。

 

内積を使った方法

2つの直線を下図のように2つのベクトルとして捉え、内積で角度を求めます。

ベクトルa と ベクトルb を

$$\overrightarrow{a}=(a_{x}, a_{y}) = (A_{x}-B_{x}, A_{y}-B_{y})$$

$$\overrightarrow{b}=(b_{x}, b_{y}) = (C_{x}-B_{x}, C_{y}-B_{y})$$

とします。

 

ベクトルa と ベクトルb の内積は

$$\overrightarrow{a}\cdot \overrightarrow{b}=a_{x}b_{x} + a_{y}b_{y} = |\overrightarrow{a}||\overrightarrow{b}|cos\theta$$

 

であるから、式を変形して、

$$cos\theta=\frac{a_{x}b_{x} + a_{y}b_{y}}{|\overrightarrow{a}||\overrightarrow{b}|}$$

$$cos\theta=\frac{a_{x}b_{x} + a_{y}b_{y}}{\sqrt{{a_{x}}^{2}+{a_{y}}^{2}}\sqrt{{b_{x}}^{2}+{b_{y}}^{2}}}$$

$$\theta=cos^{-1}\left(\frac{a_{x}b_{x} + a_{y}b_{y}}{\sqrt{{a_{x}}^{2}+{a_{y}}^{2}}\sqrt{{b_{x}}^{2}+{b_{y}}^{2}}}\right)$$

として、角度θを求める事ができます。

ただし、アークコサインで求める事の出来る角度は0°~180°なので、マイナスの角度を求める事ができず、ベクトルbがベクトルaの右側か?左側か?の判断はできません。

 

右側か?左側か?の情報が必要な場合は、外積を用います。

 

外積を使った方法

外積については、少しおさらいです。

内積の結果はノルム(大きさ)となりますが、外積の結果はベクトルです。

外積の計算は

$$\overrightarrow{a}=(a_{x},a_{y},a_{z})$$

$$\overrightarrow{b}=(b_{x},b_{y},b_{z})$$

とすると、外積の結果は

$$\overrightarrow{a}\times \overrightarrow{b}=(a_{y}b_{z}-b_{y}a_{z},a_{z}b_{x}-b_{z}a_{x},a_{x}b_{y}-b_{x}a_{y})$$

さらに、外積を行う2つのベクトルからなる平行四辺形の面積が、外積の大きさと一致します。

ここで、外積の計算は三次元ベクトルで行うのですが、外積を行う2つのベクトルのz成分を0にして、外積を計算すると、外積の結果の大きさは、外積の結果のz成分と一致します。

$$\overrightarrow{a}=(a_{x},a_{y},0)$$

$$\overrightarrow{b}=(b_{x},b_{y},0)$$

$$|\overrightarrow{a}\times \overrightarrow{b}|=a_{x}b_{y}-b_{x}a_{y}=|\overrightarrow{a}||\overrightarrow{b}|sin\theta$$

となります。

ここから式を変形して

$$sin\theta=\frac{a_{x}b_{y}-b_{x}a_{y}}{|\overrightarrow{a}||\overrightarrow{b}|}$$

$$sin\theta=\frac{a_{x}b_{y}-b_{x}a_{y}}{\sqrt{{a_{x}}^{2}+{a_{y}}^{2}}\sqrt{{b_{x}}^{2}+{b_{y}}^{2}}}$$

$$\theta=sin^{-1}\left(\frac{a_{x}b_{y}-b_{x}a_{y}}{\sqrt{{a_{x}}^{2}+{a_{y}}^{2}}\sqrt{{b_{x}}^{2}+{b_{y}}^{2}}}\right)$$

 

として、角度θを求める事ができます。

アークサインで求まる角度は-90°~90°となるので、ベクトルa と ベクトルb の位置関係を判断することができます。

θの値がプラスの場合、ベクトルb は ベクトルa に対して、反時計周りの位置にあります。

θの値がマイナスの場合、ベクトルb は ベクトルa に対して、時計周りの位置にあります。

ただし、この計算だと±90°の範囲しか計算できませんが、その範囲以上の角度を求めたい場合は、ベクトルa と ベクトルb の内積の値も使います。

 

ベクトルa と ベクトルb の内積の値が正であれば、θは±90°の範囲内

ベクトルa と ベクトルb の内積の値が負であれば、θは±90°の範囲外

 

となるので、360°の範囲で、ベクトルa と ベクトルb の位置関係を求める事ができるようになります。

 

まとめ

●アークタンジェントで角度を求めるときは、atan()関数でなくatan2()関数を使う。

●内積や外積を使って角度を求めることもできる。

●内積だと、2つの直線の位置関係がわかりませんが、外積だと位置関係が分かる。

 

参考

内積

外積

【OpenCV-Python】momentsによる重心の計算

画像処理において、重心は、欠陥部分の中心位置の算出や、レーザー光のように山なりの輝度分布を持つ画像の輝度値のピーク位置を求めるのに使っています。

 

 

 

 

 

 

 

 

 

重心の計算方法はこちらのページでも紹介しているように、計算そのものは、以下の計算式で求める事ができます。

 

$$重心=\frac{(重さ\times位置)の合計}{重さの合計}$$

 

ここで、画像処理における重さとは、画像の各画素の輝度値を差すので、

 

$$重心=\frac{(輝度値\times位置)の合計}{輝度値の合計}$$

 

となります。

 

OpenCVでは moments()関数を使って、重心を求めるのですが、OpenCVのページによると、モーメントは

$$m_{ji}=\sum_{x,y}(array(x,y)\cdot x^{j}y^{i})$$

で求められ、重心は

$$\overline{x}=\frac{m_{10}}{m_{00}},\overline{y}=\frac{m_{01}}{m_{00}}$$

となります。

と、書かれていいるのですが、式が何だか難しくて、よくわかりません。。

 

そこで、m00, m10, m01 を詳しく見ていきます。

 

m00は、

$$m_{00}=\sum_{x,y}(array(x,y)\cdot x^{0}y^{0})$$

$$=\sum_{x,y}(array(x,y)\cdot 1 \cdot 1)$$

$$=\sum_{x,y}(array(x,y))$$

 

となり、m00array(x,y)の合計ということがわかります。

array(x, y)は、位置が(x, y)の画素の輝度値を表しているので、m00 が画像の輝度値の合計となります。

 

m10は、

$$m_{10}=\sum_{x,y}(array(x,y)\cdot x^{1}y^{0})$$

$$=\sum_{x,y}(array(x,y) \cdot x \cdot 1)$$

$$=\sum_{x,y}(array(x,y) \cdot x )$$

となり、m10は画像の輝度値とx座標を掛け合わせた合計という事がわかります。

 

同様にm01は、

$$m_{01}=\sum_{x,y}(array(x,y)\cdot x^{0}y^{1})$$

$$=\sum_{x,y}(array(x,y) \cdot 1 \cdot y)$$

$$=\sum_{x,y}(array(x,y) \cdot y )$$

となり、m01は画像の輝度値とy座標を掛け合わせた合計という事がわかります。

 

ここで、重心は

$$重心のx座標=\frac{(輝度値\times x座標)の合計}{輝度値の合計}$$

$$重心のy座標=\frac{(輝度値\times y座標)の合計}{輝度値の合計}$$

となることから、重心の座標が

$$\overline{x}=\frac{m_{10}}{m_{00}},\overline{y}=\frac{m_{01}}{m_{00}}$$

で、求まる事が分かります。

 

実際のOpenCVで重心を求めるプログラムを示します。

 

画像全体の重心を求めるプログラム

import cv2

# グレースケールで画像を読み込む
img = cv2.imread("image.bmp", cv2.IMREAD_GRAYSCALE);

# 画像表示用に入力画像をカラーデータに変換する
img_disp = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

# モーメントの計算
m = cv2.moments(img, False)
# 重心の計算
x,y= m['m10']/m['m00'] , m['m01']/m['m00']
print(f"Weight Center = ({x}, {y})")

# 座標を四捨五入
x, y = round(x), round(y)

# 重心位置に x印を書く
cv2.line(img_disp, (x-5,y-5), (x+5,y+5), (0, 0, 255), 2)
cv2.line(img_disp, (x+5,y-5), (x-5,y+5), (0, 0, 255), 2)

# 結果の表示
cv2.imshow("Image", img_disp)
cv2.waitKey()

実行結果

 

画像の輪郭から重心を求める方法

findContours()関数により、連続する領域の輪郭座標を求め、輪郭座標から、それぞれの重心を求めるには、以下のようにします。

import cv2

# グレースケールで画像を読み込む
img = cv2.imread("image.bmp", cv2.IMREAD_GRAYSCALE);

# 画像表示用に入力画像をカラーデータに変換する
img_disp = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)

# 二値化
ret,img = cv2.threshold(img,127,255,cv2.THRESH_BINARY)

# 輪郭情報の取得
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) 

# 輪郭ごとの処理
for i, contour in enumerate(contours):
    # 重心の計算
    m = cv2.moments(contour)
    x,y= m['m10']/m['m00'] , m['m01']/m['m00']
    print(f"Weight Center = ({x}, {y})")
    # 座標を四捨五入
    x, y = round(x), round(y)
    # 重心位置に x印を書く
    cv2.line(img_disp, (x-5,y-5), (x+5,y+5), (0, 0, 255), 2)
    cv2.line(img_disp, (x+5,y-5), (x-5,y+5), (0, 0, 255), 2)

# 結果の表示
cv2.imshow("Image", img_disp)
cv2.waitKey()

実行結果

 

輪郭から求める重心の座標について

輪郭のから求める重心は、輪郭の座標だけの重心を求めているのではなく、グリーンの定理というのを使って輪郭の内側が塗りつぶされた状態の重心を求めているとのこと。

例えば、もし、下図のような図形の輪郭座標だけの重心を求めると、画像全体から求めた重心よりも輪郭から求めた重心の方が、少し、左側の位置に重心がズレそうですが、ほぼ、同じ位置の重心となります。
(輪郭から求めた重心は、少し誤差が出ます。)

 

参照ページ

https://docs.opencv.org/4.x/d8/d23/classcv_1_1Moments.html

重心の計算方法

http://opencv.jp/opencv-2.1/cpp/structural_analysis_and_shape_descriptors.html?highlight=moment

https://en.wikipedia.org/wiki/Green%27s_theorem

 

重心の計算方法

重心とは、重さの中心で、重心の位置で、力がつり合います。

 

重心の計算方法は、言葉で表すと

$$重心=\frac{(重さ\times位置)の合計}{重さの合計}$$

となります。

 

具体的な計算例を示すと、

のデータに関して、重心を計算すると、

$$重心=\frac{2\times0+5\times1+13\times2+15\times3+5\times4}{2+5+13+15+5}
=2.4$$

 

となります。

 

さらに二次元的な座標においても、各座標軸に関して、それぞれ計算を行います。

こちらも具体的に、以下のようなデータの場合、

$$重心のx座標=\frac{3\times0+10\times1+7\times2+5\times0+20\times1+7\times2+2\times0+9\times1+3\times2}{3+10+7+5+20+7+2+9+3}$$

$$= 1.1$$

$$重心のy座標=\frac{3\times0+5\times1+2\times2+10\times0+20\times1+9\times2+7\times0+7\times1+3\times2}{3+5+2+10+20+9+7+7+3}$$

$$= 0.9$$

となります。(分母の重さの合計は、計算の順番が分かりやすいように、順番を入れ替えています。)

 

画像処理において重心を計算する場合は、重さの値に画像の輝度値を用いますが、二値化した画像に対して重心の計算を行い、重さを考慮しない場合もありますが、この場合は、重心の座標は、しきい値以上となる画素の座標の平均と一致します。

【OpenCV-Python】カメラのフレームレートは設定できない?

OpenCVでカメラのフレームレート(fps: frames per second)を設定するには、文法上は

import cv2

# カメラを開く
cap = cv2.VideoCapture(0)

# フレームレートを設定
ret = cap.set(cv2.CAP_PROP_FPS, 15)

のようにすると、フレームレートを15fpsに設定した事になります。

しかしながら、実際にカメラで画像を撮影してみると、フレームレートが変わったように感じません。

OSがLinuxではフレームレートを設定出来ているような情報を見かけるのですが、Windowsでは、あまり見かけません。

 

という事で、Windows環境でフレームレートが設定できるのか?評価してみました。

 

評価環境

カメラ:Anker PowerConf C200

OS:Windows11

Python 3.11

opencv-python 4.7.0.72

 

評価方法

以下のサンプルプログラムを実行し、300枚撮影するのに掛かった時間を計測し、fpsを計算しました。

import cv2
import time

# カメラを開く
cap = cv2.VideoCapture(0)

# フレームレートを設定
ret = cap.set(cv2.CAP_PROP_FPS, 15)
# 設定されているフレームレートを取得
print(cap.get(cv2.CAP_PROP_FPS))

# 撮影開始時間
start = time.perf_counter()

# 画像を300枚撮影する
for i in range(300):
    # 画像をキャプチャする
    ret, frame = cap.read()

# 画像300枚を撮影するのにかかった時間からfpsの計算
print(f"{300/(time.perf_counter() - start)}fps")

# カメラを閉じる
cap.release()
# すべてのウィンドウを閉じる
cv2.destroyAllWindows()

このプログラムを実行すると、

ret = cap.set(cv2.CAP_PROP_FPS, 15)

の部分でfpsを15に設定すると、戻り値(ret)は Trueとなり、一見、設定できているように見えるのですが、

print(cap.get(cv2.CAP_PROP_FPS))

として、フレームレートを取得すると30fpsとなり、実際にはフレームレートは設定できていませんでした。

そこで、

cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)

として、DirectShowを使ってカメラを開き、set()関数でfpsを15fpsに設定し、get()関数で設定したフレームレートを取得すると、ちゃんと変更されている事が確認できます。

しかしながら、実際にread()関数でカメラのフレーム画像を取得する時間を計算してみると、29.3fpsとなり、結局のところ、フレームレートは設定できていませんでした。

CAP_DSHOW(DirectShow)以外にCAP_MSMF(Microsoft Media Foundation)など、いろいろ試してみましたが、設定できず。

 

もちろん、fpsが設定できるかどうかは、カメラ依存の部分もあろうかと思いますが、3つのカメラで評価してみたところ、フレームレートを変更することはできませんでした。。

結局のところ、fpsを落とすには、フレームを間引くしか無いのかも?

 

参考

https://docs.opencv.org/4.7.0/d8/dfe/classcv_1_1VideoCapture.html

https://docs.opencv.org/4.7.0/d4/d15/group__videoio__flags__base.html#gga023786be1ee68a9105bf2e48c700294da77ab1fe260fd182f8ec7655fab27a31d

【OpenCV-Python】Webカメラから画像をキャプチャして保存する

OpenCVでWebカメラ(USBカメラ)から画像をキャプチャして保存する方法を紹介します。

必要なもの

●Python

●OpenCV(pip install opencv-pythonでOpenCVをインストールします)

●Webカメラ(USBカメラ)

 

サンプルプログラム

OpenCVでカメラから画像を取得し、画像ファイルに保存するサンプルプログラムを以下に示します。

import cv2

# カメラを開く
cap = cv2.VideoCapture(0)

# 画像をキャプチャする
ret, frame = cap.read()

# 画像を保存する
cv2.imwrite("image.jpg", frame)

# カメラを閉じる
cap.release()

これで、Webカメラから画像を取得し、 image.jpg というファイル名で画像が保存されます。

 

次に各コードの説明をします。

 

VideoCapture()

cap = cv2.VideoCapture(0)

の部分で、カメラを開いています。

関数の引数で渡している  は、最初に見つけたカメラを開く事を意味しています。

ほとんどの場合、0 で大丈夫だと思いますが、例えば、PC内臓のカメラのあるノートPCに、別途、USBカメラを刺して使う場合には、この番号を 1 や 2 に調整してみてください。

 

read()

ret, frame = cap.read()

この部分で、現在、撮影している画像データを取得します。

画像の取得し失敗すると ret は Falseになります。

失敗する原因としては、VideoCaptureの部分で指定する番号を間違えたとか、他のプログラムでカメラを使用していた場合などがあります。

frameが撮影した画像データとなります。

 

その他のサンプルを以下に示します。

 

画像をリアルタイムに表示するサンプル

import cv2

# カメラを開く
cap = cv2.VideoCapture(0)

while True:
    # 画像をキャプチャする
    ret, frame = cap.read()

    # 画像を表示する
    cv2.imshow("Image", frame)

    # `q`キーを押すとループを終了する
    if cv2.waitKey(1) == ord('q'):
        break

# カメラを閉じる
cap.release()
# すべてのウィンドウを閉じる
cv2.destroyAllWindows()

このサンプルで q キーを押すまで画像を取得し、表示します。

 

キーを押すたびに連番でファイルに保存するサンプル

import cv2

# カメラを開く
cap = cv2.VideoCapture(0)

indexNo = 0
while True:
    # 画像をキャプチャする
    ret, frame = cap.read()

    # 画像を表示する
    cv2.imshow("Image", frame)

    # `q`キーを押すとループを終了する
    if cv2.waitKey(1) == ord('q'):
        break
    elif cv2.waitKey(1) == ord('c'):
        cv2.imwrite(f"image{indexNo:05d}.jpg", frame)
        indexNo = indexNo + 1
# カメラを閉じる
cap.release()
# すべてのウィンドウを閉じる
cv2.destroyAllWindows()

このサンプルでは、一つ前のサンプルに加えて、 c  キーを入力するたびに、連番のjpegファイルに画像を保存します。

 

read()での画像の取得タイミングについて

ここからが、個人的に興味のあった部分。

画像を取得する際に、read()関数をwhile文で繰り返し呼んでいますが、カメラのフレームレートよりも速くread()関数が呼ばれている可能性が高いです。

このとき、read()関数は、呼ばれるたびに、即、画像データを取得しているのか?それとも、1フーレムの撮影が終わるのを待ってから、画像を取得しているのか?気になった。。

 

そこで、こんなプログラムを実行してみました。

import cv2
import time

# カメラを開く
cap = cv2.VideoCapture(0)

# 撮影開始時間
start = time.perf_counter()
for i in range(300):
    # 画像をキャプチャする
    ret, frame = cap.read()

# 画像300枚を撮影するのにかかった時間
print(time.perf_counter() - start)
# カメラを閉じる
cap.release()
# すべてのウィンドウを閉じる
cv2.destroyAllWindows()

このサンプルプログラムでは、30fpsのカメラを使って、300枚の画像を撮影しています。

 

実際に、プログラムを実行すると、300枚の画像を撮影するのにかっかった時間は、10.4秒という結果になりました。

30fpsのカメラで300枚撮影しているので、計算上は300枚撮影するのに10秒かかる事になり、ほぼ、理論通りの結果となりました。

このことから、read()関数が呼ばれたら、即、画像データを取得しているのではなく、カメラが1フレーム分、撮影が完了するのを待ってから画像を取得しているものと、推測されます。

少なくとも、read()関数を繰り返し呼んでも、同じ画像を重複して取得する事は無さそうです。

【Microsoft Loop】日本語のスペルチェックの設定

初期設定状態のLoopで日本語の文章を書くと、スペルチェックが日本語に対応していないため、赤い波線が表示され、あまり使い物にならないように感じます。

 

 

そこで、スペルチェックの設定に日本語を追加します。

追加方法を以下の順で行います。

  ①赤い波線が表示されている部分を左クリック

  ②・・・ の部分を左クリック

  ③言語の管理 を左クリック

 

表示されたウィンドウの 別の言語の追加 をクリックします。

 

表示された言語の一覧から日本語の選択し、 追加 をクリックします。

 

さらに、スペルチェックを日本語を優先的に行うように設定を行います。

  ①日本語(日本)と表示されている部分の右の方の ・・・ の部分をクリック

  ②一番上へ移動 をクリック

  ③完了 をクリック

 

 

これで、日本語もスペルチェックの対象となり、不必要な赤い波線は表示されなくなります。

もし、波線の表示が残ってしまう場合は、一旦、別の表示を表示してから、元のページを表示すると、波線は表示されなくなりました。