ハフ変換の基礎と射影変換を用いた応用

はじめに

今回はハフ変換、中でもハフ線変換について紹介します。 ハフ変換(Hough Transform)とは、画像内に線や円などを検出する手法です。その中でも基本となるのはハフ線変換であり、2値画像から直線を探索する方法となります。

ハフ線変換のイメージ

ハフ線変換の基本的な考えは、2値画像内にある点はどの点でも何かしらの線の一部となっている可能性があるというものです。 その際、 y=ax+bという式で直線を表すことができますが、取り得る傾きの区間が -\inftyから +\inftyとなってしまうため最良とは言えないでしょう。そこで、ハフ線変換では \rho=x\cos \theta +y\sin \thetaという方程式で表現します。 最終的には、多数決ように \left( \rho ,\theta \right)のペアで投票が多いものが直線として検出されます。 OpenCVのハフ変換アルゴリズムは、こういった計算を我々ユーザには見せる訳ではなく、単に \left( \rho ,\theta \right)平面内の極大値を返すだけとなっているため、どんな流れで直線が検出されているのかを理解しておくのは大切です。

OpenCVのハフ線変換

早速ハフ線変換をPythonで実装する前にOpenCVでサポートされているハフ線変換シリーズを紹介します。 OpenCVには、

  1. 標準ハフ変換(SHT : Standard Hough Transform)
  2. マルチスケールハフ変換(MHT : Multiscale Hough Transform)
  3. 確率的ハフ変換(PPHT : Progressive Probabilistic Hough Transform)

があります。 それぞれ、SHTはこれまで説明してきたアルゴリズムであり、MHTはそれの改良版、PPHTはさらにその変形版です。PPHTは推測要素が入るため確率的とされており、計算時間を大幅に削減できる利点があります。 今回はPPHTを使って事前に用意した横断歩道の画像(図1)にある白線周りを検出することを目指します。

図1 オリジナル画像

ハフ線変換の実装

早速PythonでPPTHを実装していきます。コードは以下の通りで、グレースケールへの変換とぼかし処理を経てエッジ検出を用いた2値化を行いハフ線変換の準備をします。 その後、HoughLinesPというPPTHを実行できる関数を用いてハフ線変換を行っていきます。得られた極大値を使って直線を元画像に描画していき完了です。

# 画像の読み込み
img_src = cv2.imread('image_name.jpg')

# グレースケールに変換
img_src = cv2.cvtColor(img_src, cv2.COLOR_BGR2GRAY)
# ぼかし処理
img_src = cv2.blur(img_src, (5,5))
# エッジ検出を用いて二値化
img_src = cv2.Canny(img_src, 150, 200, apertureSize=3)

# 確率的ハフ変換で直線を検出
lines = cv2.HoughLinesP(img_src, 1, np.pi/180, 100, minLineLength=150, maxLineGap=30)

# 直線の描画
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img_src,  (x1, y1), (x2, y2), (0,0,255),2)

ハフ線変換後の図2にある通り、横断歩道の白線周りの線を検出できていることが分かります。しかし、他の線も検出してしまっているみたいです。 例えば、横断歩道の白線だけを検出したい場合はどうしたらよいのでしょうか?そんな時、射影変換という手法を用いて横断歩道のみに注目したハフ線変換を行うことが可能です。

図2 ハフ線変換後の画像

射影変換とハフ線変換の組み合わせ

先ほどは横断歩道以外の白線周りも一緒に検出されてしまいました。そこで、まずは射影変換を行い横断歩道のみに注目していきます。 射影変換の説明はまた今度詳しくしようと思いますが、①合同変換、②相似変換、③アフィン変換という画像形状の基本的な変換に対してどんな四角形にも変換できる方法です。 文章だけだとイメージしずらいかなと思うので、図3のように考えて貰えれば良いかと思います。

図3 射影変換のイメージ図

では、横断歩道の画像に射影変換を施し、その後改めてハフ線変換を行っていきます。

# 画像サイズの取得
size = tuple(np.array([img_src.shape[1], img_src.shape[0]]))

# 射影変換を行いたい座標の読み込み
pts1 = np.float32([[1241,357],[3,369],[1199,765],[1911,506]])
pts2 = np.float32([[0,0],[0,1080],[1920,1080],[1920,0]])

# 射影変換の実行
psp_mat = cv2.getPerspectiveTransform(pts1, pts2)
img_dst = cv2.warpPerspective(img_src, psp_mat, size, flags=cv2.INTER_LINEAR)

図4 射影変換後の横断歩道

# グレースケールに変換
img_gray = cv2.cvtColor(img_dst, cv2.COLOR_BGR2GRAY)
# ぼかし処理
img_gray = cv2.blur(img_gray, (5,5))
# エッジ検出を用いて二値化
img_edges = cv2.Canny(img_gray, 30, 40, apertureSize=3)

# 確率的ハフ変換で直線を検出
lines = cv2.HoughLinesP(img_edges, 1, np.pi/180, 100, minLineLength=100, maxLineGap=50)

# 直線の描画
for line in lines:
    x1, y1, x2, y2 = line[0]
    cv2.line(img_dst,  (x1, y1), (x2, y2), (0,0,255),2)

図5 ハフ線変換後の画像

おわり

前半にハフ線変換とは何かであったりの説明や確率的ハフ線変換の実装を行い、後半に射影変換を用いた注目したい部分に絞ったハフ線変換をおこないました。 少し画像解析らしいことができて楽しかったです。次回は輪郭抽出や物体検出に少し入っていこうかと思います。

微分オペレータを使ったエッジ検出の方法と実装

目次

はじめに

今回はエッジ検出フィルタを紹介します。 エッジ検出フィルタとは(edge detect filtering)とは、画像中の画素値が急激に変わる部分を取り出す処理です。もう少しわかりやすく説明すると、画像の輪郭を検出あるいは抽出する処理のようなことです。

ここではエッジ検出の流れと、エッジ検出の1つである微分オペレータの実装について紹介します。

エッジ検出のイメージ

まずは、画像の境界部分がどのように検出されるのか見ていきましょう。 画像の領域境界(輪郭部分)は画素値の変化が大きくなると先ほど述べましたが、その境界部分を検出するために画素値に対して微分演算を行うことにより画像の境界部分を検出します。 具体的な流れは以下の通りです。 ① 画素値に対して微分演算を行う。 ② 画素の勾配を求める。 ③ 大きな勾配を持つ部分を抽出する。(=境界部分の抽出)

例えば図1.1のような周辺が黒(画素値0)、中心に白(画素値255)が集まる正方形の画像があるとします。この画像を例にとって微分演算のイメージを掴んでいきましょう。

図1.1
まず、 y=3におけるx軸方向の画素値 I\left( x,3\right)の変化は図1.2(a)のようになります。 そして、  I'\left( x,3\right) =I\left( x+1,3\right) -I\left( x,3\right) として微分演算を行うと、図1.2(b)のように輪郭部分の画素値が大きくなることが分かると思います。
  
左:図1.2(a) 右:図1.2(b)

また、x軸方向の微分をオペレータで表現した際、図1.4のように3種類となります。それぞれの特徴として、(a)の微分オペレータは注目画素とその右隣との画素値の差を、(b)は注目画素とその左隣との差を、(c)は注目画素とその両隣との画素値の差の平均を、それぞれ注目画素の値として出力します。

図1.4 横方向微分オペレータ K=\left( m,n\right)の例

例えば図1.1に図1.4(a)の微分オペレータを適用させた際には図1.5で示す通り、横方向に注目画素との差を出力していることが分かります。

図1.5 微分オペレータの適用結果

次にこの微分オペレータを実装し、画像がどう変化するかを確認していきます。

微分オペレータの実装

早速Pythonで微分オペレータを実装し、図1.6(a)のエッジを検出していきましょう。結果は、図1.6(b)で示している通り、元画像の縦方向の輪郭をうまく検出することができました。

# 入力画像をグレースケールに変換
img_src = cv2.imread(file_src, cv2.COLOR_BGR2GRAY)

# カーネルの作成(x軸方向の輪郭検出)
# y軸方向の輪郭検出の場合、np.array([[-1], [0], [1]])
kernel = np.array([[-1, 0, 1]])


# 微分計算した画像の作成とデータ型の変換
img_tmp = cv2.filter2D(img_src, cv2.CV_32F, kernel)
img_dst = cv2.convertScaleAbs(img_tmp)

    
図1.6(a) エッジ検出前の画像      図1.6(b) 縦方向のエッジ検出の結果

まとめ

前半にエッジ検出(画像の境界部分の検出)の流れを図とグラフを用いて紹介し、後半に微分オペレータをPythonで実装して実際にある画像の縦方向のエッジを検出しました。 実装自体は簡単にできますが、その中身を理解するとより理解が深まると感じたので、他の画像処理を学ぶ際にも意識していきたいと思います。

画像処理ライブラリOpenCVを使った平滑化フィルタ処理の手法

目次

はじめに

タイトルにある平滑化フィルタ処理(soomthing filterling)とは、画像を平滑化するフィルタ処理のことです。画像をぼかしたり、画像のノイズを除去したりする際に使用されることが多く、以下では平滑化で用いられる①平均化オペレータ(移動平均オペレータ)②加重平均オペレータ③中央値フィルタ処理について実装コードと合わせて簡単に説明します。
画像処理ライブラリのOpenCVを使っていくので、初めて使う方はインストールしておきましょう。

pip install opencv-python

①平均化オペレータ

注目画素(通常はオペレータの中心)の周辺の画素値の平均を計算し、注目画素の画素値とすることで平滑化を行うオペレータのことです。
平均化オペレータ例を図1に示します。

  
図1 平均化オペレータの例(左:3×3ピクセル、右:5×5ピクセル)
それでは早速Pythonで平均化オペレータを実装し、3×3ピクセルで画像平滑化を行っていきます。

import cv2
import numpy as np

file_src = 'pic_name.jpg'
file_dst = 'pic_name_soomthing.jpg'

# 画像読み込み
img_src = cv2.imread(file_src, cv2.IMREAD_COLOR)

cv2.namedWindow('src')
cv2.namedWindow('dst')

# 平均化オペレータ
img_dst = cv2.blur(img_src, (3,3))

cv2.imshow('src', img_src)  # 入力画像を表示
cv2.imshow('dst', img_dst)  # 出力画像を表示
cv2.imwrite(file_dst, img_dst)  # 処理結果の保存

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

結果は図2に示した通り、元画像よりも若干ですがぼかすことに成功しました。ピクセル数を増やすほど、より画像をぼかすことができます。

 
図2 左が元画像 右が平滑化を施した画像

②加重平均オペレータ

平均化オペレータとは異なり、注目(画素に近いほど大きな重みを付けオペレータです。 平均をとるだけの単純な平均化オペレータに比べて、入力画像を残す度合いが大きな平滑化を実現することができ、一般的に加重平均オペレータの方がより自然な平滑化を行うことができます。 平均オペレータと同様、まずは加重平均オペレータのイメージを図3に示します。
 
図3 加重平均オペレータの例(左:3×3ピクセル、右:5×5ピクセル)
加重平均オペレータもPythonで実装してみましょう。 平均化オペレータとは違い、加重平均オペレータでは異なるピクセルに異なる重みを割り当てるため、カーネルを使って畳み込み演算を行っています。また、関数はfilter2Dを使っています。
# カーネルを作成
kernel = np.array([[1, 2, 1], [2, 4, 2], [1, 2, 1]]) / 16.0
# 畳み込み演算
img_dst = cv2.filter2D(img_src, -1, kernel)
結果は図4に示した通りです。
 
図4 加重平均オペレータにより平滑化の結果

③中央値フィルタ処理

最後に図5に見られる黒い点のようなノイズ除去に効果のある中央値フィルタ処理を行って締めたいと思います。 中央値フィルタ処理は、これまでの処理とは違い、注目画素の周辺領域内のすべての画素値をソートし、その中央値を注目画素の画素値とするいわゆる非線形フィルタ処理です。 実際にやっていきましょう。
# 中央値フィルタ処理
img_dst = cv2.medianBlur(img_src, 9)
 
図6 中央値フィルタ処理によるノイズ除去(左が元画像、右が処理後の画像)
少し画像が荒くなってしまいましたが、スパイクノイズをうまく除去できたことが確認できました。

まとめ

平滑化フィルタ処理について簡単に説明し、平均化オペレータ・加重平均オペレータ・中央値フィルタ処理という3つの処理についてPythonで実装する方法をご紹介いたしました。 特にノイズ処理は実務でも役に立ちそうなので、画像が荒くならないようより高度なGaussianフィルタの実装もやってみたいと思います。