Python/OpenCVで画像中の特定色領域を抽出する
今回はPythonとOpenCVを使って、ちょっと遊んでみようと思います。
実際にやることは下図のように「特定の色がついた領域を抽出する」です。
(対象画像は本ブログのアイコンで、エルモとクッキーモンスターの領域をいい感じに2値化するイメージです。)
まずは以下のライブラリをインポートします。
import numpy as np import cv2 import matplotlib.pyplot as plt from copy import deepcopy
続いて、画像を読み込みます。
img = cv2.imread("image.jpg")
plt.imshow(img)
OpenCVはBGRで画像を読み込むので、そのままmatplolibでimshowするとBチャネルとRチャネルの値が入れ替わった画像が表示されてしまいます(これあるあるだと思うのですが私だけでしょうか)。ただ、コードを書く上では処理対象のチャネルを間違えさえしなければよいので、表示を気にしすぎる必要はないかなとも思います。
一応、下のコードのようにRGBに変換しておきましょう。
img_BGR = cv2.imread("image.jpg")
img = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2RGB)
plt.imshow(img)
画像のRGBチャネルをそれぞれ見てみましょう。
#R channelの可視化 plt.imshow(img[:,:,0], cmap="gray") plt.show() #G channelの可視化 plt.imshow(img[:,:,1], cmap="gray") plt.show() #B channelの可視化 plt.imshow(img[:,:,2], cmap="gray") plt.show()
当たり前ではありますが、Rチャネル(一番上)にエルモの領域が強く出ていて(白色に近い)、Bチャネル(一番下)にクッキーモンスターの領域が強く出ています(白色に近い)。そこで、RチャネルとBチャネルに注目し、閾値処理してみましょう(今回は閾値を0.7に設定しました)。
"""Rチャネルの値を閾値処理""" #0~1に正規化 R_channel = img[:,:,0] / np.amax(img[:,:,0]) #コピーしておく ROI_R = deepcopy(R_channel) #閾値処理 ROI_R[R_channel < 0.7] = 0 ROI_R[R_channel >= 0.7] = 1 #表示する plt.imshow(ROI_R,cmap="gray")
"""Bチャネルの値を閾値処理""" #0~1に正規化 B_channel = img[:,:,2] / np.amax(img[:,:,2]) #コピーしておく ROI_B = deepcopy(B_channel) #閾値処理 ROI_B[B_channel < 0.7] = 0 ROI_B[B_channel >= 0.7] = 1 #表示する plt.imshow(ROI_B,cmap="gray")
白色はRGBチャネルすべてに大きい値が出るので、目の部分も抽出されています。ちなみに0~1に正規化する意味は特にないです。では、それぞれの領域を統合しましょう。
#Rチャネルで抽出した領域とBチャネルで抽出した領域を結合する ROI_RB = np.maximum(ROI_R, ROI_B) #表示する plt.imshow(ROI_RB,cmap="gray")
これでいいのですが、ちょっと後ろの線が気になるので、モルフォロジー変換(オープニング)で消してしまいます。カーネルサイズは画像サイズに応じて変更する必要があります。
#オープニング処理する kernel = np.ones((15,15),np.uint8) opening = cv2.morphologyEx(ROI_RB, cv2.MORPH_OPEN, kernel) #表示する plt.imshow(opening,cmap="gray")
ちなみにOpenCVで保存するときは、0~255でuint8にしておかなければいけません。
opening = opening * 255. cv2.imwrite("result.jpg", opening.astype("uint8"))
これで完成です。
終わりに
この記事は元々違うやり方(もっと面白い処理方法)を紹介しようと思って書き始めたのですが、諸々の事情でやめることになり、こんな感じになりました。Lab空間やHSV空間に変換して抽出してみるのも面白いと思います。