Warning: Cannot modify header information - headers already sent by (output started at /home/cra-log/www/index.php:1) in /home/cra-log/www/site-admin-wp/wp-includes/feed-rss2.php on line 8
OpenCV – CRAFT GoGo https://craft-gogo.com Raspberry Pi、3Dプリンタ、プログラミング、DIYなど、ものづくりを中心に趣味レベルで情報を発信! Sat, 30 Mar 2024 23:44:44 +0000 ja hourly 1 https://wordpress.org/?v=6.3.4 https://craft-gogo.com/site-admin-wp/wp-content/uploads/2021/05/cropped-CRAFT-GOGO-LOGO-32x32.png OpenCV – CRAFT GoGo https://craft-gogo.com 32 32 【Python+OpenCV】顔検出応用!目と笑顔の検出方法 https://craft-gogo.com/opencv-eyedetection/ Fri, 18 Aug 2023 00:02:21 +0000 https://craft-gogo.com/?p=3506

以前の記事でOpenCVで顔検出するプログラムを解説しました。 今回は、顔検出の応用として、目と笑顔を検出する方法について解説します。 サンプルプログラム こちらが目、笑顔を検出するサンプルプログラムです。 実行結果がこ […]]]>

以前の記事でOpenCVで顔検出するプログラムを解説しました。

今回は、顔検出の応用として、目と笑顔を検出する方法について解説します。

サンプルプログラム

こちらが目、笑顔を検出するサンプルプログラムです。

import cv2 as cv

# カスケード分類器読み込み
frontalface_cascade = cv.CascadeClassifier("haarcascade_frontalface_default.xml")
eye_cascade = cv.CascadeClassifier("haarcascade_eye.xml")
smile_cascade = cv.CascadeClassifier("haarcascade_smile.xml")

# 画像の読み込み
img = cv.imread("sample.jpg")

# 顔検出
face = frontalface_cascade.detectMultiScale(img)

# 顔部を赤枠で囲む
for x, y, w, h in face:
    cv.rectangle(img,(x,y), (x+w, y+h), (0,0,255), 1)
    
    # 顔部の上3/4を切り取り
    upper_face_img = img[y:int(y+3*h/4), x:x+w]
    
    # 目検出
    eye = eye_cascade.detectMultiScale(upper_face_img)
    
    # 目部を青枠で囲む
    for x1, y1, w1, h1 in eye:
        cv.rectangle(img, (x+x1,y+y1), (x+x1+w1, y+y1+h1), (255,0,0), 1)
      
    # 顔部の下半分を切り取り  
    under_face_img = img[int(y+h/2):(y+h), x:x+w]

    # 笑顔検出
    smile = smile_cascade.detectMultiScale(under_face_img)
     
    # 笑顔を緑枠で囲む
    for x2, y2, w2, h2 in smile:
        cv.rectangle(img,(x+x2, int(y+h/2)+y2), (x+x2+w2, int(y+h/2)+y2+h2), (0,255,0), 1)

cv.imshow("img", img)
cv.waitKey(0)
cv.destroyAllWindows()

実行結果がこちら

3人とも目が検出できました。右2人は笑っているので、口元が笑顔と判定されています。

次章ではサンプルプログラムを解説していきます。

プログラム解説

サンプルプログラムの流れは以下のとおりとなっています。

STEP
カスケード分類器読み込み
STEP
画像の読み込み
STEP
顔の検出
STEP
目の検出
STEP
笑顔の検出

STEP1~3は、別記事で解説していますので、本記事では割愛しました。

ここでは、STEP4: 目の検出、STEP5: 笑顔の検出について解説していきます。

目の検出

目を検出するには「haarcascade_eye.xml」を使用します。

# カスケード分類器読み込み
eye_cascade = cv.CascadeClassifier("haarcascade_eye.xml")

# 顔部の上3/4を切り取り
upper_face_img = img[y:int(y+3*h/4), x:x+w]

# 目検出
eye = eye_cascade.detectMultiScale(upper_face_img)

# 目部を青枠で囲む
for x1, y1, w1, h1 in eye:
    cv.rectangle(img, (x+x1,y+y1), (x+x1+w1, y+y1+h1), (255,0,0), 1)mg)

顔部の上3/4を切り取り、この中から目を検出して青枠で囲むということをしています。

もし、顔部を切り取らなかったらどうなるのでしょうか?

下図は顔部を切り取らなかった場合の実行結果です。

左の女性のおでこと口が目と誤判定されてしまいました。目は顔の上部に位置していますので、誤判定しないためにも検出範囲を顔の上部のみに絞る方が良いことが分かります。

笑顔の検出

笑顔を検出するのは「haarcascade_smile.xml」です。

# カスケード分類器読み込み
smile_cascade = cv.CascadeClassifier("haarcascade_smile.xml")

# 顔部の下半分を切り取り  
under_face_img = img[int(y+h/2):(y+h), x:x+w]

# 笑顔検出
smile = smile_cascade.detectMultiScale(under_face_img)
    
# 笑顔を緑枠で囲む
for x2, y2, w2, h2 in smile:
    cv.rectangle(img,(x+x2, int(y+h/2)+y2), (x+x2+w2, int(y+h/2)+y2+h2), (0,255,0), 1)

目の検出同様、顔部の下半分を切り取り、この中から笑顔を検出して緑枠で囲みます。

こちらも顔部を切り取らなかった場合を確認してみましょう。

様々なところで笑顔が検出されてしまいました。検出範囲を絞ることが重要です!

色々な画像で目・笑顔の検出

こちらの画像では右側女性の右目が検出出来ませんでした。

こちらの写真は3人とも笑顔ではないですが、口元が笑顔と検出されています。

笑顔検出というよりは、口検出に近いですね。

まとめ

顔検出の応用として、OpenCVの「カスケード分類器」を使用した目・笑顔検出プログラムについて解説しました。

手順のおさらい

  • カスケード分類器読み込み
  • 画像の読み込み
  • 顔の検出
  • 目の検出
  • 笑顔の検出

今回の記事が皆さんのPython学習に役立つなら幸いです。

Python独学が大変な方は、書籍やスクールを活用するのも手です。

]]>
【画像処理基礎】ノイズを除去しつつエッジを抽出するソーベルフィルタ https://craft-gogo.com/sobel-fillter/ Sat, 13 May 2023 02:05:24 +0000 https://craft-gogo.com/?p=3164

物体のエッジを抽出するプログラムを作りたいが、どのように作ればよいか分からない。エッジ抽出の原理について詳しく知りたい。 そんな悩みを解消すべく本記事では、ノイズを除去しつつエッジ抽出するソーベルフィルタについて解説しま […]]]>

物体のエッジを抽出するプログラムを作りたいが、どのように作ればよいか分からない。エッジ抽出の原理について詳しく知りたい。

そんな悩みを解消すべく本記事では、ノイズを除去しつつエッジ抽出するソーベルフィルタについて解説します。

まず、エッジ抽出原理を解説します。次にソーベルフィルタとは何かについての解説です。最後にPython,OpenCVを用いて画像のエッジを抽出するプログラムを解説します。

それでは始めましょう!

この記事はこんな人におすすめ!

  • 画像処理を学んでいる初心者
  • エッジ抽出の技術を理解したい人
  • プログラミングで画像処理を実践したい人

エッジ抽出の原理

ソーベルフィルタについて理解するためには、エッジ抽出の原理を学ぶ必要があります。ここでは、微分によってエッジ検出する原理を解説します。

微分で勾配を数値化

画像処理でのエッジとは、画像の明るい部分と暗い部分が急激に変化する勾配部です。この勾配部を抽出するには、画像の輝度値を微分し数値化します。関数\(f(x)\)の微分\(f'(x)\)は以下式で表されますよね。

$$f'(x)=\lim_{h \to 0}\frac{f(x+h)-f(x)}{h}$$

下図のように、輝度値を微分した値が大きい部分がエッジに相当することが分かります。

デジタル画像は2変数かつ1画素ごとの値のため、どうやったら勾配を求められる?

偏微分と微分の離散化によってデジタル画像の勾配を求めることができます。

偏微分

2変数関数の場合は、微分する変数を1つ指定し、それ以外の変数を定数として微分します。このような微分を偏微分といいます。2変数関数を\(f(x,y)\)とし、xで微分した導関数を\(f_x(x,y)\)、yで微分した導関数を\(f_y(x,y)\)とすると

$$f_x(x,y) = \lim_{h \to 0}\frac{f(x+h, y)-f(x, y)}{h}$$

$$f_y(x,y) = \lim_{h \to 0}\frac{f(x, y+h)-f(x, y)}{h}$$

\(f_x(x,y)\)はx方向、\(f_y(x,y)\)はy方向の勾配を表します。

微分の離散化

デジタル画像は、1画素ごとの離散値です。そのため、\(h\)は有限です。そこで、\(h=1\)として微分値を求めます。

$$f_x(x,y) = f(x+1, y)-f(x, y)$$

$$f_y(x,y) = f(x, y+1)-f(x, y)$$

これは座標\((x,y)\)とその前方座標\((x+1,y)、(x,y+1)\)の差分をとっていますので前方差分と呼ばれます。

一方、\(h=-1\)の場合は

$$f_x(x,y) = f(x, y)-f(x-1, y)$$

$$f_y(x,y) = f(x, y)-f(x, y-1)$$

となり、座標\((x,y)\)とその前方座標\((x-1,y)\)、\((x,y-1)\)の差分をとることから後方差分と呼ばれます。

また、前方差分と後方差分の平均を求めると

$$f_x(x,y) = \frac{f(x+1, y)-f(x-1, y)}{2}$$

$$f_y(x,y) = \frac{f(x, y+1)-f(x, y-1)}{2}$$

となります。座標\((x+1,y)\)と座標\((x-1,y)\)の差分、座標\((x,y+1)\)と座標\((x,y-1)\)の差分をとることから中心差分と呼ばれます。

微分フィルタ

空間フィルタリングにおいて線形フィルタは、入力画像を\(f(i,j)\)、出力画像を\(g(i,j)\)、フィルタ係数を\(h(m,n)\)とするとき、以下式で表されました。

$$g(i,j)=\sum_{n=-W}^{W}\sum_{m=-W}^{W}f(i+m,j+n)h(m,n)$$

https://craft-gogo.com/gauss-fillter/

微分をこの形式で表すと微分フィルタは以下表となります。

x方向y方向
前方差分\begin{pmatrix}
0 & 0 & 0 \\
0 & -1 & 1 \\
0 & 0 & 0
\end{pmatrix}
\begin{pmatrix}
0 & 0 & 0 \\
0 & -1 & 0 \\
0 & 1 & 0
\end{pmatrix}
後方差分\begin{pmatrix}
0 & 0 & 0 \\
-1 & 1 & 0 \\
0 & 0 & 0
\end{pmatrix}
\begin{pmatrix}
0 & -1 & 0 \\
0 & 1 & 0 \\
0 & 0 & 0
\end{pmatrix}
中心差分\begin{pmatrix}
0 & 0 & 0 \\
-1/2 & 0 & 1/2 \\
0 & 0 & 0
\end{pmatrix}
\begin{pmatrix}
0 & -1/2 & 0 \\
0 & 0 & 0 \\
0 & 1/2 & 0
\end{pmatrix}

ソーベルフィルタとは

画像にノイズが混入していると、ノイズをエッジと誤検出してしまいます。

そこで登場するのが、ノイズを除去しつつエッジ抽出するソーベルフィルタです。

そこで登場するのがソーベルフィルタです。ソーベルフィルタとは、前節で説明した中央差分したのち、中央に重み付けした平滑化を行ったフィルタです。

フィルタ係数

下記表は、x方向、y方向の中央差分、重み付き平滑化、ソーベルフィルタのフィルタ係数です。

中央差分: \(h_1(x,y)\)重み付き平滑化: \(h_2(x,y)\) ソーベルフィルタ: \(h_2(x,y)\cdot h_1(x,y)\)
x方向\(\begin{pmatrix}
0 & 0 & 0 \\
-\frac{1}{2} & 0 & \frac{1}{2} \\
0 & 0 & 0
\end{pmatrix}\)
\(\begin{pmatrix}
0 & \frac{1}{4} & 0 \\
0 & \frac{2}{4} & 0 \\
0 & \frac{1}{4} & 0
\end{pmatrix}\)
\(\frac{1}{8}\begin{pmatrix}
-1 & 0 & 1 \\
-2 & 0 & 2 \\
-1 & 0 & 1
\end{pmatrix}\)
y方向\(\begin{pmatrix}
0 & -\frac{1}{2} & 0 \\
0 & 0 & 0 \\
0 & \frac{1}{2} & 0
\end{pmatrix}\)
\(\begin{pmatrix}
0 & 0 & 0 \\
\frac{1}{4} & \frac{2}{4} & \frac{1}{4} \\
0 & 0 & 0
\end{pmatrix}\)
\(\frac{1}{8}\begin{pmatrix}
-1 & -2 & 1 \\
0 & 0 & 0 \\
-1 & 2 & 1
\end{pmatrix}\)

中央差分を\(h_1(x,y)\)、重み付き平滑化を\(h_2(x,y)\)とするとき、中央差分と重み付き平滑化を順に施したソーベルフィルタは、\(h_2(x,y)\cdot h_1(x,y)\)で計算できます。

ブリューウィットフィルタとの違い

ソーベルフィルタと同様なフィルタにブリューウィットフィルタ(Prewitt filter)というものがありますので紹介しておきます。

中央差分: \(h_1(x,y)\)平滑化: \(h_2(x,y)\) ブリューウィットフィルタ: \(h_2(x,y)\cdot h_1(x,y)\)
x方向\(\begin{pmatrix}
0 & 0 & 0 \\
-\frac{1}{2} & 0 & \frac{1}{2} \\
0 & 0 & 0
\end{pmatrix}\)
\(\begin{pmatrix}
0 & \frac{1}{3} & 0 \\
0 & \frac{1}{3} & 0 \\
0 & \frac{1}{3} & 0
\end{pmatrix}\)
\(\frac{1}{6}\begin{pmatrix}
-1 & 0 & 1 \\
-1 & 0 & 1 \\
-1 & 0 & 1
\end{pmatrix}\)
y方向\(\begin{pmatrix}
0 & -\frac{1}{2} & 0 \\
0 & 0 & 0 \\
0 & \frac{1}{2} & 0
\end{pmatrix}\)
\(\begin{pmatrix}
0 & 0 & 0 \\
\frac{1}{3} & \frac{1}{3} & \frac{1}{3} \\
0 & 0 & 0
\end{pmatrix}\)
\(\frac{1}{6}\begin{pmatrix}
-1 & -1 & 1 \\
0 & 0 & 0 \\
-1 & 1 & 1
\end{pmatrix}\)

こちらは中央差分と重みなしの平滑化を順に施したフィルタです。

OpenCVを用いたエッジ抽出

では、PythonとOpenCVを用いて画像のエッジを抽出してみましょう。

サンプルプログラム

下記は、画像を読み込み、ソーベルフィルタを適用したサンプルプログラムです。

import cv2 as cv

def concat_tile(im_list_2d):
    return cv.vconcat([cv.hconcat(im_list_h) for im_list_h in im_list_2d])

img = cv.imread("sample.jpg") # 写真の読み込み

img_sobelx = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=3) # 3x3の横方向ソーベルフィルタ
img_sobelx = cv.convertScaleAbs(img_sobelx)

img_sobely = cv.Sobel(img, cv.CV_64F, 0, 1, ksize=3) # 3x3の縦方向ソーベルフィルタ
img_sobely = cv.convertScaleAbs(img_sobely)

img_gradient = cv.addWeighted(img_sobelx, 0.5, img_sobely, 0.5, 0) # 勾配の大きさ 

imgs = concat_tile([[img, img_sobelx], [img_sobely, img_gradient]])

cv.imshow("img", imgs)
cv.imwrite("output.jpg", imgs)

if cv.waitKey(0) & 0xFF == ord('q'):
    cv.destroyAllWindows()

実行結果

左上が元画像、右上が横方向エッジ抽出、左下が縦方向のエッジ抽出、右下が勾配の大きさになります。

花の輪郭が抽出されているのが分かりますよね。

プログラム解説

サンプルプログラムを解説していきましょう。

ソーベルフィルタ適用は、cv.Sobel()関数を呼び出します。

cv.Sobel(img, cv.CV_64F, 1, 0, ksize=3)

第1引数に読み込む画像、第2引数は、出力画像のビット深度を指定します。第3,4引数はx,yに関する微分の次数です。(1, 0)でx方向、(0, 1)でy方向のエッジが抽出できます。ksizeはフィルタ係数の大きさです。今回は3×3のフィルタサイズを指定しました。

ソーベルフィルタは微分値をとるため、負の値にもなります。しかし、負の値が含まれていると画像表示することが出来ません。そこで、cv.convertScaleAbs()関数を呼び出して絶対値にします。

cv.convertScaleAbs(img)

縦方向と横方向のエッジを合成するには、cv.addWeighted()関数を呼び出します。

cv.addWeighted(src1, alpha1, src2, beta, gamma)

出力結果は、\(\alpha\cdot src1 + \beta \cdot src2 + \gamma\)となります。

まとめ

今回は、ノイズを除去しつつエッジ抽出するソーベルフィルタについて解説しました。

  • 微分によってエッジ検出する
  • ソーベルフィルタとは、中央差分したのち、中央に重み付けした平滑化を行ったフィルタ
  • OpenCVのcv.Sobel()関数によって、ソーベルフィルタを適用できる

これで、物体のエッジを抽出するプログラムが簡単に作れますね。

この記事が画像処理を学ぶ皆さんのためになれば幸いです。

独学が大変な方は、書籍やスクールを活用するのも手です。私も活用しているものを載せておきますので参考にして下さい。

最後までお読み頂きありがとうございました!

プログラミング言語の人気オンラインコース ]]>
【画像処理基礎】ノイズを除去するメディアンフィルタ https://craft-gogo.com/median-fillter/ Thu, 04 May 2023 21:00:00 +0000 https://craft-gogo.com/?p=3142

画像のごま塩ノイズを除去したい。そんなときはメディアンフィルタの登場です。本記事では、ノイズを除去するメディアンフィルタについて解説します。 メディアンフィルタとは メディアンフィルタとは空間フィルタリングの一種です。 […]]]>

画像のごま塩ノイズを除去したい。そんなときはメディアンフィルタの登場です。本記事では、ノイズを除去するメディアンフィルタについて解説します。

この記事はこんな人におすすめ!

  • 画像処理を学んでいる初心者
  • ノイズ除去の技術を理解したい人
  • プログラミングで画像処理を実践したい人

メディアンフィルタとは

メディアンフィルタとは空間フィルタリングの一種です。

メディアン(median)とは中央値のこと。メディアンフィルタは、周辺画素の画素値を降(昇)順に並べ、中央値を出力します。周辺画素に対して著しく外れた値(ノイズ)は、メディアンフィルタを通過すると除去されます。

下図は3×3メディアンフィルタでノイズが除去されるイメージ図です。

座標(i,j)の画素は上下左右斜めに対して著しく外れた「10」ですが、メディアンフィルタを通過すると、上下左右斜めの画素中央値である「2」に変換され、ノイズが除去されることが分かります。

OpenCVを用いたノイズ除去

PythonとOpenCVライブラリを用いれば、簡単にメディアンフィルタで画像のノイズを除去することが出来ます。

こちらがメディアンフィルタのPythonサンプルプログラムです。

import cv2 as cv

def concat_tile(im_list_2d):
    return cv.vconcat([cv.hconcat(im_list_h) for im_list_h in im_list_2d])

img = cv.imread("sample.jpg") # 写真の読み込み

img_median3 = cv.medianBlur(img, 3)  # 3x3のメディアンフィルタ
img_median5 = cv.medianBlur(img, 5)  # 5x5のメディアンフィルタ
img_median7 = cv.medianBlur(img, 7)  # 7x7のメディアンフィルタ

imgs = concat_tile([[img, img_median3], [img_median5, img_median7]])

cv.imshow("img", imgs)
cv.imwrite("output.jpg", imgs)

if cv.waitKey(0) & 0xFF == ord('q'):
    cv.destroyAllWindows()

左上が元画像。右上、左下、右下がそれぞれ3×3、5×5、7×7のメディアンフィルタを適用した画像となります。

3×3フィルタサイズで、ごま塩ノイズが十分除去されていることが分かりますね。フィルタサイズを大きくしすぎると、画像全体がぼやけてしまうので注意が必要です。

medianBlur関数を用いることで入力画像にメディアンフィルタを簡単に適用することができます。
cv.medianBlur(img, N)  # NxNのメディアンフィルタ

第1引数に入力画像、第2引数にフィルタサイズを設定します。

ガウシアンフィルタとの比較

ぼかしの基本であるガウシアンフィルタでも、ごま塩ノイズが除去できるか試してみましょう。

ガウシアンフィルタについては、下記リンク先参照して下さい。

https://craft-gogo.com/gauss-fillter/

左上が元画像。右上、左下、右下がそれぞれσ=1、σ=2、σ=3のガウシアンフィルタを適用した画像です。

σを大きくしないと、ごま塩ノイズは除去できないことが分かります。ごま塩ノイズ除去できたとしても、画像はかなりぼやけてしまいますね。

まとめ

本記事では、ノイズを除去するメディアンフィルタについて解説しました。

画像のごま塩ノイズを除去したければ、メディアンフィルタを適用すればよいことが分かりましたね。

この記事が画像処理を学ぶ皆さんのためになれば幸いです。

独学が大変な方は、書籍やスクールを活用するのも手です。私も活用しているものを載せておきますので参考にして下さい。

最後までお読み頂きありがとうございました!

プログラミング言語の人気オンラインコース ]]>
【画像処理基礎】ぼかしの基本!ガウシアンフィルタって何? https://craft-gogo.com/gauss-fillter/ Wed, 03 May 2023 22:55:50 +0000 https://craft-gogo.com/?p=3078

写真の主役を引き立たせるために背景をぼかしたり、プライバシー保護のため写真の顔をぼかしたりしますよね? Photoshopなどの画像処理ソフトで画像をぼかそうとすると、種類がたくさんあって何を使ったらいいものか̷ […]]]>

写真の主役を引き立たせるために背景をぼかしたり、プライバシー保護のため写真の顔をぼかしたりしますよね?

Photoshopなどの画像処理ソフトで画像をぼかそうとすると、種類がたくさんあって何を使ったらいいものか…。

本記事では、ぼかしの基本であるボックスフィルタガウシアンフィルタについて解説します。また、PythonとOpenCVで実際に写真をぼかす実践をします。この記事を読めば、画像をぼかしたかったら、悩まずガウシアンフィルタを選択すれば無難なことが分かります。

もう、ぼかしで悩むことから脱却しましょう!

この記事はこんな人におすすめ!

  • 画像処理を学んでいる初心者
  • ぼかしの技術を理解したい人
  • プログラミングで画像処理を実践したい人

空間フィルタリングとは

概念

ぼかしを学ぶ前に空間フィルタリングを理解する必要があります。フィルタを直訳すると、ろ過器やこし器などです。画像処理では、画像をこの空間フィルタで処理することで様々な画像に変換します。空間フィルタには、ぼかすためのフィルタ、エッジを抽出するためのフィルタ、ノイズを除去するためのフィルタなどがあります。

空間フィルタの概念は、コーヒーの抽出で例えることができます。コーヒーは、コーヒーの粉をコーヒーフィルタに入れ、お湯を注ぐと作ることができますよね。澄んだお湯がコーヒーフィルタを介すと黒く濁ったコーヒーになる。空間フィルタリングも同様なイメージです。入力画像は、お湯です。空間フィルタリングは、コーヒーフィルタです。コーヒーの粉は、ぼかしフィルタなどフィルタの種類です。出力画像は、コーヒーです。入力画像をぼかしフィルタで処理すると、出力画像は入力画像がぼけた結果になります。

空間フィルタは、線形フィルタと非線形フィルタに分けられます。ボックスフィルタ、ガウシアンフィルタは線形フィルタのため、ここでは線形フィルタについて解説していきます。

線形フィルタの数式

線形フィルタは、入力画像を\(f(i,j)\)、出力画像を\(g(i,j)\)とするとき、以下の式により計算されます。

$$g(i,j)=\sum_{n=-W}^{W}\sum_{m=-W}^{W}f(i+m,j+n)h(m,n)$$

\(h(m,n)\)はフィルタの係数を表す配列であり、フィルタの種類によってこの配列は変わってきます。ボックスフィルタ、ガウシアンフィルタの係数は次章で詳しく説明します。

フィルタの大きさは、\((2W+1)\times (2W+1)\)です。\(W=1\)の場合、\(3\times 3\)行列、\(W=2\)の場合、\(5\times 5\)行列となります。

数式見てもイメージしづらいですよね…。

計算例

そこで数式に具体的な数値を代入し、イメージしやすくしましょう。下図は3×3フィルタの計算例です。

入力画像の(i,j)座標の画素は「10」でしたが、フィルタを通すと(i,j)座標の画素は「7」となります。

3×3フィルタの場合は、入力画像のある画素に対して、上下左右斜めの8画素が影響し出力画素となっていますよね。これが空間フィルタリングと呼ばれる理由です。

空間フィルタリングの知識は身に付いたと思いますので、画像をぼかすためのボックスフィルタ、ガウシアンフィルタについて解説していきます。

ボックスフィルタとは

ボックスフィルタは、非常に簡単なぼかし(平滑化)フィルタです。フィルタの範囲を平均化するのみです。3×3フィルタの場合は、3×3=9画素をフィルタしますので、各フィルタ係数は9分の1となります。同様に5×5フィルタの場合は、各フィルタ係数は25分の1となります。

ガウシアンフィルタとは

ガウシアンフィルタは、フィルタ係数がガウス分布となっているものです。

ガウス分布?

ガウス分布とは、正規分布とも呼ばれ、確率論や統計学で用いられる連続的な変数に関する確率分布の一つです。平均値と最頻値と中央値が一致し、これを中心に左右対称な山なりの分布です。自然界の多くの事象がガウス分布に従うと言われています。

1次元のガウス分布は以下式で表されます。σは標準偏差です。

$$h_g (x) = \frac{1}{\sqrt{2\pi}\sigma}\exp(-\frac{x^2}{2\sigma^2})$$

グラフにすると下図です。

σが小さいと尖った山となり、周辺画素の影響は小さくなります。σが大きくなるにつれて、山は平坦となり、周辺画素の影響が大きくなります。ぼかしを強くしたいならσを大きくすればよいことが分かります。

画像は2次元ですので、2次元ガウス分布が必要です。2次元ガウス分布は以下式で表されます。

$$h_g(x) = \frac{1}{2\pi\sigma^2}\exp(-\frac{x^2+y^2}{2\sigma^2})$$

σ=2を代入してグラフにするとこんな感じ。

この式からNxNフィルタ係数行列を作れば、ガウシアンフィルタの出来上がりです。

OpenCVを用いたぼかし処理

PythonとOpenCVライブラリを用いれば、簡単にボックスフィルタとガウシアンフィルタで画像をぼかすことが出来ます。

ボックスフィルタのPythonプログラム

まず、ボックスフィルタのサンプルプログラムです。

import cv2 as cv

def concat_tile(im_list_2d):
    return cv.vconcat([cv.hconcat(im_list_h) for im_list_h in im_list_2d])

img = cv.imread("sample.jpg") # 写真の読み込み
img = cv.resize(img, None, fx=0.5, fy=0.5) # 写真サイズを2分の1

img_blur3 = cv.blur(img, (3, 3)) # 3×3のボックスフィルタ
img_blur5 = cv.blur(img, (5, 5)) # 5×5のボックスフィルタ
img_blur7 = cv.blur(img, (7, 7)) # 7x7のボックスフィルタ

imgs = concat_tile([[img, img_blur3], [img_blur5, img_blur7]])

cv.imshow("img", imgs)
cv.imwrite("output.jpg", imgs)

if cv.waitKey(0) & 0xFF == ord('q'):
    cv.destroyAllWindows()

左上が元画像。右上、左下、右下がそれぞれ3×3、5×5、7×7のボックスフィルタを適用した画像です。

フィルタサイズが大きくなるほど、ぼかしが強くなっていくことが分かります。

blur関数を用いることで入力画像にボックスフィルタを簡単に適用することができます。
cv.blur(img, (N, N)) # N×Nのボックスフィルタ

第1引数に入力画像、第2引数にフィルタサイズを設定します。

ガウシアンフィルタのPythonプログラム

次にガウシアンフィルタのサンプルプログラムです。

import cv2 as cv

def concat_tile(im_list_2d):
    return cv.vconcat([cv.hconcat(im_list_h) for im_list_h in im_list_2d])

img = cv.imread("sample.jpg") # 写真の読み込み
img = cv.resize(img, None, fx=0.5, fy=0.5) # 写真サイズを2分の1

img_gauss1 = cv.GaussianBlur(img, (0, 0), 1) # σ=1のガウシアンフィルタ
img_gauss2 = cv.GaussianBlur(img, (0, 0), 2) # σ=2のガウシアンフィルタ
img_gauss3 = cv.GaussianBlur(img, (0, 0), 3) # σ=3のガウシアンフィルタ

imgs = concat_tile([[img, img_gauss1], [img_gauss2, img_gauss3]])

cv.imshow("img", imgs)
cv.imwrite("output.jpg", imgs)

if cv.waitKey(0) & 0xFF == ord('q'):
    cv.destroyAllWindows()

左上が元画像。右上、左下、右下がそれぞれσ=1、σ=2、σ=3のガウシアンフィルタを適用した画像です。

σを大きくすると、ぼかしが強くなります。

GaussianBlur関数を用いることで入力画像にガウシアンフィルタを簡単に適用することができます。
cv.GaussianBlur(img, (0, 0), N) # σ=Nのガウシアンフィルタ

第1引数に入力画像、第2引数にフィルタサイズ、第3引数にσを設定します。

ここでフィルタサイズが0?と思われるでしょう。

openCVでは、フィルタサイズを0に設定するとσに応じてフィルタサイズが自動設定されます。

フィルタサイズ設定に困ったら、σだけ設定すればよいです。

ボックスフィルタとガウシアンフィルタを比べてみると、ガウシアンフィルタの方がぼかしが自然かと思われます。画像をぼかしたかったら、ガウシアンフィルタを選んでおけば間違いないです。

まとめ

本記事では、ぼかしの基本であるボックスフィルタ、ガウシアンフィルタについて解説しました。また、PythonとOpenCVで実際に写真をぼかす実践をしました。

画像をぼかしたかったら、悩まずガウシアンフィルタを選択すれば無難なことが分かりましたね。

これで、写真の主役を引き立たせるために背景をぼかしたり、プライバシー保護のため写真の顔をぼかしたりが出来ると思います。

この記事が画像処理を学ぶ皆さんのためになれば幸いです。最後まで読んで頂きありがとうございました!

独学が大変な方は、書籍やスクールを活用するのも手です。私も活用しているものを載せておきますので参考にして下さい。

プログラミング言語の人気オンラインコース ]]>
【Python×OpenCV】画像を平行移動・回転する方法 https://craft-gogo.com/python-opencv-rotation/ Mon, 22 Aug 2022 05:15:15 +0000 https://craft-gogo.com/?p=3025

今回は、OpenCVを使って画像を平行移動・回転する方法について解説していきます。 画像の平行移動・回転は、アフィン変換という知識が必要です。 まず、アフィン変換について解説します。そして、OpenCVを使った画像処理プ […]]]>

今回は、OpenCVを使って画像を平行移動・回転する方法について解説していきます。

画像の平行移動・回転は、アフィン変換という知識が必要です。

まず、アフィン変換について解説します。そして、OpenCVを使った画像処理プログラムを解説していきます。

アフィン変換とは

画像の回転、平行移動、拡大縮小、せん断などを行列を使って座標変換する事をアフィン変換と呼びます。

平面座標系のアフィン変換は3×3の行列を使って次式で表現できます。

$$ \left( \begin{array}{ccc} x’ \\ y’ \\ 1 \end{array} \right) = \left( \begin{array}{ccc} a & b & t_{x} \\ c & d & t_{y} \\ 0 & 0 & 1 \end{array} \right) \left( \begin{array}{ccc} x \\ y \\ 1 \end{array} \right) $$

3行目って左辺が「1」、右辺も「1」でムダじゃない?

3行目は、実際の座標に加えて、仮想的な座標を付け加えています。このような座標系を同次座標系と呼びます。同次座標系による表現は、平行移動を行う上で重要となります。

平行移動

まず、アフィン変換を使って平行移動する方法を説明します。

座標\((x,y)\)から\(x\)方向に\(t_{x}\)、\(y\)方向に\(t_{y}\)平行移動するとします。移動先の座標を\((x’,y’)\)としました。座標\((x’,y’)\)は、次の式で表せますね。

$$ \left( \begin{array}{ccc} x’ \\ y’ \end{array} \right) = \left( \begin{array}{ccc} x \\ y \end{array} \right) + \left( \begin{array}{ccc} t_{x} \\ t_{y} \end{array} \right) $$

これを同次座標系で表現すると次の式で整理できます。

$$ \left( \begin{array}{ccc} x’ \\ y’ \\ 1 \end{array} \right) = \left( \begin{array}{ccc} 1 & 0 & t_{x} \\ 0 & 1 & t_{y} \\ 0 & 0 & 1 \end{array} \right) \left( \begin{array}{ccc} x \\ y \\ 1 \end{array} \right) $$

このように平行移動は、アフィン変換の3×3行列 \(a,b,c,d\)に\(a=d=1\)、\(b=c=0\)を代入すればよいことが分かります。

回転

次にアフィン変換を使って回転する方法の説明です。

座標\((x,y)\)を原点中心に\(\theta\)角回転するとします。ここでも移動先の座標を\((x’,y’)\)としました。座標\((x’,y’)\)は次の式で表すことが出来ます。

$$ \left( \begin{array}{ccc} x’ \\ y’ \end{array} \right) = \left( \begin{array}{ccc} r \cos (\theta + \alpha) \\ r \sin (\theta + \alpha) \end{array} \right) $$

ここで三角関数の加法定理を使うと

$$ \left( \begin{array}{ccc} x’ \\ y’ \end{array} \right) = \left( \begin{array}{ccc} r \cos \theta \cos \alpha – r \sin \theta \sin \alpha \\ r \sin \theta \sin \alpha + r \cos \theta \cos \alpha \end{array} \right) $$
加法定理が分からない方は、高校数学を復習しましょう。

座標\((x,y)\)は

$$ \left( \begin{array}{ccc} x \\ y \end{array} \right) = \left( \begin{array}{ccc} r \cos \alpha \\ r \sin \alpha \end{array} \right) $$

ですので、座標\((x’,y’)\)は次式となります。

$$ \left( \begin{array}{ccc} x’ \\ y’ \end{array} \right) = \left( \begin{array}{ccc} x \cos \theta – y \sin \theta \\ x \sin \theta + y \cos \theta \end{array} \right) $$

式を整理すると

$$ \left( \begin{array}{ccc} x’ \\ y’ \end{array} \right) = \left( \begin{array}{ccc} \cos \theta & – \sin \theta \\ \sin \theta & \cos \theta \end{array} \right) \left( \begin{array}{ccc} x \\ y \end{array} \right) $$

最後に、同次座標系で表現すると次式になります。

$$ \left( \begin{array}{ccc} x’ \\ y’ \\ 1 \end{array} \right) = \left( \begin{array}{ccc} \cos \theta & -\sin \theta & 0 \\ \sin \theta & \cos \theta & 0 \\ 0 & 0 & 1 \end{array} \right) \left( \begin{array}{ccc} x \\ y \\ 1 \end{array} \right) $$

回転は、アフィン変換の3×3行列\(a,b,c,d\)に\(a=\cos\theta\)、\(b=-\sin\theta\)、\(c=\sin\theta\)、\(d=\cos\theta\)、\(t_{x}=0\)、\(t_{y}=0\)を代入すればよいことが分かります。

このように平行移動、回転はアフィン変換で実現できます。

OpenCVで画像の平行移動・回転

アフィン変換について理解できたと思いますので、本題のOpenCVで画像を平行移動・回転していきましょう!

今回は▼の画像を平行移動・回転します。画像は何でもよいですよ。

平行移動のプログラム

画像を平行移動するサンプルプログラムです。

import cv2 as cv
import numpy as np

#画像データの読み込み
img = cv.imread("sample.jpg")

height, width = img.shape[:2]
tx, ty = 100, 100

#アフィン変換: 平行移動
mv_mat = np.float32([[1, 0, tx],[0, 1, ty]])

afin_img = cv.warpAffine(img, mv_mat, (width, height))

cv.imshow("img", afin_img)

if cv.waitKey(0) & 0xFF == ord('q'):
    cv.destroyAllWindows()

水平方向に100、垂直方向に100移動させるため、\(t_{x}=100\)、\(t_{y}=100\)としました。アフィン変換の3×3行列 「mv_mat」を作成します。OpenCVでは、画像をアフィン変換するためのwarpAffine関数が用意されていますので、第1引数に移動対象の画像、第2引数にアフィン変換の行列、第3引数に出力画像サイズを指定しましょう。

出力結果が▼となります。

回転のプログラム

画像を回転するサンプルプログラムです。

import cv2 as cv
import numpy as np

#画像データの読み込み
img = cv.imread("sample.jpg")

height, width = img.shape[:2]

#アフィン変換: 回転
rot_mat = cv.getRotationMatrix2D((width/2, height/2), 45, 1)

afin_img = cv.warpAffine(img, rot_mat, (width, height))

cv.imshow("img", afin_img)

if cv.waitKey(0) & 0xFF == ord('q'):
    cv.destroyAllWindows()

前章で解説した回転のアフィン変換は、原点を中心に行われます。任意の点を中心に回転したい場合は、平行移動→回転→平行移動を繰り返す必要があります。OpenCVは、この処理を一括で行ってくれる関数があります。それがgetRotationMatrix2D関数です。第1引数に回転中心の座標、第2引数に角度、第3引数に拡大率を指定します。getRotationMatrix2D関数を実行するとアフィン変換の行列が戻り値として返ってきます。

出力結果が▼となります。

画像の中央点を中心に画像が回転されました。

まとめ

今回は、OpenCVを使って画像を平行移動・回転する方法について解説しました。

  • 画像を平行移動・回転するためのアフィン変換について解説
  • OpenCVで画像を平行移動・回転するプログラムを解説

画像処理ソフトがどうやって画像を平行移動・回転しているか理解出来たのではないかと思います。

今回の記事が皆さんのPython学習に役立つなら幸いです。

Python独学が大変な方は、書籍やスクールを活用するのも手です。

プログラミング言語の人気オンラインコース ]]>
Python + OpenCVを使って文字描画 https://craft-gogo.com/python-opencv-drawtext/ Sun, 13 Jun 2021 11:17:15 +0000 https://craft-gogo.com/?p=1185

今回はOpenCVを使って文字を描画する方法について解説します。 文字の大きさ、フォント、太さを様々に変えて表示の違いを見ることで、画像処理などで文字描画を使うときに本記事が役立てればよいと思っています。 文字描画の基本 […]]]>

今回はOpenCVを使って文字を描画する方法について解説します。

文字の大きさ、フォント、太さを様々に変えて表示の違いを見ることで、画像処理などで文字描画を使うときに本記事が役立てればよいと思っています。

文字描画の基本プログラム

まず、文字描画の土台となるプログラムを下記に示します。文字描画にはcv.putText()を使います。この引数を変えることで、文字の大きさ、フォント、色を変えることが出来ます。

import cv2 as cv
import numpy as np

# 黒い背景を作成
img = np.zeros((240,640,3), np.uint8)

# ここにプログラムを追加して、文字を描画します
font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img,'CRAFT',(20,150),font,4,(0,255,0),2,cv.LINE_AA)

cv.imshow("Draw Text", img)
k = cv.waitKey(0)

if k == ord("q"):
    cv.destroyAllWindows()

文字描画の方法

文字描画はcv.putText()を用いることで実現出来ます。

cv.putText(img, text, (X0,Y0), fontType, scale, (Blue, Green, Red), thickness, lineType)
  • img: 文字を描画する画像
  • text: 描画する文字
  • (X0,Y0): 文字を描画する座標 (文字の左下が基準となります)
  • fontType: フォント
  • scale: 文字の大きさ
  • (Blue, Green, Red): 文字の色
  • thickness: 文字の太さ
  • lineType: ラインタイプ

文字の大きさを変えてみる

文字の大きさを1~4まで変化させてみました。

cv.putText(img,'CRAFT',(10,30),font,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,80),font,2,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,150),font,3,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,240),font,4,(0,255,0),2,cv.LINE_AA)

表示はこんな感じになります。

フォントを変えてみる

OpenCVにはフォントが9種類用意されています。全部表示してみます。

font1 = cv.FONT_HERSHEY_SIMPLEX
font2 = cv.FONT_HERSHEY_PLAIN
font3 = cv.FONT_HERSHEY_DUPLEX
font4 = cv.FONT_HERSHEY_COMPLEX
font5 = cv.FONT_HERSHEY_TRIPLEX
font6 = cv.FONT_HERSHEY_COMPLEX_SMALL
font7 = cv.FONT_HERSHEY_SCRIPT_SIMPLEX
font8 = cv.FONT_HERSHEY_SCRIPT_COMPLEX
font9 = cv.FONT_ITALIC
cv.putText(img,'CRAFT',(10,30),font1,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,60),font2,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,90),font3,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,120),font4,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,150),font5,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,180),font6,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,210),font7,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,240),font8,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,270),font9,1,(0,255,0),2,cv.LINE_AA)

表示結果です。

個人的には上から5番目の「FONT_HERSHEY_TRIPLEX」が好みですね。

文字太さを変えてみる

今度は、文字太さを1~9まで変えてみます。

font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img,'CRAFT',(10,30),font,1,(0,255,0),1,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,60),font,1,(0,255,0),2,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,90),font,1,(0,255,0),3,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,120),font,1,(0,255,0),4,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,150),font,1,(0,255,0),5,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,180),font,1,(0,255,0),6,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,210),font,1,(0,255,0),7,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,240),font,1,(0,255,0),8,cv.LINE_AA)
cv.putText(img,'CRAFT',(10,270),font,1,(0,255,0),9,cv.LINE_AA)

表示はこんな感じです。

まとめ

OpenCVを使って文字を描画する方法について解説しました。

文字を描画する関数は、cv.putText()です。

今回、文字の大きさ、フォント、太さを変え、表示の違いを確認しました。

画像処理などで文字描画を実際に使う時のイメージがつかめたのではないでしょうか。

これからもOpenCVの使い方を解説していく予定です。

]]>
Python + OpenCVを使って図形描画 https://craft-gogo.com/python-opencv-draw-figure/ Sat, 12 Jun 2021 06:38:47 +0000 https://craft-gogo.com/?p=1153

OpenCVを使うと画像や動画から特定の物体を検出することなどが簡単に出来ます。こんなプログラムを作成したときに、検出した物体を枠で囲んでマークしたいですよね。 ▼こんなふうに 今回は、OpenCVを使って線、円、楕円、 […]]]>

OpenCVを使うと画像や動画から特定の物体を検出することなどが簡単に出来ます。こんなプログラムを作成したときに、検出した物体を枠で囲んでマークしたいですよね。

▼こんなふうに

今回は、OpenCVを使って線、円、楕円、矩形を描画する方法について解説します。

図形描画の基本プログラム

まず、図形を描画するのに土台となるプログラムを下記に記しました。cv.line()で赤線を描いています。この部分に図形描画のプログラムを記述することで様々な図形を描くことが出来ます。

次章では、線、円、矩形の描き方について解説します。

import cv2 as cv
import numpy as np

# 黒い背景を作成
img = np.zeros((480,640,3), np.uint8)

# ここにプログラムを追加して、色々な図形を描きます
cv.line(img,(0,0),(640,480),(0,0,255),10)

cv.imshow("Drawing Figure", img)
k = cv.waitKey(0)

if k == ord("q"):
    cv.destroyAllWindows()

線の描画方法

最初に線の描画方法を解説します。線を引くにはcv.line()を使用します。

cv.line(Img,(X0,Y0),(X1,Y1),(Blue,Green,Red),Thickness)
  • Img: 線を描きたい画像
  • (X0,Y0): 線の座標
  • (X1,Y1): 線の終点座標
  • (Blue, Green, Red): 線の色
  • Thickness: 線の太さ

座標の原点は、画像の左上です。水平右方向がX座標、鉛直下方向がY座標となります。

線の色と太さを変えてみる

cv.line(img,(20, 20),(600,400),(255,0,0),1)
cv.line(img,(20, 40),(600,420),(0,255,0),3)
cv.line(img,(20, 60),(600,440),(0,0,255),5)

線タイプをアンチエイリアスにすると線が滑らかに

第5引数で線のタイプを指定できます。線のタイプは省略可能です。

cv.line(img,(20, 20),(600,400),(255,0,0),3, cv.FILLED)
cv.line(img,(20, 40),(600,420),(255,0,0),3, cv.LINE_4) # 4-connected line
cv.line(img,(20, 60),(600,440),(255,0,0),3, cv.LINE_8) # 8-connected line (デフォルト)
cv.line(img,(20, 80),(600,460),(255,0,0),3, cv.LINE_AA) # antialiased line

うーん・・・。私にはアンチエイリアスの線以外、違いが分かりませんでした。アンチエイリアスにするかしないかの選択で良い気がします。

円の描画方法

続いて円の描画方法です。円を描くにはcv.circle()を使用します。

cv.circle(Img, (X0, Y0), Radius, (Blue, Green, Red), Thickness)
  • Img: 円を描きたい画像
  • (X0,Y0): 円の中心座標
  • Radius: 円の半径
  • (Blue, Green, Red): 円の色
  • Thickness: 線の太さ

円の大きさ、色を変えてみる

cv.circle(img, (100, 100), 50, (255,0,0), 1)
cv.circle(img, (150, 150), 80, (0,255,0), 3)
cv.circle(img, (200, 200), 100, (0,0,255), 5)
cv.circle(img, (250, 250), 130, (255,0,0), -1)

線の太さを-1に設定すると円が塗りつぶされます。

楕円の描画方法

楕円の描画方法はややこしいです。

cv.ellipse(Img, (X0,Y0),(Width,Height),Angle,StartAngle,EndAngle,(Blue,Green,Red),Thickness)
  • Img: 楕円を描きたい画像
  • (X0,Y0): 楕円の中心座標
  • (Width, Height): 楕円の幅、高さ
  • Angle: 楕円を傾ける角度
  • StartAngle: 楕円を描き始める角度
  • EndAngle: 楕円を描き終わる角度
  • (Blue, Green, Red): 楕円の色
  • Thickness: 線の太さ

楕円の大きさ、色、角度を変えてみる

cv.ellipse(img, (100,50),(20,50),0,0,360,(255,0,0),1)
cv.ellipse(img, (150,100),(100,50),0,45,270,(0,255,0),3)
cv.ellipse(img, (200,150),(100,50),45,0,360,(0,0,255),5)
cv.ellipse(img, (400,300),(200,50),0,45,180,(255,0,0),-1)

矩形の描画方法

cv.rectangle(img, (X0,Y0), (X1,Y1), (Blue, Green, Red), Thickness)
  • Img: 矩形を描きたい画像
  • (X0,Y0): 矩形の座標
  • (X1,Y1): (X0,Y0)の対角座標
  • (Blue, Green, Red): 矩形の色
  • Thickness: 線の太さ

矩形の大きさ、色を変えてみる

cv.rectangle(img, (50,50),(150,100), (255,0,0),1)
cv.rectangle(img, (100,150),(350,250), (0,255,0),3)
cv.rectangle(img, (150,200),(400,300), (0,0,255),5)
cv.rectangle(img, (350,350),(600,450), (255,0,0),-1)

まとめ

OpenCVを使って線、円、楕円、矩形を描画する方法について解説しました。

図形描画の関数をまとめると

  • 線描画: cv.line()
  • 円描画: cv.circle()
  • 楕円描画: cv.ellipse()
  • 矩形描画: cv.rectangle()

です。

]]>
【Python+OpenCV】簡単!たった十数行のプログラムで顔検出 https://craft-gogo.com/python-opencv-face-detection/ Fri, 30 Apr 2021 06:57:39 +0000 https://cra-log.sakura.ne.jp/?p=391

デジカメのように顔検出するプログラムを作ってみたいと思いませんか? OpenCVを使えば、たった十数行で顔検出できるプログラムが出来上がります。 OpenCVには大量の画像から人の顔の特徴を学習し、人の顔を判別する「カス […]]]>

デジカメのように顔検出するプログラムを作ってみたいと思いませんか?

OpenCVを使えば、たった十数行で顔検出できるプログラムが出来上がります。

OpenCVには大量の画像から人の顔の特徴を学習し、人の顔を判別する「カスケード分類器」が用意されています。これを使用した顔検出プログラムを解説していきます。

顔検出の流れは以下のとおりとなります。

STEP
カスケード分類器を読み込む
STEP
画像を読み込む
STEP
顔検出
STEP
顔部を枠で囲む

サンプルプログラム

import cv2 as cv

#カスケード分類器読み込み
HAAR_FILE = "haarcascade_frontalface_default.xml"
cascade = cv.CascadeClassifier(HAAR_FILE)

#画像の読み込み
img = cv.imread("sample.jpg")

#顔検出
face = cascade.detectMultiScale(img)

#顔部を枠で囲む
for x, y, w, h in face:
    cv.rectangle(img,(x,y),(x+w,y+h),(0,0,255),1)

cv.imshow("img", img)
cv.waitKey(0)
cv.destroyAllWindows()

プログラム解説

カスケード分類器を読み込む

HAAR_FILE = "haarcascade_frontalface_default.xml"
cascade = cv.CascadeClassifier(HAAR_FILE)

cv.CascadeClassifier()メソッドを呼び出し、人の顔を検出するための「カスケード分類器」を読み込みます。

カスケード分類器?

画像からある物体を検出するためには、大量の画像からある物体の特徴を学習し、この特徴をまとめたデータが必要となります。この特徴をまとめたデータのことを「カスケード分類器」と呼びます。

OpenCVは、人の顔を集めた大量画像から人の顔の特徴量をまとめたHaar-Cascadeが用意されています。これはGitHubからダウンロードすることが出来ます。

今回は、正面の顔を検出する「haarcascade_frontalface_default.xml」を使用しました。他にも色々な分類器があります。

画像を読み込む

img = cv.imread("sample.jpg")

cv.imread()メソッドを呼び出し、人の顔を検出したい画像を読み込みます。

顔検出

face = cascade.detectMultiScale(img)

cascade.detectMultiScale()メソッドを呼び出し、画像内の顔を検出します。戻り値として、検出した顔の位置が配列に格納され返ってきます。戻り値は順に、顔の位置のx座標、y座標、横幅、縦幅になります。

顔部を枠で囲む

for x, y, w, h in face:
    cv.rectangle(img,(x,y),(x+w,y+h),(0,0,255),1)

faceに顔の位置座標が格納されています。顔が正常に検出されたかどうか確認するため、元画像に赤枠を加えます。

色々な画像で顔検出を試してみる

違う画像でも試してみました。この画像は5人中、4人検出出来ました。検出出来ない人もいます。

横顔は検出するのが難しそうです。誤検出も発生しています。

PCカメラでリアルタイムに顔検出

ここまでの解説は静止画像でした。プログラムを少し改変することで、PCカメラのリアルタイム画像から顔検出することが出来ます。

自分の顔が検出されるか試してみて下さい。

import cv2 as cv

HAAR_FILE = "haarcascade_frontalface_default.xml"
cascade = cv.CascadeClassifier(HAAR_FILE)

cap = cv.VideoCapture(0)

while(True):
    ret, frame = cap.read()

    face = cascade.detectMultiScale(frame)

    for x, y, w, h in face:
        cv.rectangle(frame,(x,y),(x+w,y+h),(0,0,255),1)

    cv.imshow('frame',frame)
    if cv.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv.destroyAllWindows()

まとめ

OpenCVの「カスケード分類器」を使用した顔検出プログラムについて解説しました。

手順をおさらいすると以下になります。

  1. カスケード分類器を読み込む
  2. 画像を読み込む
  3. 顔検出
  4. 顔部を枠で囲む

今回の記事が皆さんのPython学習に役立つなら幸いです。

Python独学が大変な方は、書籍やスクールを活用するのも手です。

Python 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイルを学ぶオンライン講座 ]]>
【Python+OpenCV】特定の色を検出するプログラム https://craft-gogo.com/python-opencv-color-detection/ Wed, 21 Apr 2021 13:46:43 +0000 https://cra-log.sakura.ne.jp/?p=270

今回の記事では、OpenCVライブラリを用いて、1枚の画像から特定の色を検出する方法について解説します。 それでは始めましょう! サンプルプログラム サンプルプログラムは以下のようになります。 青色のみを検出するよう色し […]]]>

今回の記事では、OpenCVライブラリを用いて、1枚の画像から特定の色を検出する方法について解説します。

この記事はこんな人におすすめ!

  • OpenCVで画像処理を学んでいる人
  • Python初心者

それでは始めましょう!

サンプルプログラム

サンプルプログラムは以下のようになります。

import cv2 as cv
import numpy as np

#画像データの読み込み
img = cv.imread("sample.jpg")

#BGR色空間からHSV色空間への変換
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)

#色検出しきい値の設定
lower = np.array([90,64,0])
upper = np.array([150,255,255])

#色検出しきい値範囲内の色を抽出するマスクを作成
frame_mask = cv.inRange(hsv, lower, upper)

#論理演算で色検出
dst = cv.bitwise_and(img, img, mask=frame_mask)

cv.imshow("img", dst)

if cv.waitKey(0) & 0xFF == ord('q'):
    cv.destroyAllWindows()

青色のみを検出するよう色しきい値を設定し、1枚の画像の青色部のみを表示しました。色しきい値を変更することによって、赤色や緑色など様々な色を検出することが出来ます。

実行結果

元画像です。

プログラム実行結果です。青色の花と空が検出され、他は黒くなって表示されました。

【Pythonで学ぶ】OpenCVでの画像処理入門

HSV色空間を理解する

サンプルプログラムの解説をしていきます。openCVのcv.imread()は、BGR色空間で読み込まれます。色検出する場合、HSV空間の方が色検出しきい値を指定しやすいので、BGR色空間からHSV色空間に変換します。

#画像データの読み込み
img = cv.imread("sample.jpg")

#BGR色空間からHSV色空間への変換
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)

HSV色空間とは、「色相(Hue)」「彩度(Saturation)」「明度(Value・Brightness)」の3要素から成る色空間です。各要素の説明は以下表のとおりとなります。HSV色空間の値とOpenCVの値は異なるので、注意が必要です。

説明値範囲OpenCVの値範囲
色相(H)色合い。赤が0度で360度まで虹色の順で変化する。0-360度0-180
彩度(S)色の鮮やかさ。100%が純色で低くなるにつれて白くなる。0-100%0-255
明度(V)色の明るさ。100%が純色で低くなるにつれて暗くなる。0-100%0-255

検出する色範囲を設定し、マスクをかける

青系の色を検出をするために、色相が90-150(180-300度)、彩度が64-255(25-100%)、明度が0-255(0-100%)の範囲を抽出するマスクを作成します。作成するにはcv.inRange()を使用します。cv.inRange()は、指定したい範囲の色を255、それ以外の範囲の色を0として2値化します。第1引数に2値化したい画像を指定します。第2引数、第3引数で色範囲を指定します。

#色検出しきい値の設定
lower = np.array([90,64,0])
upper = np.array([150,255,255])

#色検出しきい値範囲内の色を抽出するマスクを作成
frame_mask = cv.inRange(hsv, lower, upper)

下図は2値化した画像です。青色部が白、それ以外は黒になりました。

#論理演算で色検出
dst = cv.bitwise_and(img, img, mask=frame_mask)

cv.bitwise_and()でマスクをかけ、cv.imshow()で表示させると実行結果の通り青色のみ検出されます。

色検出しきい値を変えてみる

色検出しきい値を変えて、違う色も検出してみましょう。まずは緑です。色相範囲は30-90(60-180度)としました。

#色検出しきい値の設定
lower = np.array([30,64,0])
upper = np.array([90,255,255])

#色検出しきい値範囲内の色を抽出するマスクを作成
frame_mask = cv.inRange(hsv, lower, upper)

花の葉と木の葉が検出されました。

次は赤です。赤は0-30(0-60度)、150-180(300-360度)です。色範囲が2条件となりますので、プログラムに工夫が必要です。

#色検出しきい値の設定
lower = np.array([0,64,0])
upper = np.array([30,255,255])

#色検出しきい値範囲内の色を抽出するマスクを作成
frame_mask1 = cv.inRange(hsv, lower, upper)

#色検出しきい値の設定
lower = np.array([150,64,0])
upper = np.array([180,255,255])

#色検出しきい値範囲内の色を抽出するマスクを作成
frame_mask2 = cv.inRange(hsv, lower, upper)

frame_mask = frame_mask1 + frame_mask2

2つのマスクを足し合わせています。赤、オレンジ、黄色の花が検出されました。

まとめ

以上、OpenCVを使って特定の色を検出するプログラムの解説でした。

おさらいしますと

  • HSV色空間について理解
  • 検出する色範囲を設定し、マスクをかける方法を学習
  • 色検出しきい値を変えて結果の違いを確認

Python、OpenCV学習のお役に立てたなら幸いです。

独りでPython学習するのは大変だなと思う方は、書籍やスクールを活用するのも手です。

プログラミング言語の人気オンラインコース

最後までお読み頂きありがとうございました!

]]>
【Python+OpenCV】PCカメラを使って動画を表示しよう! https://craft-gogo.com/python-opencv-camera-display/ Sun, 18 Apr 2021 00:26:01 +0000 https://cra-log.sakura.ne.jp/?p=244

OpenCVライブラリを使用して、PCカメラから動画を表示してみました。その方法について解説します。 サンプルプログラム サンプルプログラムは以下のようになります。実行することでウィンドウが立ち上がり、PCカメラで映し出 […]]]>

OpenCVライブラリを使用して、PCカメラから動画を表示してみました。その方法について解説します。

サンプルプログラム

サンプルプログラムは以下のようになります。実行することでウィンドウが立ち上がり、PCカメラで映し出された動画で表示されます。「q」キーを押すことでウィンドウが閉じ、プログラムが終了します。

import cv2 as cv

WIDTH: int = 320
HEIGHT: int = 240

cap = cv.VideoCapture(0)
if not cap.isOpened():
    print("Cannot open camera")
    exit()

cap.set(cv.CAP_PROP_FRAME_WIDTH, WIDTH)
cap.set(cv.CAP_PROP_FRAME_HEIGHT, HEIGHT)

print("Width = ", cap.get(cv.CAP_PROP_FRAME_WIDTH))
print("Height = ", cap.get(cv.CAP_PROP_FRAME_HEIGHT))  

while True:
    # 1フレームずつ読み込む
    ret, frame = cap.read()

    # フレームが正しく読み込まれない場合
    if not ret:
        print("Can't receive frame")
        break
    
    # 読み込んだフレームを表示
    cv.imshow("frame", frame) 
    
    #「q」キーが押されたらウィンドウを閉じる
    if cv.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()
cv.destroyAllWindows()

次項では、プログラム詳細を解説していきます。

VideoCaptureオブジェクトの取得

まず、VideoCapture()でVideoCaptureオブジェクトを取得します。

cap = cv.VideoCapture(0)

引数は、PCに接続されているカメラ番号になります。PCにカメラが1台しか接続されていない場合は「0」を指定します。

if not cap.isOpened():
    exit()

もし、cap.isOpened()がFalseを返したならば、プログラムを終了するようにしています。

解像度の変更

openCVのデフォルトでは、解像度は640×480のようです。今回は320×240の解像度に変更しました。カメラの仕様により解像度は自由に変更することは出来ないので、自身がお持ちのカメラ仕様を調べて下さい。

解像度の幅と高さを指定して、指定した値が反映されているか確認しています。

WIDTH: int = 320
HEIGHT: int = 240

cap.set(cv.CAP_PROP_FRAME_WIDTH, WIDTH)
cap.set(cv.CAP_PROP_FRAME_HEIGHT, HEIGHT)

print("Width = ", cap.get(cv.CAP_PROP_FRAME_WIDTH))
print("Height = ", cap.get(cv.CAP_PROP_FRAME_HEIGHT))  

cap.set()は動画のプロパティを設定するメソッドです。第1引数にプロパティ、第2引数に値を指定します。

cap.get()はプロパティを取得するメソッドです。第1引数にプロパティを指定します。

プロパティ説明
CAP_PROP_FRAME_WIDTHフレーム幅
CAP_PROP_FRAME_HEIGHTフレーム高さ

cap.read()で1フレームずつ読み込んだ画像をwhile文で繰り返し、動画にする

カメラから連続した画像を表示するため、cap.read()で読み込んだ画像をcv.imshow()で表示し、while文で繰り返します。

while True:
    # 1フレームずつ読み込む
    ret, frame = cap.read()

    # フレームが正しく読み込まれない場合
    if not ret:
        print("Can't receive frame")
        break
    
    # 読み込んだフレームを表示
    cv.imshow("frame", frame) 
    cv.waitKey(1)

cap.read()は2つの戻り値を返します。1つ目は、正しくフレームが読み込まれたかどうかTrue/FalseのBoolean値。2つ目は読み込んだ画像データです。

正しくフレームが読み込まれない場合は、while文を抜け、プログラムを終了させます。

フレームを読み込むだけでは、スクリーンに表示されないので、cv.imshow()で表示させます。cv.imshow()の第1引数は、表示させるフレームの名称を文字列で指定します。第2引数は、読み込む画像データを指定します。

cv.imshow()だけでは、処理が高速すぎてウィンドウは真っ黒のまま表示され、固まってしまいます。cv.waitkey(1)で1ms処理を待機させることで動画として表示出来るようにしています。

動画表示の終了

「q」キーが押されたらウィンドウを閉じるようにします。

while True:
   #「q」キーが押されたらウィンドウを閉じる
   if cv.waitKey(1) & 0xFF == ord("q"):
      break

cap.release()
cv.destroyAllWindows()

cv.waitKey()はキーが何も押されていないとき-1を返します。ここでは、「q」が押されたらwhile文を抜けるようにしています。

cap.release()でVideoCaptureオブジェクトを解放し、cv.destroyAllWindows()でウィンドウを閉じてプログラム終了です。

まとめ

今回は、PCカメラを使って動画を表示する方法を解説しました。

要点をまとめると

  • VideoCaptureオブジェクトを取得する
  • 解像度を変更する
  • cap.read()で1フレームずつ読み込んだ画像をwhile文で繰り返し、動画にする
  • 動画表示の終了
]]>