OpenCV – マスク画像を利用した画像処理について

目次

概要

マスク画像とその使い道について解説します。

マスク画像

画像と同じ大きさの 2 値画像で、処理したい画素を 255、それ以外の画素を 0 で表すものをマスク画像 (mask image) といいます。マスク画像を使用することで、画像の一部の画素のみを対象に処理を行ったり、背景合成などに利用できます。

マスク画像

マスク画像の作り方

マスク画像は機械的または手動で作成します。

  • 機械的にマスク画像を作成する方法:
    • 2 値化などの方法で機械的に抽出する
    • 対象領域が図形の場合、cv2.rectangle()cv2.circle() など描画関数を使用して作成する
  • 手動でマスク画像を作成する方法:
    • GIMP や Photoshop などのレイヤ機能があるペイントソフトを使い、元画像を背景に設定して作成する

関連記事

描画関数を使用してマスク画像を作成する

cv2.rectangle()cv2.circle() といった関数は、通常は画像に図形を描くことに使用されますが、値を 0 で初期化した画像を作成し、これらの関数で処理したい領域を 255 に塗りつぶすことで、マスク画像の作成にも使用できます。

In [1]:
import cv2
from IPython.display import Image, display


def imshow(img):
    """ndarray 配列をインラインで Notebook 上に表示する。"""
    ret, encoded = cv2.imencode(".jpg", img)
    display(Image(encoded))

長方形のマスク画像を作成する

In [2]:
import cv2
import numpy as np

# 0で初期化した画像を作成する。
mask = np.zeros((400, 400), dtype=np.uint8)

# 処理したい領域を 255 で塗りつぶしたマスク画像を作成する。
cv2.rectangle(mask, (100, 100), (300, 300), color=255, thickness=-1)
imshow(mask)

円のマスク画像を作成する

In [3]:
import cv2
import numpy as np

# 0で初期化した画像を作成する。
mask = np.zeros((400, 400), dtype=np.uint8)

# 処理したい領域を 255 で塗りつぶしたマスク画像を作成する。
cv2.circle(mask, (200, 200), 100, color=255, thickness=-1)
imshow(mask)

ポリゴンのマスク画像を作成する

In [4]:
import cv2
import numpy as np

# 0 で初期化した画像を作成する。
mask = np.zeros((400, 400), dtype=np.uint8)


# 処理したい領域を 255 で塗りつぶしたマスク画像を作成する。
def create_star_points(cx, cy, r1, r2, n):
    points = []
    for i in range(n):
        theta = 2 * np.pi * i / n
        x = cx + r1 * np.cos(theta)
        y = cy + r1 * np.sin(theta)
        points.append((x, y))

        theta += np.pi / n
        x = cx + r2 * np.cos(theta)
        y = cy + r2 * np.sin(theta)
        points.append((x, y))

    return points


# ポリゴンの点を作成する。
points = create_star_points(200, 200, 100, 50, 5)
cv2.fillPoly(mask, [np.array(points, np.int32)], color=255)
imshow(mask)

マスク画像を利用して一部の画素のみ処理を行う

画像の中央部分以外にガウシアンフィルタを適用し、外縁部をぼやけさせる例を紹介します。

sample1.jpg

In [5]:
import cv2
import numpy as np


# 画像を読み込む。
img = cv2.imread("sample1.jpg")

# マスク画像を作成する。
mask = np.full(img.shape[:2], 255, dtype=img.dtype)

# 白い画像の中心に黒い円を描画する。
cx = img.shape[1] // 2
cy = img.shape[0] // 2
cv2.circle(mask, (cx, cy), 100, color=0, thickness=-1)
imshow(mask)

# メディアンフィルタを適用する。
dst = cv2.blur(img, (13, 13))

# マスクの値が0の画素は処理を行わないので、元画像の画素値に戻す。
dst[mask == 0] = img[mask == 0]
imshow(dst)

マスク画像を利用して背景合成する

マスク画像を利用して背景を合成することをマスク合成 (mask compositing) といいます。

sample2.jpg

前景画像

sample3.jpg

背景画像

In [6]:
# 前景画像を読み込む。
fg_img = cv2.imread("sample2.jpg")

# 背景画像を読み込む。
bg_img = cv2.imread("sample3.jpg")

# HSV に変換する。
hsv = cv2.cvtColor(fg_img, cv2.COLOR_BGR2HSV)

# 2値化する。
bin_img = cv2.inRange(hsv, (0, 10, 0), (255, 255, 255))
imshow(bin_img)

# 輪郭抽出する。
contours, _ = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 面積が最大の輪郭を取得する
contour = max(contours, key=lambda x: cv2.contourArea(x))

抽出した輪郭は以下のようになっています。黒い画像を作成し、輪郭の内部を cv2.drawContours() で 255 に塗りつぶすことでマスク画像を作成します。

In [7]:
# マスク画像を作成する。
mask = np.zeros_like(bin_img)
cv2.drawContours(mask, [contour], -1, color=255, thickness=-1)
imshow(mask)

前景画像 fg_img を背景画像 bg_img に貼り付けます。 貼り付けた際に前景画像が背景画像にはみ出さないように調整します。

貼り付け処理

In [8]:
x, y = 10, 10  # 貼り付け位置

# 幅、高さは前景画像と背景画像の共通部分をとる
w = min(fg_img.shape[1], bg_img.shape[1] - x)
h = min(fg_img.shape[0], bg_img.shape[0] - y)

# 合成する領域
fg_roi = fg_img[:h, :w]  # 前傾画像のうち、合成する領域
bg_roi = bg_img[y : y + h, x : x + w]  # 背景画像のうち、合成する領域

# 合成する。
bg_roi[:] = np.where(mask[:h, :w, np.newaxis] == 0, bg_roi, fg_roi)
imshow(bg_img)

クロマキー合成

色情報を元にマスク画像を作成して背景を合成するマスク合成をクロマキー合成 (chromekey compositing) といいます。通常は、前景と背景を分離してマスク画像を作成しやすいように、緑を背景にして撮影します (グリーンバック)。 映画やニュース番組の撮影でよく使われる手法です。

sample4.jpg

前景画像

sample5.jpg

背景画像

HSV 色空間に変換し、cv2.inRange() で Hue の値に着目して、緑の領域とそれ以外の領域に分けます。 緑以外の領域を白としたマスク画像を作成するため、cv2.inRange() の結果をネガポジ反転します。

In [9]:
# 画像を読み込む。
fg_img = cv2.imread("sample4.jpg")

# 背景画像を読み込む。
bg_img = cv2.imread("sample5.jpg")

# HSV に変換する。
hsv = cv2.cvtColor(fg_img, cv2.COLOR_BGR2HSV)

# 2値化する。
bin_img = ~cv2.inRange(hsv, (62, 100, 0), (79, 255, 255))

# 輪郭抽出する。
contours, _ = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 面積が最大の輪郭を取得する
contour = max(contours, key=lambda x: cv2.contourArea(x))

# マスク画像を作成する。
mask = np.zeros_like(bin_img)
cv2.drawContours(mask, [contour], -1, color=255, thickness=-1)
imshow(mask)
In [10]:
# 貼り付け位置
x, y = 30, 10

# 幅、高さは前景画像と背景画像の共通部分をとる
w = min(fg_img.shape[1], bg_img.shape[1] - x)
h = min(fg_img.shape[0], bg_img.shape[0] - y)

# 合成する領域
fg_roi = fg_img[:h, :w]
bg_roi = bg_img[y : y + h, x : x + w]

# 合成する。
dst = np.where(mask[:h, :w, np.newaxis] == 0, bg_roi, fg_roi)
imshow(dst)

コメント

コメント一覧 (0件)

  • コメント失礼致します。
    当方勉強し始めたばかりの初心者です…。

    「マスク画像を利用して背景合成をする」についてご質問があります
    背景画像自体のサイズはそのままで
    貼り付ける画像が背景に対して横幅のみ収まっていれば良い時は
    どのような記述になりますでしょうか…?

    お手数おかけして申し訳ありませんが、ご教授いただけますと幸いです。

      • お忙しい中、早速のご返信有難うございます!やりたかった事は正にこれです!
        分かりづらい言い回しで申し訳有りませんでした…
        非常に勉強になりました!有難う御座います。
        わざわざコード全体も記載して下さって…

        今後も参考に勉強にさせて頂きます!!

コメントする

目次