見出し画像

【Python】特定の色の領域を切り抜いて、一番大きい領域だけ取得する

画像の中から「赤い!!!!」って部分だけを見つけて切り抜きます。

「人間の目には赤く見えてるけど~~実際はちょっと青が入ってて~~」的な考慮一切なし、「RGB値が[255,0,0]のところを見つけてぶっこ抜く!!!」という力業です。

が、原始的すぎるからかなかなか解説記事見つからなくて大変だったのでまとめておきます。

    def get_color_area(image_2d, target_bgr, buffer):
       bgr_lower = np.array(target_bgr) - buffer
       bgr_upper = np.array(target_bgr) + buffer
       img_mask = cv2.inRange(image_2d, bgr_lower, bgr_upper)
       return img_mask

cv2.imread(path)等で読み込んだ画像の二次元配列と、しきい値となるBGR値を配列([0,0,0]形式)で、バッファ値(後述)を数値で渡すと、その色以外の領域をマスクした白黒画像(その色の部分が白、そうじゃない部分が黒)の二次元配列を得られます。

後はこれを

cv2.findContours(image_2d, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

の第一引数に渡してあげれば領域の配列を得られます。

全体の流れは

path = "画像のパス"
image_2d = cv2.imread(path)
target_bgr = [0,0,255]
buffer = 5
area = get_color_area(image_2d, target_bgr, buffer)
contours = cv2.findContours(image_2d, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

ってな具合です。

注意すべきは、cv2.inRangeへ渡すしきい値はRGB値ではなくBGR値である必要があるところです。最初からBGR値を用意するか、RGB値をなんか上手いこと変換して渡しましょう。

ちなみに前回記事でEnum型の説明のついでにRGBからBGRに変換する処理やっています。超原始的な方法ですが。良い方法あれば教えて欲しい。

photoshopで保存すると色が変わるんだよこんちくしょう

[255,0,0]を普通にjpgで保存すると[254,0,0]に、[0,0,255]を保存すると[1,0,255]になるんですよあいつ。なんで勝手に増えたり減ったりするんですか困るんですけど。

というわけでPS側の設定を見直しても良いんですが、現実的には別のソフトで作った画像も読み込む訳で、となると[255,0,0]だけ受け付けますと言って、ユーザーも[255,0,0]のつもりで作った画像でも、勝手に[254,0,0]になってる可能性があるよな、jpgじゃ特に境界線ノイズ出てぶれたりするもんな、ってことで若干のバッファを持たせることにしました。

しかも上にぶれたり下にぶれたりするので、バッファを指定すると、基準として渡すBGR値からバッファ値を引いた分が最低ライン、足した分が最高ラインになるようにしてあります。

np.array()でnumpyのarray形式にしてあげてから、直接数字足したり引いたりすると、array全体の数字に足し算引き算してくれるそうです。このとき、255超えちゃったり0下回っちゃったりしても大丈夫っぽいし、np.arrayのまま引数に渡して大丈夫です。

バッファは5~10くらい渡せばいい気がします。

見つけたエリアの中で一番大きいところだけ返す

今回の最終的な目標は「画像の中から赤で囲まれている場所を探してOCRする」で、「赤で囲む場所は画像1つにつき1箇所」というルールで運用予定なので、うっかり紛れちゃった細かい赤は捨て去り、一番大きい赤いエリアだけを取得したいと思います。

まず上記の方法で「赤いところだけ白、後は黒」にした画像(の二次元配列)を用意します。で、

    def get_main_contour(self, image_2d):
       contours = self.__get_contour(image_2d)
       contour_areas = [cv2.contourArea(contour) for contour in contours]
       if not contour_areas: return []
       
       areas_array = np.array(contour_areas)
       max_index = np.argmax(areas_array)
       return contours[max_index]

    def __get_contour(self, image_2d):
       contours, _ = cv2.findContours(image_2d, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
       return contours

ってなかんじで。

cv2.findContoursで領域を取得すると領域のリストが返ってくるのですが、その状態だと「領域の右上、右下、左上、左下の座標のリスト」なので、一旦cv2.contourAreaを噛ませて領域のサイズのリストを作ります。

で、この先リストが空だとエラーになるので、リストが空だったら早期リターン。

領域サイズのリストをnp.arrayに変換して、np.argmaxに渡すと、リスト内の最大値のインデックスを返してくれるので、もとの領域の座標リストから同じインデックスの値を返してあげればOKというわけです。

これで、「指定のRGB値の領域の中で一番おっきいところを取得」が完成です。やったね。

この記事が気に入ったらサポートをしてみませんか?