PythonによるOpenCVの画像処理入門

このチュートリアルでは、Python 言語を使ってどのように画像処理を行うかを学びます。このチュートリアルでは、1つのライブラリやフレームワークに限定するつもりはありませんが、最も頻繁に使用するライブラリとして、Open CV ライブラリを紹介します。まず、画像処理について少し説明し、次に画像処理が便利な様々なアプリケーションやシナリオを見ていきます。では、始めましょう。

画像処理とは?

画像処理とは何なのか、そしてその役割は何なのかを知っておくことは、その方法を知る前に重要なことです。画像処理とは、一般的に「デジタル画像処理」と呼ばれ、「コンピュータビジョン」と呼ばれる分野でもよく使用されます。ここでは、この2つの用語の意味と、それらの関連性について説明します。画像処理アルゴリズムとコンピュータビジョン(CV)アルゴリズムはどちらも画像を入力としますが、画像処理では出力も画像であるのに対し、コンピュータビジョンでは出力は画像に関する何らかの特徴や情報である場合があります。

なぜそれが必要なのか?

私たちが収集したり生成したりするデータは、ほとんどが生データです。つまり、さまざまな理由が考えられるため、アプリケーションで直接使用するのには適していません。したがって、まずそれを分析し、必要な前処理を行い、それから利用する必要がある。

例えば、猫の分類器を作ろうとしたとします。このプログラムは、画像を入力として受け取り、その画像に猫が含まれているかどうかを教えてくれるものです。この分類器を作るには、まず何百枚もの猫の写真を集める必要があります。一般的な問題として、集めた画像はすべて同じサイズや大きさではないので、学習用のモデルに渡す前に、すべての画像を標準サイズにリサイズ/前処理する必要があります。

このように、画像処理はコンピュータビジョンのアプリケーションに不可欠なものなのです。

前提条件

先に進む前に、このチュートリアルを簡単に進めるために知っておくべきことを説明します。まず、何らかの言語で基本的なプログラミングの知識を持っている必要があります。次に、機械学習とは何か、そしてその仕組みの基本を知っている必要があります。この記事では、画像処理にいくつかの機械学習アルゴリズムを使用します。さらに、このチュートリアルを始める前に、Open CVに触れたことがあるか、あるいはその基本的な知識があれば、助かります。しかし、これは必須ではありません。

このチュートリアルに従うために絶対に知っておくべきことの1つは、画像がメモリ上で正確にどのように表現されるかということです。それぞれの画像は、ピクセルの集合、つまりピクセル値の行列で表現されます。グレースケール画像の場合、ピクセル値は0から255の範囲で、そのピクセルの強さを表します。例えば、20×20の画像があれば、20×20のマトリックス(合計400画素の値)で表現されることになります。

カラー画像であれば、赤、緑、青(RGB)の3つのチャンネルがあることを知る必要があります。したがって、1つの画像に対して、このような行列が3つ存在することになります。

インストール

注意:OpenCV を Python 経由で利用するため,ワークステーションに Python (version 3) が既にインストールされていることが暗黙の条件となります.

Windows

Fake code

MacOS

FALSE CODE

Linux

$ pip install opencv-python


インストールが成功したかどうかを確認するには、Python シェルまたはコマンドプロンプトで次のコマンドを実行します。

$ brew install opencv3 --with-contrib --with-python3


知っておきたい基礎知識

画像処理とはどのような処理なのか、またどのように処理するのか、アプリケーションで使用する前に知っておく必要があります。これらの操作は、後にアプリケーションで使用されることになります。では、さっそく始めましょう。

今回は以下の画像を使用します。

注:この画像はこの記事で表示するために縮小されていますが、元々使用しているサイズは約1180×786です。

この画像は現在カラーで、赤、緑、青の3つのカラーチャンネルで表現されていることにお気づきでしょう。以下のコードを使って、画像をグレースケールに変換し、各チャンネルに分割する予定です。

画像詳細の検索

imread()` 関数で画像を読み込んだら、その画像のピクセル数や寸法などの簡単なプロパティを取得することができます。

$ sudo apt-get install libopencv-dev python-opencv


出力されます。

import cv2


画像を各チャンネルに分割する

OpenCVを使って画像を赤、緑、青に分割し、表示します。

import cv2


img = cv2.imread('rose.jpg')


print("Image Properties")
print("- Number of Pixels: " + str(img.size))
print("- Shape/Dimensions: " + str(img.shape))


簡潔のために、グレースケール画像だけを表示します。

グレースケール画像

画像の閾値処理

閾値の概念は非常にシンプルです。画像表現で述べたように、画素の値は0から255の間の任意の値にすることができます。例えば、画像を2値画像に変換したい場合、つまり、画素に0か1のどちらかの値を割り当てたい場合を考えてみましょう。これを行うには、閾値処理を行います。例えば、しきい値(T)が125であれば、125より大きい値を持つすべてのピクセルに1、それ以下の値を持つすべてのピクセルに0を割り当てることになります。

閾値処理に使用する画像

Image Properties
- Number of Pixels: 2782440
- Shape/Dimensions: (1180, 786, 3)


出力

しいたげ

ご覧のように、結果画像には、黒い領域(画素値0)と白い領域(画素値1)の2つの領域が設定されていることがわかります。これは、設定した閾値が画像のちょうど真ん中あたりだったため、そこで黒と白の値が分かれていることがわかります。

アプリケーション

その1:画像からノイズを除去する

画像処理とは何か、何に使われるのか、基本的なことはご理解いただけたと思いますので、次にその具体的な応用例について説明します。

多くの場合、私たちが収集した生のデータにはノイズが含まれています。つまり、画像を知覚しづらくする不要な特徴があるのです。このような画像は、そのまま特徴抽出に使うことができますが、アルゴリズムの精度が大きく損なわれてしまいます。そのため、画像に画像処理を施してからアルゴリズムに渡すことで、精度を高めているのです。

ノイズには、ガウスノイズ、ソルト&ペッパーノイズなど、さまざまな種類があります。そのノイズを除去する、あるいは少なくともその影響を最小化するフィルターを適用することで、画像からそのノイズを除去することができるのです。フィルターにも多くの選択肢があり、それぞれ異なる強度を持つため、特定の種類のノイズに最適です。

このことを正しく理解するために、先ほどのバラの画像のグレースケール版に「塩コショウ」ノイズを加え、そのノイズをさまざまなフィルタを使って除去し、どのフィルタがその種類に最も適しているかを見てみましょう。

from google.colab.patches import cv2_imshow


blue, green, red = cv2.split(img) # Split the image into its channels
img_gs = cv2.imread('rose.jpg', cv2.IMREAD_GRAYSCALE) # Convert image to grayscale


cv2_imshow(red) # Display the red channel in the image
cv2_imshow(blue) # Display the red channel in the image
cv2_imshow(green) # Display the red channel in the image
cv2_imshow(img_gs) # Display the grayscale version of image


さて、バラの画像にノイズを加えてみると、このようになります。

ノイズのある画像

この画像に様々なフィルタを適用し、それぞれのフィルタがどの程度ノイズを軽減しているかを観察してみましょう。

シャープカーネル付き算術フィルタ
import cv2


# Read image
img = cv2.imread('image.png', 0)


# Perform binary thresholding on the image with T = 125
r, threshold = cv2.threshold(img, 125, 255, cv2.THRESH_BINARY)
cv2_imshow(threshold)


塩コショウノイズを含む画像に算術フィルタを適用した結果は以下のようになります。元のグレースケール画像と比較すると、画像を明るくしすぎて、バラの明るい部分を強調できていないことが分かります。したがって、演算フィルタはソルト&ペッパーノイズの除去に失敗していると結論付けることができます。

算術フィルタの出力。

中点フィルター
import numpy as np


# Adding salt & pepper noise to an image
def salt_pepper(prob):
      # Extract image dimensions
      row, col = img_gs.shape


# Declare salt & pepper noise ratio
      s_vs_p = 0.5
      output = np.copy(img_gs)


# Apply salt noise on each pixel individually
      num_salt = np.ceil(prob * img_gs.size * s_vs_p)
      coords = [np.random.randint(0, i - 1, int(num_salt))
            for i in img_gs.shape]
      output[coords] = 1


# Apply pepper noise on each pixel individually
      num_pepper = np.ceil(prob * img_gs.size * (1. - s_vs_p))
      coords = [np.random.randint(0, i - 1, int(num_pepper))
            for i in img_gs.shape]
      output[coords] = 0
      cv2_imshow(output)


return output


# Call salt & pepper function with probability = 0.5
# on the grayscale image of rose
sp_05 = salt_pepper(0.5)


# Store the resultant image as 'sp_05.jpg'
cv2.imwrite('sp_05.jpg', sp_05)


塩コショウノイズの入った画像に中間点フィルタを適用した結果は以下のようになります。元のグレースケール画像と比較すると、上記のカーネル法と同様に画像を明るくしすぎていますが、バラの明るい部分を強調できていることがわかります。したがって、算術フィルタよりは良い選択と言えますが、それでも元の画像を完全に復元することはできません。

中点フィルターの出力。

非調和平均フィルタ

注:これらのフィルタの実装はオンラインで簡単に見つけることができ、正確にどのように動作するかは、このチュートリアルの範囲外です。我々は、抽象的/より高いレベルからアプリケーションを見ます。

# Create our sharpening kernel, the sum of all values must equal to one for uniformity
kernel_sharpening = np.array([[-1,-1,-1],
                              [-1, 9,-1],
                              [-1,-1,-1]])


# Applying the sharpening kernel to the grayscale image & displaying it.
print("

--- Effects on S&P Noise Image with Probability 0.5 ---

")


# Applying filter on image with salt & pepper noise
sharpened_img = cv2.filter2D(sp_05, -1, kernel_sharpening)
cv2_imshow(sharpened_img)


塩と胡椒のノイズを含む画像にContraharmonic Mean Filterを適用した結果は以下の通りです。元のグレースケール画像と比較すると、元の画像とほぼ同じ画像が再現されていることが分かります。また、その強度や輝度レベルも同じで、バラの明るい部分を強調しています。したがって、逆調和平均フィルタはソルト&ペッパーノイズに非常に効果的であると結論づけることができます。

逆調和平均フィルタの出力。

さて、ノイズの多い画像から元の画像を復元するのに最適なフィルターが見つかったので、次の応用に進みます。

その2:Canny Edge Detectorを用いたエッジ検出

これまで使ってきたバラの画像は、背景が黒で一定なので、このアプリケーションでは、アルゴリズムの能力をよりよく示すために、別の画像を使うことにします。背景が一定であれば、エッジ検出のタスクはむしろ単純になり、我々はそれを望まないからである。

このチュートリアルの前半で、猫の分類器についてお話しましたが、その例を進めて、画像処理がどのように重要な役割を担っているのか見てみましょう。

分類アルゴリズムでは、まず画像をスキャンして「オブジェクト」を探します。つまり、画像を入力すると、アルゴリズムはその画像内のすべてのオブジェクトを見つけ、それらを見つけようとしているオブジェクトの特徴と比較します。猫分類器の場合、画像内のすべてのオブジェクトと猫画像の特徴を比較し、一致するものがあれば、入力画像に猫が含まれていると教えてくれます。

今回は猫分類器を例にしているので、猫の画像を使うのが妥当でしょう。以下に、使用する画像を示します。

エッジ検出のための画像

from scipy.ndimage import maximum_filter, minimum_filter


def midpoint(img):
    maxf = maximum_filter(img, (3, 3))
    minf = minimum_filter(img, (3, 3))
    midpoint = (maxf + minf) / 2
    cv2_imshow(midpoint)


print("

---Effects on S&P Noise Image with Probability 0.5---

")
midpoint(sp_05)


エッジ検出の出力。

このように、画像中の物体(この場合は猫)を含む部分がエッジ検出により点線化/分離されています。ここで、Cannyエッジ検出器とは何か、どのようにしてこのような処理を実現したのか、について説明します。

上記のことを理解するためには、3つの重要なステップを説明する必要があります。まず、前回説明したのと同じように、画像に対してノイズ除去を行います。次に、各画素の一次微分を用いてエッジを見つけます。これは、エッジが存在する箇所では強度が急激に変化するため、一次微分の値が急上昇し、その画素が「エッジピクセル」となる、という論理です。

最後に、ヒステリシス閾値処理を行います。上記でエッジでは一次導関数の値にスパイクがあると言いましたが、エッジとして分類されるために必要なスパイクの「高さ」については述べていません – これを閾値と呼びます! このチュートリアルの前半で、単純な閾値が何であるかを説明しました。ヒステリシス閾値はそれを改良したもので、1つの閾値の代わりに2つの閾値を使用します。その理由は、もし閾値が高すぎると、実際のエッジを見逃してしまい(true negative)、低すぎると、実際にはエッジでない多くのポイントがエッジとして分類されてしまう(false positive)ためです。1つの閾値は高く設定され、もう1つは低く設定される。高いしきい値」以上のすべての点がエッジとして識別され、次に低いしきい値以上高いしきい値未満のすべての点が評価され、エッジとして識別された点に近い、または隣接する点もエッジとして識別され、残りは破棄されます。

以上が、Canny Edge Detector アルゴリズムが画像内のエッジを識別するために使用する基本的な概念/方法です。

結論

今回は、Pythonの画像処理ライブラリとして最も有名なOpenCVをWindows、MacOS、Linuxなどの異なるプラットフォームにインストールする方法と、インストールが成功したことを確認する方法について学びました。

続いて、画像処理とは何か、そして機械学習のコンピュータビジョン領域におけるその用途について説明しました。一般的なノイズの種類と、アプリケーションで画像を使用する前に、さまざまなフィルタを使用して画像からノイズを除去する方法について説明しました。

さらに、オブジェクト検出や分類といったハイエンドなアプリケーションにおいて、画像処理がいかに重要な役割を担っているかを学びました。この記事は氷山の一角に過ぎず、デジタル画像処理には、1回のチュートリアルではカバーしきれないほど多くのことが含まれていることに留意してください。これを読めば、画像処理に関連する他の高度な概念について、より深く学ぶことができるはずです。幸運を祈ります。

タイトルとURLをコピーしました