写真の人の顔に自動的にぼかしを入れる (OpenCV)

目標

たくさん写真があって他人が写っているとSNSにはあげられないので、ぼかしを入れることが多いと思いますが、たくさん写真があると作業がめんどくさいので、自動化スクリプトを作ります。

結果的に、opencvでの検出は思ったよりも上手くいかなかったので、retinanetで検出だけ行ってみました。長くなるので別の記事 RetinaNetで顔検出 - 情報関連の備忘録 で説明します。

ここではopencvだけの手順について触れます。 以下すべて pythonで作業します。 opencvの準備は GitHub - opencv/opencv: Open Source Computer Vision Librarypip install opencv-pythonで。

手順

  1. jpgで画像を読み込み
  2. OpenCVで顔を認識
  3. そのエリアにぼかしを入れる。適当に平滑化する

まずは1つの画像で手順を見ていきます。

1. jpgで画像を読み込み

import numpy as np
import cv2 as cv

img = cv.imread("sample.jpg")

opencvはBGRになっているので注意。 描画するときは以下のようにする必要がある。

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.show()

f:id:endosan:20190703043647p:plain
そのままimshowした時(BGRの時)

f:id:endosan:20190703043810p:plain
RGBに戻した結果

2. OpenCVで顔を認識

OpenCVに入っているカスケード分類機を使います。brew install opencv3で入れたら/usr/local/opt/opencv/share/opencv4/haarcascades/以下に入ってました。

face_cascade = "/usr/local/opt/opencv/share/opencv4/haarcascades/haarcascade_frontalface_default.xml"
cascade = cv.CascadeClassifier(face_cascade)

# RBGにする
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
facerect = cascade.detectMultiScale(img, scaleFactor=1.2, minNeighbors=2, minSize=(1, 1))

# show result
print(facerect)
if len(facerect) > 0:
    # 検出した場所すべてに赤色で枠を描画する
    for rect in facerect:
        cv.rectangle(img, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (0, 0, 255), thickness=3)

haarcascade_frontalcatface.xmlでdetectしましたが、ほとんど検出できてないです。

f:id:endosan:20190703110507j:plain
detectした結果

3. そのエリアにぼかしを入れる。適当に平滑化する

facerectのエリアの中だけ平滑化してしまう。

平滑化フィルタにはいくつか種類があります。

  • Average fileter
  • Gaussian filter
  • median filter

一番ぼかせそうな平均値の物を使っていきます。 facerectのエリアだけ取ってきて、ぼかします。そのあと、元の画像のfacerectのエリアにぼかしたものを代入します。 一応描画してどうなっているか確認します。

def bluring(img, x, y, width, height):
    tmp = img.copy()
    tmp[y:y + height, x:x + width] = cv.blur(tmp[y:y + height, x:x + width], (weight/3, height/3))
    return tmp

blured = img.copy()
for x, y, w, h in facerect:
    blured = bluring(blured, x, y, w, h) 

bluringの処理を変えたら好きなようにぼかせます。 上のデータでは難しかったので、別のもので行ってみました。

f:id:endosan:20190704005358j:plain
OpenCVで顔を検出してぼやかした結果

完成したコード

import cv2 as cv
import glob
import re

face_cascade = "/usr/local/opt/opencv/share/opencv4/haarcascades/haarcascade_frontalface_default.xml"
cascade = cv.CascadeClassifier(face_cascade)

def detect(img):
    """
    RGBで入れる
    """
    facerect = cascade.detectMultiScale(img, scaleFactor=1.2, minNeighbors=2, minSize=(1, 1))
    return facerect

def bluring(img, x, y, width, height):
    tmp = img.copy()
    tmp[y:y + height, x:x + width] = cv.blur(tmp[y:y + height, x:x + width], (weight/3, height/3))
    return tmp

def img2blured(img):
    facerect = detect(img)
    blured = img.copy()
    for x, y, w, h in facerect:
        blured = bluring(blured, x, y, w, h) 
    return blured

if __name__ == "__main__":
    jpgs = glob.glob("path/*.jpg")
    for jpg_path in jpbs:
        img = cv.imread(jpg_path)
        img = img2blured(cv.cvtColor(img, cv.COLOR_BGR2RGB))
        cv.imwrite("blured_" + jpg_path, cv.cvtColor(img, cv.COLOR_RGB2BGR))

補足: RAWデータを直接扱う時

直接RAWを扱うときはそれ用のモジュールを入れる。どこの会社のRAWか勝手に判定してくれるみたい。 rawpy · PyPI

brew install librawしてから、pip install rawpyでOK。

参考までに読み込みのコード

>>> import rawpy

>>> raw = rawpy.imread("sample1.ARW") # SONY
>>> type(raw)
rawpy._rawpy.RawPy
>>> raw.sizes
ImageSizes(raw_height=4024, raw_width=6048, height=4024, width=6024, top_margin=0, left_margin=0, iheight=4024, iwidth=6024, pixel_aspect=1.0, flip=0)
>>> # numpyに変換
>>> rgb = raw.postprocess(use_camera_wb=True)
>>> # saving
>>> cv.imwrite("sample1.jpg", cv.cvtColor(rgb, cv.COLOR_RGB2BGR))

f:id:endosan:20190703103038j:plain
ARWを読み込んでからjpgで保存した結果

参考

Python, OpenCVで顔検出と瞳検出(顔認識、瞳認識) | note.nkmk.me