ピンボケや手ぶれ写真を機械的に検出する方法

Python

カメラを新調したことから週末には1000毎単位で写真を撮影することが多々あります。
動体撮影をするため連射を多用しています。このことから、ピンボケや手ぶれなどの不要な写真も含まれます。
RAW+JPEGで撮影しているため1毎あたり約60MBです。
このままではストレージを圧迫するため、サムネイルを一覧して明らかに不要な写真は消しています。
それでも、人間が手動でやっていくのは骨が折れる作業です。
この問題を解決するために、機械的に不要な写真を検出する方法を何回かにわたって紹介していきます。
今回はOpenCVを利用してピンボケの写真を検出する実装をしてみます。

スポンサーリンク

ピンボケや手ぶれをどのように検出するのか?

基本的には写真のエッジを検出することによって実現します。
ピンボケの写真はエッジがボケてしまっていて、エッジが少ない状態になっています。
OpenCVではLaplacian微分を利用することによって用意にエッジを検出することが可能です。
参考: 画像の勾配 — OpenCV-Python Tutorials 1 documentation

利用する画像

利用する画像はLenaを利用します。一方は、意図的にぼかし編集をした画像です。

元画像 ぼかし画像

ピンボケ画像を検出する実装

まずはコマンドラインで利用できるようにしたいので、おまじないを記述してきます。

from imutils import paths
import argparse
import cv2
import os
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--images", required=True,
                help="path to input directory of images")
ap.add_argument("-t", "--threshold", type=float, default=100.0,
                help="focus measures that fall below this value will be considered 'blurry'")
args = vars(ap.parse_args())

OpenCVのLaplacianを利用してエッジを検出する処理をするメソッドを追加

def variance_of_laplacian(image):
    return cv2.Laplacian(image, cv2.CV_64F)

検出したエッジのスコアを画像の左上に記述処理をするメソッドを追加

def report_image(image, laplacian, text):
    cv2.putText(image, "{}: {:.2f}".format(text, laplacian.var()), (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 3)

スコアを記述した画像を保存する処理を追加

def write_image(file_path, image, sub_dir="/report"):
    dir_file = os.path.split(file_path)
    dir = dir_file[0]
    file_name = dir_file[1]
    report_dir = dir + sub_dir
    os.makedirs(report_dir, exist_ok=True)
    cv2.imwrite(report_dir + "/" + file_name, image)

メインの処理を追加。ここでは引数のimagesで渡されたディレクトリに格納されている画像を次々に処理していきます。
Laplacianのスコアが100以下であれば、ピンボケ写真と判断しています。

for image_path in paths.list_images(args["images"]):
    image = cv2.imread(image_path)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    laplacian = variance_of_laplacian(gray)
    text = "Not Blurry"
    if laplacian.var() < args["threshold"]:
        text = "Blurry"
    report_image(image, laplacian, text)
    write_image(image_path, image)
    write_image(image_path, laplacian, "/laplacian")

全体を通したコードはGitHubを確認してください。

ピンボケや手ぶれを検出する

それでは実装したスクリプトを実行してみます。

python detect_blur_for_all.py -i ./images

それぞれ処理された画像を確認してみます。

元画像 ぼかし画像

Not Blurry: 390.35

Blurry: 11.34

これで、エッジを数値化することができました。
元画像のスコアが390.35、ぼかし処理されている画像が11.34となりました。
ぼけている画像の方が数値が低くなっていることが確認できました。つまりエッジの検出があまりできなかったことを意味します。
実際に検出したエッジ部分の画像を確認してみます。

元画像 ぼかし画像

元画像の方はエッジを検出しているため、人の形や顔が確認できるかと思います。
ぼかし画像はまったくエッジが検出できていないことがわかります。

考察

以上のことから、写真や画像のエッジを検出することによって、ピンボケ写真を検出することができます。
ですが、一眼レフカメラなどで顔や瞳にピントをあてて、背景をぼかした写真を撮影することがあるかと思います。そのような写真の場合はエッジが検出できない部分が多くなります。
今回のような判定だと、背景がぼけていて、顔にピントがあっているポートレートだとピンボケ写真として判断してしまうことがあります。
スコアの閾値はコマンドラインのオプションの-tを閾値としているので、閾値を下げることで多少の対処はできますが、根本的な解決はできません。
次回はこのような写真でも対処できる判定方法を実装していこうと思います。

タイトルとURLをコピーしました