勉強しないとな~blog

ちゃんと勉強せねば…な電気設計エンジニアです。

OpenCVやってみる-5. 2値化

画像の2値化(閾値処理)をやってみます。
ヒストグラムも表示してみます。

ヒストグラム

2値化するのに、まずどんな閾値を設定すればいいのか見るために、ヒストグラムを表示してみます。

今回は、以下の2つの画像を使います。
2020年と2021年の春のパン祭り台紙+シールです。シールの模様と色が少し違うみたいです。

>>> import cv2
>>> img1 = cv2.imread('harupan_200317_1.jpg')
>>> img2 = cv2.imread('harupan_210402_1.jpg')
>>> v,h = img1.shape[:2]
>>> size = (int(h/10), int(v/10))
>>> img1 = cv2.resize(img1, size, interpolation=cv2.INTER_AREA)
>>> img2 = cv2.resize(img2, size, interpolation=cv2.INTER_AREA)
>>> import numpy as np
>>> stackedImg = np.hstack((img1, img2))
>>> cv2.imshow('harupan', stackedImg)
>>> cv2.waitKey(0)

f:id:nokixa:20210412090122p:plain

2画像の連結でまとめて表示しました。

Numpy の vstack、hstack で配列を連結-python | コード7区

シールの色が、特定の1色になっているので、HSVフォーマットで色相(Hue)を見てやると良さそうな気がします。

>>> img1_hsv = cv2.cvtColor(img1, cv2.COLOR_BGR2HSV)
>>> img2_hsv = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)

cv2.calcHist()関数でヒストグラムを計算、matplotlibで表示してみます。importする必要があります。

>>> hist1_hue = cv2.calcHist([img1_hsv], [0], None, [180], [0, 180])
>>> hist2_hue = cv2.calcHist([img2_hsv], [0], None, [180], [0, 180])
>>> from matplotlib import pyplot as plt
>>> plt.plot(hist1_hue, color='b')
[<matplotlib.lines.Line2D object at 0x000002256AF43808>]
>>> plt.plot(hist2_hue, color='g')
[<matplotlib.lines.Line2D object at 0x000002256AF45808>]
>>> plt.xlim([0,180])
(0.0, 180.0)
>>> plt.show()

f:id:nokixa:20210413234456p:plain

結構鋭いピークが出ています。

色相は360°の角度等、ぐるっと回るような値で表され、赤→黄→緑→水色→青→紫→赤といった形で回っていくようです。
今回は0~180の値が割り当てられているので、0が赤、60が緑、120が青、といった形になるよう。

HSV色空間とは : PEKO STEP

今回は、シールのピンクのような、赤のような色を取り出したいと思いますが、どちらかというと紫寄りの色に思えるので、170、180あたりのピークがそれではないかと思われます。
20あたりのピークはフローリングの色かな?

このHue画像を閾値160ぐらいで2値化してやればシール領域(および台紙のピンク領域)が取り出せるのでは?
やってみます。

>>> img1_hsv.shape
(403, 302, 3)
>>> ret, thresh1 = cv2.threshold(img1_hsv[:,:,0], 160, 255, cv2.THRESH_BINARY)
>>> ret, thresh2 = cv2.threshold(img2_hsv[:,:,0], 160, 255, cv2.THRESH_BINARY)
>>> thresh1.shape
(403, 302)
>>> thresh = np.hstack((thresh1, thresh2))
>>> cv2.imshow('thresh', thresh)

f:id:nokixa:20210413235038p:plain

いい感じにシール領域が取れました。

シール台紙領域

ついでに、フローリング領域も分離して、シール台紙領域も検出してみたいと思います。

Hueの値0付近はシール領域な気がするのと、40付近のなだらかなピークはフローリングでの光の反射な気がするので、Hueが10~60のあたりを取ってみたいと思います。

cv2.threshold()では閾値以上、以下の2分類しかできないので、ビット単位処理での組み合わせを使います。

画像の算術演算 — OpenCV-Python Tutorials 1 documentation

>>> ret, thresh1_gt10 = cv2.threshold(img1_hsv[:,:,0], 10, 255, cv2.THRESH_BINARY)
>>> ret, thresh1_lt60 = cv2.threshold(img1_hsv[:,:,0], 60, 255, cv2.THRESH_BINARY_INV)
>>> ret, thresh2_gt10 = cv2.threshold(img2_hsv[:,:,0], 10, 255, cv2.THRESH_BINARY)
>>> ret, thresh2_lt60 = cv2.threshold(img2_hsv[:,:,0], 60, 255, cv2.THRESH_BINARY_INV)
>>> thresh1_10to60 = cv2.bitwise_and(thresh1_gt10, thresh1_lt60)
>>> thresh2_10to60 = cv2.bitwise_and(thresh2_gt10, thresh2_lt60)
>>> thresh = np.hstack((thresh1_10to60, thresh2_10to60))
>>> cv2.imshow('thresh', thresh)

f:id:nokixa:20210414000253p:plain

シール台紙領域も一緒に入ってしまった…

彩度でシール台紙領域

シール台紙領域は白い色になっていますが、白、グレー、黒といった色だとHue(色相)の値は不定になってしまうとのこと。

HSV色空間 - Wikipedia

HSVのS(Saturation:彩度)も使ってみます。

まずはヒストグラム

>>> hist1_sat = cv2.calcHist([img1_hsv], [1], None, [256], [0, 256])
>>> hist2_sat = cv2.calcHist([img2_hsv], [1], None, [256], [0, 256])
>>> plt.plot(hist1_sat, color='b')
[<matplotlib.lines.Line2D object at 0x000002256DD8B8C8>]
>>> plt.plot(hist2_sat, color='g')
[<matplotlib.lines.Line2D object at 0x000002256DDBF448>]
>>> plt.xlim([0, 256])
(0.0, 256.0)
>>> plt.show()

f:id:nokixa:20210414001818p:plain

彩度が低いところに鋭いピークがあります。これがシール台紙領域では?

彩度で2値化してみます。

>>> ret, thresh1_sat = cv2.threshold(img1_hsv[:,:,1], 50, 255, cv2.THRESH_BINARY_INV)
>>> ret, thresh2_sat = cv2.threshold(img2_hsv[:,:,1], 50, 255, cv2.THRESH_BINARY_INV)
>>> thresh = np.hstack((thresh1_sat, thresh2_sat))
>>> cv2.imshow('thresh', thresh)

f:id:nokixa:20210414002312p:plain

シール台紙領域がいい感じに取れたと思います。
ただ、左画像のフローリングの光の反射も拾ってしまった…
これは今のところ除外する方法が思いつきません。要リサーチ。

合成

最後にシール領域、シール台紙領域、その他の領域、と分類した画像を作ってみたいと思います。

>>> thresh_a = np.hstack((thresh1, thresh2))
>>> thresh_b = np.hstack((thresh1_sat_gray, thresh2_sat_gray))
>>> thresh_all = cv2.bitwise_or(thresh_a, thresh_b)
>>> cv2.imshow('thresh_all', thresh_all)
>>> cv2.waitKey(0)

f:id:nokixa:20210414083718p:plain

白い部分がシール領域、グレーの部分がシール台紙領域(シールの白い部分も含む)です。

ここまで

以上、ヒストグラムと2値化をやってみました。
チュートリアルでは適応的閾値処理、大津の2値化というのもありますが、 必要に応じてまた調べながらやってみたいと思います。

次回はチュートリアル通り画像の平滑化、としたいと思います。