【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

二値化

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

【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(トラックバー、スライダー)

【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

 

【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()関数を繰り返し呼んでも、同じ画像を重複して取得する事は無さそうです。

【OpenCV-Python】矩形抽出(矩形度)

以前、輪郭の内側の面積と周囲長の関係から円形度なる値を求め、円らしき領域を抽出する方法を行いました。

【OpenCV-Python】円形度

 

この考え方を応用して、今度は、矩形度(長方形らしさ)なる値を求めて、矩形らしき領域を抽出する方法を考えてみたいと思います。

矩形(長方形)は、名刺や本、コピー用紙のように、割と良く目にする図形なので、応用範囲は広いかと思います。ただし、今回紹介する方法は、四角形の被写体を斜めから撮影して、台形のようになってしまう画像には対応できません。

 

今回は、OpenCVを用いて、輪郭の内側面積contourArea()関数で求め、輪郭を囲う最小の矩形領域の面積minAreaRect()関数で求めて、この2つの面積の比で、矩形度なる値を求めます。

ただし、矩形度という言葉や計算方法が教科書的に存在するのかは不明です。(私が勝手に言ってます。)

この2つの面積が一致していれば完全に矩形(長方形)となります。

矩形度(小) 矩形度(中) 矩形度(大)

 

サンプルプログラム

矩形度を求め、下図の中から矩形らしい部分を抽出するサンプルプログラムは以下の通りです。

(サンプルプログラム)

import cv2
import numpy as np

def rectangularity(contour):
    '''
    矩形度を求める

    Parameters
    ----------
    contour : ndarray
        輪郭の(x,y)座標の配列

    Returns
    -------
        矩形度

    '''
    # 面積
    area = cv2.contourArea(contour)
    # 傾いた外接する矩形領域
    _, (width, height), _ = cv2.minAreaRect(contour)

    # 矩形度を返す
    return area / width / height

# 8ビット1チャンネルのグレースケールとして画像を読み込む
img = cv2.imread("image.jpg", cv2.IMREAD_GRAYSCALE) 

# 白黒反転して二値化
ret, img = cv2.threshold(img, 128, 255, cv2.THRESH_BINARY_INV)

# 一番外側の輪郭のみを取得
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) 

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

# 全ての輪郭を描画
cv2.drawContours(img_disp, contours, -1, (0, 0, 255), 2)

# 輪郭の点の描画
for contour in contours:
    # 傾いた外接する矩形領域の描画
    rect = cv2.minAreaRect(contour)
    box = cv2.boxPoints(rect)
    box = np.intp(box)
    cv2.drawContours(img_disp,[box],0,(0,255,255), 1)
    # 矩形度の計算
    val = rectangularity(contour)
    # 輪郭の矩形領域
    x,y,w,h = cv2.boundingRect(contour)
    # 矩形度の描画
    cv2.putText(img_disp, f"{val:.3f}", (x, y-10), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 1, cv2.LINE_AA)
    # 円らしい領域(円形度が0.85以上)を囲う
    if val > 0.85:
        cv2.rectangle(img_disp,(x-10,y-10),(x+w+10,y+h+10),(255,0,0),2) # 少し外側を囲う

cv2.imshow("Image", img_disp)

# キー入力待ち(ここで画像が表示される)
cv2.waitKey()

実行結果

 

関連記事

【OpenCV-Python】findContoursによる輪郭検出

【OpenCV-Python】輪郭(contour)の面積(contourArea)

【OpenCV-Python】輪郭(contour)の矩形領域の取得

【OpenCV-Python】円形度

【OpenCV-Python】デモザイキング(Bayer変換)

デモザイキング(Demosaicing)とは、一般的なカラーカメラでは、センサの各画素に下図のようなBayerパターンと呼ばれる配置で、フィルタが配置されています。

この状態で画像を撮影すると、画像そのものはモノクロ画像の市松模様のようなノイズがのったような画像が撮影されます。

この画像からカラー画像へ変換することをデモザイキング(Demosaicing) や、Bayer変換 と言います。

OpenCVでデモザイキングを行うにはcvtColor()関数の codeの部分にBayerパターンの配置の4種類、デモザイキング処理アルゴリズムの3種類の組み合わせの定数が用意されています。

 

●Demosaicing using bilinear interpolation

cv2.COLOR_BayerBG2BGR, cv2.COLOR_BayerGB2BGR, cv2.COLOR_BayerRG2BGR, cv2.COLOR_BayerGR2BGR

 

Demosaicing using VNG(Variable Number of Gradients).

cv2.COLOR_BayerBG2BGR_VNG, cv2.COLOR_BayerGB2BGR_VNG, cv2.COLOR_BayerRG2BGR_VNG, cv2.COLOR_BayerGR2BGR_VNG

 

●EA(Edge-Aware) Demosaicing.

cv2.COLOR_BayerBG2BGR_EA, cv2.COLOR_BayerGB2BGR_EA, cv2.COLOR_BayerRG2BGR_EA, cv2.COLOR_BayerGR2BGR_EA

 

(参考)

https://docs.opencv.org/3.4/d8/d01/group__imgproc__color__conversions.html#ga57261f12fccf872a2b2d66daf29d5bd0

 

コード例

import cv2

# 画像ファイルの読み込み
src_img = cv2.imread("Mandrill_RAW.bmp", cv2.IMREAD_UNCHANGED)

# デモザイキング(COLOR_BayerRG2BGR, COLOR_BayerRG2BGR_VNG, COLOR_BayerRG2BGR_EAなど)
color_img = cv2.cvtColor(src_img, cv2.COLOR_BayerRG2BGR)

# 画像の表示
cv2.imshow("Src Image", src_img)
cv2.imshow("Demosaicing Image", color_img)

# キー入力待ち(ここで画像が表示される)
cv2.waitKey()

実行結果

 

バイリニア補間で行うデモザイキングは昔からあったので、知っていましたが、

Variable Number of Gradients や Edge-Aware

というのがあることを最近知って、???となったのですが、詳しい処理アルゴリズムについては分かりませんでしたが、処理結果の比較を行いました。

 

RAWデータ

Bilinearの処理結果

一部拡大

エッジの部分(鼻の赤い部分の輪郭など)がギザギザしていたり、擬色(本来無いはずの色)が確認できます。

 

VNG(Variable Number of Gradients)の処理結果

一部拡大

Bilinearと比べて、エッジ部分のギザギザが抑えられ、擬色も低減されています。

 

EA(Edge-Aware)の処理結果

一部拡大

鼻の脇の水色部分の黒い線の模様を見ると、VNGと比べて線が滑らかになっているように見えます。

ヒゲの部分のように、細かいパターンが潰れがちです。

 

処理時間の比較

2048×2048画素のRAWデータを私のPC(i7-7700 2.8GHz)で試してみたところ、1000回処理行った時の平均処理時間は以下の通りでした。

処理アルゴリズム 処理時間(mSec)
Bilinear 3.2
Variable Number of Gradients 45
Edge-Aware 47

 

Bilinear以外の処理は、そこそこキレイな画像になりますが、カメラで撮影した画像をリアルタイムで処理しようとすると、Bilinear一択になりそうです。

参考リンク

【Kinect SDK】RawBayerデータとは?

カラーカメラはモノクロカメラを兼ねない

https://docs.opencv.org/3.4/d8/d01/group__imgproc__color__conversions.html

【OpenCV-Python】cvtColor(色変換)

【OpenCV-Python】cvtColorでBayer変換するときの定数

【OpenCV-Python】Tkinter GUI Sample

OpenCVのPython版でもC#のようなウィンドウのGUIプログラムを作りたい!

ということで、GUIにTkinterを使って、簡単なサンプルプログラムを作成しました。

 

ここで公開しているコードは自由に変更して使って頂いてもらって構いません。
どこかで公開する場合は、参照元を書いて頂けると嬉しいです。
エラー処理は甘めなので、自己責任でお願いします。

 

機能的には画像ファイル(日本語ファイルを含む)を開いてtkinterのCanvas上に画像を表示します。

画像は、マウス操作で上下左右の移動と、マウスホイールで拡大縮小が可能になっています。

マウスのダブルクリックで画像全体を表示します。

 

OpenCVの処理そのものは二値化とガウシアンフィルタのみの至ってシンプルなものなので、いろいろと追加してお試しください。

 

ソースコードはこちら↓

 

GitHubにもソースコードは上げておきました。

https://github.com/ImagingSolution/OpenCVTkinterGUISample

 

関連記事

【Python】画像ビューア(ズーム(拡大/縮小)、移動表示)

【Python/tkinter】ウィジェットの配置(pack)

【Python/tkinter】ウィジェットの配置(grid)

【Python/tkinter】Label(ラベル)

【Python/tkinter】Button(ボタン)

【Python/tkinter】Entry(テキストボックス)

【Python/tkinter】Menu(メニュー)

【Python/tkinter】Frame(フレーム)

【Python/tkinter】Canvas(キャンバス)の作成

【OpenCV/Python】日本語の画像ファイル読込・保存

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

【OpenCV-Python】resize(画像の拡大縮小)

OpenCVで画像のリサイズを行うには、resize関数を用います。

resize関数では、リサイズ後の画像の大きさ(幅と高さ)を指定する方法と、リサイズのスケールを指定する方法があります。

構文

resize( src, dsize[, dst[, fx[, fy[, interpolation]]]] ) -> dst
src リサイズを行う画像データを指定します。
dsize リサイズ後の画像サイズ(幅, 高さ)を指定します。
倍率(fx, fy)を指定するときは、Noneを指定します。
fx 横方向の倍率を指定します。
fy 縦方向の倍率を指定します。
interpolation 補間モードを以下の中から指定します。
cv2.INTER_NEAREST
cv2.INTER_LINEAR
cv2.INTER_CUBIC
cv2.INTER_AREA
cv2.INTER_LANCZOS4

 

出力サイズを指定してリサイズする方法

リサイズ後のサイズを(幅, 高さ)を指定してリサイズを行います。

サンプルプログラム

import cv2

# 画像の読込
src = cv2.imread("image.bmp", cv2.IMREAD_UNCHANGED);

# 出力サイズ(幅、高さ)を指定してリサイズ
dst = cv2.resize(src, (400, 200))

# 画像の表示
cv2.imshow("Src Image", src)
cv2.imshow("Resize Image", dst)
cv2.waitKey()

実行結果

倍率を指定してリサイズする方法

倍率はfx, fyを指定するのですが、出力後のサイズ(dsize)は省略できないので、dsizeにNoneを指定します。

指定方法は

dst = cv2.resize(src, None, None, 0.8, 1.2)

や、変数名を指定して

dst = cv2.resize(src, None, fx = 0.8, fy = 1.2)

のようにします。

サンプルプログラム

import cv2

# 画像の読込
src = cv2.imread("image.bmp", cv2.IMREAD_UNCHANGED);

# スケール(横方向、縦方向)を指定してリサイズ
dst = cv2.resize(src, None, None, 0.8, 1.2)

# 画像の表示
cv2.imshow("Src Image", src)
cv2.imshow("Resize Image", dst)
cv2.waitKey()

実行結果

補間方法の指定

リサイズ時の補間方法については

cv2.INTER_NEAREST
cv2.INTER_LINEAR
cv2.INTER_CUBIC
cv2.INTER_AREA
cv2.INTER_LANCZOS4

の中から指定します。

指定方法は

dst = cv2.resize(src, None, None, 0.8, 1.2, cv2.INTER_NEAREST)

や、変数名を指定して

dst = cv2.resize(src, None, fx = 0.8, fy = 1.2, interpolation = cv2.INTER_NEAREST)

のようにします。

 

補間モードについてはこちら↓を参照ください。

【OpenCV-Python】アフィン変換(同次座標系を使用)

 

補間そのものについてはこちら↓を参照ください。

画素の補間(Nearest neighbor,Bilinear,Bicubic)の計算方法

 

参照ページ

https://docs.opencv.org/4.6.0/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d

【OpenCV-Python】JPEG画像の品質を指定して保存する

OpenCVで画像をファイルに保存するには imwrite 関数を用いますが、この関数でjpegファイルの品質を指定して保存することが可能です。

imwrite関数の書式は

imwrite(filename, img[, params]) ->retval
引数 説明
filename ファイル名
img 画像データ
params オプション設定(省略可)

 

となりますが、paramsの部分に jpegファイルの品質の値(0~100)を指定することが可能です。

品質を指定して保存するサンプルは以下の通りです。

import cv2

# 画像を開く
img = cv2.imread("Mandrill.bmp", cv2.IMREAD_UNCHANGED)

# JPEGで品質(75)を指定して画像を保存する
cv2.imwrite("Mandrill_q75.jpg", img, [cv2.IMWRITE_JPEG_QUALITY, 75])

品質の値による画像の違いは、Pillowのときの例を参照ください↓

【Python/Pillow(PIL)】JPEG画像の品質を指定して保存する

 

Pillowでは品質の初期値が75であったのに対して、OpenCVでは95になります。

OpenCVでは画像処理用として画像ファイルに保存されることが多いので、品質の値が大きめな初期値になっているのだと思いますが、品質95だと、jpeg画像特有のモスキートノイズのようなノイズは見た目上、ほぼ無いものの、輝度値レベルで見ると、1程度の計算誤差レベル?の誤差はあります。

画像処理用として画像ファイルに保存するには、基本的にはビットマップファイル(*.bmp)に保存することをおススメします。

参考ページ

https://docs.opencv.org/4.6.0/d4/da8/group__imgcodecs.html#gabbc7ef1aa2edfaa87772f1202d67e0ce

https://docs.opencv.org/4.6.0/d8/d6a/group__imgcodecs__flags.html#ga292d81be8d76901bff7988d18d2b42ac