OpenCVによる画像処理

画像の読み書き表示

OpenCV は画像を扱うためのライブラリである。インストールは pip install opencv-python(または conda install -c conda-forge opencv)でできる。歴史的な事情から、インポートするパッケージ名は cv2 であるが、import cv2 as cv のようにインポートすることが推奨されているようである(The OpenCV Coding Style Guide 参照)。

まずは適当な画像を読み込んでみる:

import numpy as np
import cv2 as cv

img = cv.imread("画像ファイル名")
type(img)   # numpy.ndarray
img.shape   # (縦ピクセル数, 横ピクセル数, チャンネル数)

これでエラーが出ればうまく読めていないことになる。私の環境では URL や ~/ で始まるパス名はうまくいかなかった。Windows 環境では全角ファイル名が読み込めないらしい(Python OpenCV の cv2.imread 及び cv2.imwrite で日本語を含むファイルパスを取り扱う際の問題への対処について 参照)。

このように読み込んだ画像を OpenCV で表示するには

cv.imshow('Image', img)  # 最初の引数(ウィンドウ名)は何でもよい

とすればよい。私の環境(python.org からインストールした Mac 版)ではうまくいくが、環境によってはうまくいかないようである。うまくいかなければ諦めて、次に示すように matplotlib で表示すればよい。なお、OpenCV の窓をすべて閉じるには次のようにする:

cv.destroyAllWindows()

上の方法でうまくいかない場合、matplotlib で表示すればよいが、OpenCV で読み込んだ画像は BGR の順、matplotlib の画像は RGB の順なので、そのまま matplotlib で表示すると色がおかしくなる。ここで論じられているように、img2 = cv.cvtColor(img, cv.COLOR_BGR2RGB) で変換してから plt.imshow(img2) すればよい。あるいは次のようにすればよい:

import matplotlib.pyplot as plt

plt.imshow(img[..., ::-1])  # ::-1 は逆順にするハック

画像は NumPy の ndarray で、成分は 'uint8' である。チャンネルは [B,G,R] の順に入る。例えば青だけの縦150、横100の画像は次のようにして生成できる:

img = np.array([[[255,0,0] for x in range(100)] for y in range(150)], dtype='uint8')
# または img = np.array([[[255,0,0]] * 100] * 150, dtype="uint8")

保存するには、次のようにする:

cv.imwrite("hoge.png", img)

Matplotlib でも画像を読み書きできるが、チャンネルは RGB の順である。デフォルトで読めるのは PNG だけだが、Pillow をインストール(pip install pillow)すれば他の画像形式も読める。

import matplotlib.pyplot as plt
img = plt.imread("画像ファイル名")
plt.imshow(img)
plt.imsave("hoge.png", img)

あるいは、画像の読み書きに特化した imageio ライブラリが多機能である:

import imageio
img = imageio.imread("入力画像ファイル名")
imageio.imsave("出力画像ファイル名", img)

Jupyter Notebook なら次のようにすればカレントディレクトリの画像ファイルをブラウザにインライン表示できる:

from IPython.display import Image
Image("画像ファイル名")

画像の加工

いろいろな加工ができる。例:

img2 = cv.GaussianBlur(img, (9,9), 0)  # カーネルサイズは奇数に

線画化

いらすとやさんの蝶と遊ぶ子供のイラストを線画化してみる:

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

def makecontour(path):
    # カーネルを定義
    kernel = np.ones((5,5), np.uint8)
    kernel[0,0] = kernel[0,4] = kernel[4,0] = kernel[4,4] = 0
    # グレースケールで画像を読み込む.
    # gray = cv.imread(path, cv.IMREAD_GRAYSCALE)
    # いらすとやの画像はアルファチャンネルがあるのでこれをまず白にする
    # ImageMagickの convert -flatten x.png y.png に対応
    img = cv.imread(path, cv.IMREAD_UNCHANGED)
    if img.shape[2] == 4:
        mask = img[:,:,3] == 0
        img[mask] = [255] * 4
        img = cv.cvtColor(img, cv.COLOR_BGRA2BGR)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    # ノイズ除去が必要なら
    # gray = cv.fastNlMeansDenoising(gray, h=20)
    # 白い部分を膨張させる
    dilated = cv.dilate(gray, kernel, iterations=1)
    # 差をとる
    diff = cv.absdiff(dilated, gray)
    # 白黒反転して2値化
    # _, contour = cv.threshold(255 - diff, 240, 255, cv.THRESH_BINARY)
    # あるいは
    contour = cv.adaptiveThreshold(255 - diff, 255,
                                   cv.ADAPTIVE_THRESH_GAUSSIAN_C,
                                   cv.THRESH_BINARY, 7, 8)
    return contour

# plt.close("all")
# plt.figure(figsize=[8, 8])
plt.set_cmap("gray")
# plt.clf()
contour = makecontour("bug_chou_tawamureru_kids.png")
plt.imshow(contour)
cv.imwrite("190831a.png", contour)
蝶と遊ぶ子供のイラスト

顔認識

顔が写っている画像を読み込み、顔の部分を赤い四角形で囲む:

import matplotlib.pyplot as plt
import cv2 as cv

img = cv.imread("lena_std.png")  # 顔が写っている画像

cascade = cv.CascadeClassifier('/usr/local/lib/python3.7/' +
    'site-packages/cv2/data/haarcascade_frontalface_default.xml')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
faces = cascade.detectMultiScale(gray, scaleFactor=1.5)

for (x, y, w, h) in faces:
    cv.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255))

plt.imshow(img[..., ::-1])