質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.86%

OpenCVでの顔画像のトリミング・保存が失敗してしまう

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,232

sahuransan

score 7

 前提・実現したいこと

画像の顔の部分をトリミングして保存するという事をしてみたくて、他の方が公開されていたコードを使ってみたのですがエラーが出てしまい実行できません。何がいけないのでしょうか?
初歩的な質問だったら申し訳ありませんが、是非レクチャーお願いいたします。

 発生している問題・エラーメッセージ

C:\Users\kiara\PycharmProjects\openCV\venv\Scripts\python.exe C:/Users/kiara/PycharmProjects/openCV/img_cuts.py
Namespace(cascade='alt', input_dir='./input/', min=80, move_dir='/done/', neighbors=2, scale=1.3)
Traceback (most recent call last):
  File "C:\Users\kiara\Anaconda3\lib\shutil.py", line 544, in move
    os.rename(src, real_dst)
FileExistsError: [WinError 183] 既に存在するファイルを作成することはできません。: './input/00000001.jpg' -> './input//done/'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:/Users/kiara/PycharmProjects/openCV/img_cuts.py", line 124, in <module>
    shutil.move(FLAGS.input_dir + file_name, FLAGS.input_dir + FLAGS.move_dir)
  File "C:\Users\kiara\Anaconda3\lib\shutil.py", line 558, in move
    copy_function(src, real_dst)
  File "C:\Users\kiara\Anaconda3\lib\shutil.py", line 257, in copy2
    copyfile(src, dst, follow_symlinks=follow_symlinks)
  File "C:\Users\kiara\Anaconda3\lib\shutil.py", line 121, in copyfile
    with open(dst, 'wb') as fdst:
OSError: [Errno 22] Invalid argument: './input//done/'

Process finished with exit code 1

 該当のソースコード

import cv2, os, argparse, shutil

# 切り抜いた画像の保存先ディレクトリ
SAVE_PATH = "./outputs/"

# 基本的なモデルパラメータ
FLAGS = None

# 学習済モデルの種類
CASCADE = ["default", "alt", "alt2", "tree", "profile", "nose"]

# 直接実行されている場合に通る(importされて実行時は通らない)
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--cascade",
        type=str,
        default="alt",
        choices=CASCADE,
        help="cascade file."
    )
    parser.add_argument(
        "--scale",
        type=float,
        default=1.3,
        help="scaleFactor value of detectMultiScale."
    )
    parser.add_argument(
        "--neighbors",
        type=int,
        default=2,
        help="minNeighbors value of detectMultiScale."
    )
    parser.add_argument(
        "--min",
        type=int,
        default=80,
        help="minSize value of detectMultiScale."
    )
    parser.add_argument(
        "--input_dir",
        type=str,
        default="./input/",
        help="The path of input directory."
    )
    parser.add_argument(
        "--move_dir",
        type=str,
        default="/done/",
        help="The path of moving detected files."
    )

# パラメータ取得と実行
FLAGS, unparsed = parser.parse_known_args()

# 分類器ディレクトリ(以下から取得)
# https://github.com/opencv/opencv/blob/master/data/haarcascades/
# https://github.com/opencv/opencv_contrib/blob/master/modules/face/data/cascades/

# 学習済モデルファイル
if FLAGS.cascade == CASCADE[0]:  # "default":
    cascade_path = "C:/Users/kiara/Desktop/opencv-master/data/haarcascades/haarcascade_frontalface_default.xml"
elif FLAGS.cascade == CASCADE[1]:  # "alt":
    cascade_path = "C:/Users/kiara/Desktop/opencv-master/data/haarcascades/haarcascade_frontalface_alt.xml"
elif FLAGS.cascade == CASCADE[2]:  # "alt2":
    cascade_path = "C:/Users/kiara/Desktop/opencv-master/data/haarcascades/haarcascade_frontalface_alt2.xml"
elif FLAGS.cascade == CASCADE[3]:  # "tree":
    cascade_path = "C:/Users/kiara/Desktop/opencv-master/data/haarcascades/haarcascade_frontalface_alt_tree.xml"
elif FLAGS.cascade == CASCADE[4]:  # "profile":
    cascade_path = "C:/Users/kiara/Desktop/opencv-master/data/haarcascades/haarcascade_profileface.xml"
elif FLAGS.cascade == CASCADE[5]:  # "nose":
    cascade_path = "C:/Users/kiara/Desktop/opencv_contrib-master/modules/face/data/cascades/haarcascade_mcs_nose.xml"

# カスケード分類器の特徴量を取得する
faceCascade = cv2.CascadeClassifier(cascade_path)

# 顔検知に成功した数(デフォルトで0を指定)
face_detect_count = 0

# 顔検知に失敗した数(デフォルトで0を指定)
face_undetected_count = 0

# フォルダ内ファイルを変数に格納(ディレクトリも格納)
files = os.listdir(FLAGS.input_dir)

# 成功ファイルを移動いない場合は、出力用のディレクトリが存在する場合、削除して再作成
if FLAGS.move_dir == "":
    if os.path.exists(SAVE_PATH):
        shutil.rmtree(SAVE_PATH)
    os.mkdir(SAVE_PATH)

print(FLAGS)

# 集めた画像データから顔が検知されたら、切り取り、保存する。
for file_name in files:

    # ファイルの場合(ディレクトリではない場合)
    if os.path.isfile(FLAGS.input_dir + file_name):

        # 画像ファイル読込
        img = cv2.imread(FLAGS.input_dir + file_name)

        # 大量に画像があると稀に失敗するファイルがあるのでログ出力してスキップ(原因不明)
        if img is None:
            print(file_name + ':Cannot read image file')
            continue

        # カラーからグレースケールへ変換(カラーで顔検出しないため)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        # 顔検出
        face = faceCascade.detectMultiScale(gray, scaleFactor=FLAGS.scale, minNeighbors=FLAGS.neighbors,
                                            minSize=(FLAGS.min, FLAGS.min))

        if len(face) > 0:
            for rect in face:
                # 切り取った画像出力
                cv2.imwrite(SAVE_PATH + str(face_detect_count) + file_name,
                            img[rect[1]:rect[1] + rect[3], rect[0]:rect[0] + rect[2]])
                face_detect_count = face_detect_count + 1

            # 検出できたファイルは移動
            if FLAGS.move_dir != "":
                shutil.move(FLAGS.input_dir + file_name, FLAGS.input_dir + FLAGS.move_dir)
        else:
            print(file_name + ':No Face')
            face_undetected_count = face_undetected_count + 1

print('Undetected Image Files:%d' % face_undetected_count)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

既に存在するファイルを作成することはできません。

原因は移動先にinput/done/00000001.jpgのファイルが存在するのが問題です。
※)どうしてこの事象が発生するのかは後述します。

対策としてはPath#replaceを使えばよいかと。
以下は未テストコードです。

from pathlib import Path

# 前略

# 成功ファイルを移動いない場合は、出力用のディレクトリが存在する場合、削除して再作成
if FLAGS.move_dir == "":
    if os.path.exists(SAVE_PATH):
        shutil.rmtree(SAVE_PATH)
    os.mkdir(SAVE_PATH)
else: # この行を追加
    move_dir = Path(FLAGS.input_dir, FLAGS.move_dir)
    # input/done ディレクトリの作成
    move_dir.mkdir(parents=True, exist_ok=True)

# 中略

# 集めた画像データから顔が検知されたら、切り取り、保存する。
for file_name in files:
    src_file = Path(FLAGS.input_dir, file_name)
    # ファイルの場合(ディレクトリではない場合)
    if src_file.is_file():
        # 画像ファイル読込
        img = cv2.imread(str(src_file))

        # 中略

            # 検出できたファイルは移動
            if FLAGS.move_dir != "":
                src_file.replace(move_dir / file_name)

※解説
Windowsではshutil.move()が内部で呼び出しているos.rename()がリネーム先ファイルが存在する時にOSErrorになる点が他のOS(macOS/Linux)と動作が違います。

  File "C:\Users\kiara\Anaconda3\lib\shutil.py", line 544, in move
    os.rename(src, real_dst)

憶測ですが、サンプルコード記載した人はMacOSlinuxで動作テストを行い、質問者さんはWindowsで動作させたことでこの事象が発生したのではないかと。
Python言語においてクロスプラットフォーム環境でrename処理を行うにはos.replaceもしくはPath#replaceを使ってくださいな。

◆参考情報

Unixでは、dst が存在し、かつファイルの場合、ユーザーの権限があるかぎり暗黙のうちに置き換えられます。この操作は、一部の Unix 系システムにおいて src と dst が異なるファイルシステム上にあると失敗することがあります。ファイル名の変更が成功する場合はアトミック操作となります (これは POSIX 要求仕様です)。Windows では、dst がすでに存在する場合には、たとえファイルの場合でも OSError が送出されます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/02 17:36 編集

    あ、ごめんなさい。回答コードがミスしてました。回答コードを修正しました。

    キャンセル

  • 2018/09/02 18:27

    ありがとうございます!!完璧に動きました!
    本当に助かりましたm(_ _"m)

    キャンセル

  • 2018/09/02 18:56

    @sahuransanさんへ
    問題が解決してよかったですー。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.86%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る