画像の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)
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()
結構鋭いピークが出ています。
色相は360°の角度等、ぐるっと回るような値で表され、赤→黄→緑→水色→青→紫→赤といった形で回っていくようです。
今回は0~180の値が割り当てられているので、0が赤、60が緑、120が青、といった形になるよう。
今回は、シールのピンクのような、赤のような色を取り出したいと思いますが、どちらかというと紫寄りの色に思えるので、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)
いい感じにシール領域が取れました。
シール台紙領域
ついでに、フローリング領域も分離して、シール台紙領域も検出してみたいと思います。
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)
シール台紙領域も一緒に入ってしまった…
彩度でシール台紙領域
シール台紙領域は白い色になっていますが、白、グレー、黒といった色だとHue(色相)の値は不定になってしまうとのこと。
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()
彩度が低いところに鋭いピークがあります。これがシール台紙領域では?
彩度で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)
シール台紙領域がいい感じに取れたと思います。
ただ、左画像のフローリングの光の反射も拾ってしまった…
これは今のところ除外する方法が思いつきません。要リサーチ。
合成
最後にシール領域、シール台紙領域、その他の領域、と分類した画像を作ってみたいと思います。
>>> 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)
白い部分がシール領域、グレーの部分がシール台紙領域(シールの白い部分も含む)です。
ここまで
以上、ヒストグラムと2値化をやってみました。
チュートリアルでは適応的閾値処理、大津の2値化というのもありますが、
必要に応じてまた調べながらやってみたいと思います。
次回はチュートリアル通り画像の平滑化、としたいと思います。