knowwell-livewellの日記

knowwell-livewellの日記

好きなこととかもろもろ書きます

Python/OpenCVで画像中の特定色領域を抽出する

今回はPythonOpenCVを使って、ちょっと遊んでみようと思います。
実際にやることは下図のように「特定の色がついた領域を抽出する」です。
(対象画像は本ブログのアイコンで、エルモとクッキーモンスターの領域をいい感じに2値化するイメージです。)

f:id:knowwell-livewell:20220129153018p:plain
本ブログアイコン画像内の黒色以外の領域を抽出する


まずは以下のライブラリをインポートします。

import numpy as np
import cv2
import matplotlib.pyplot as plt
from copy import deepcopy

続いて、画像を読み込みます。

img = cv2.imread("image.jpg")
plt.imshow(img)

f:id:knowwell-livewell:20220129134957j:plain:w200:left

OpenCVはBGRで画像を読み込むので、そのままmatplolibでimshowするとBチャネルとRチャネルの値が入れ替わった画像が表示されてしまいます(これあるあるだと思うのですが私だけでしょうか)。ただ、コードを書く上では処理対象のチャネルを間違えさえしなければよいので、表示を気にしすぎる必要はないかなとも思います。
一応、下のコードのようにRGBに変換しておきましょう。

img_BGR = cv2.imread("image.jpg")
img = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2RGB)
plt.imshow(img)

f:id:knowwell-livewell:20220129144004j:plain:w200:left

画像の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()
f:id:knowwell-livewell:20220129141200j:plain:w200:left
f:id:knowwell-livewell:20220129141233j:plain:w200:left
f:id:knowwell-livewell:20220129141304j:plain:w200:left

当たり前ではありますが、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")
f:id:knowwell-livewell:20220129155041j:plain:w200:left
"""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")
f:id:knowwell-livewell:20220129155244j:plain:w200:left

白色はRGBチャネルすべてに大きい値が出るので、目の部分も抽出されています。ちなみに0~1に正規化する意味は特にないです。では、それぞれの領域を統合しましょう。

#Rチャネルで抽出した領域とBチャネルで抽出した領域を結合する
ROI_RB = np.maximum(ROI_R, ROI_B)
#表示する
plt.imshow(ROI_RB,cmap="gray")
f:id:knowwell-livewell:20220129160401j:plain:w200:left

これでいいのですが、ちょっと後ろの線が気になるので、モルフォロジー変換(オープニング)で消してしまいます。カーネルサイズは画像サイズに応じて変更する必要があります。

#オープニング処理する
kernel = np.ones((15,15),np.uint8)
opening = cv2.morphologyEx(ROI_RB, cv2.MORPH_OPEN, kernel)
#表示する
plt.imshow(opening,cmap="gray")
f:id:knowwell-livewell:20220129155953j:plain:w200:left

ちなみにOpenCVで保存するときは、0~255でuint8にしておかなければいけません。

opening = opening * 255.
cv2.imwrite("result.jpg", opening.astype("uint8"))


これで完成です。

終わりに

この記事は元々違うやり方(もっと面白い処理方法)を紹介しようと思って書き始めたのですが、諸々の事情でやめることになり、こんな感じになりました。Lab空間やHSV空間に変換して抽出してみるのも面白いと思います。