Python: OpenCVを使用して、アメ横を行き交う人々の顔にモザイクをかける

今までやってきたことを応用して、アメ横を行き交う人々の顔にモザイクをかけていきます。

  • Python: OpenCVを使用して動画を読み込んで、何もせず書き込む1
  • Python: OpenCVを使用して動画を読み込んで、何もせず書き込む2
  • Python: OpenCVを使用して顔検出をする
  • すべてのフレームについて、それぞれ顔検出をし、モザイクをかけていきます。

    import cv2
    import numpy as np
    
    cascade = cv2.CascadeClassifier('/usr/local/lib64/python3.7/site-packages/cv2/data/haarcascade_frontalface_alt2.xml')
    
    def mosaic(img):
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        rects = cascade.detectMultiScale(gray, 1.1, 1, 0, (5,5))
        if len(rects) > 0:
            for rect in rects:
                x, y, w, h = rect
                face = img[y:y+h, x:x+w]
                dst = cv2.GaussianBlur(face, (25, 25), 10)
                img[y:y+h, x:x+w] = dst
        return img
                
    cap = cv2.VideoCapture('./img.MOV')
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    out = cv2.VideoWriter('output.m4v', fourcc, fps, (height, width))
    
    while True:
        ret, frame = cap.read()
        if ret:
            frame270 = np.rot90(frame, 3)
            out.write(mosaic(frame270))
        else:
            break
    
    cap.release()
    out.release()
    

    実行してみましたが、角度によって顔として検出されない場合があり、モザイクがチカチカしてしまいます。そこで、少し強引な手法ですが、顔検出した位置について、その後の15フレームについてモザイクをかけるようにしました。

    import cv2
    import numpy as np
    
    cascade = cv2.CascadeClassifier('/usr/local/lib64/python3.7/site-packages/cv2/data/haarcascade_frontalface_alt2.xml')
    
    def mosaic(img, rects_list):
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        rects = cascade.detectMultiScale(gray, 1.1, 1, 0, (5,5))
        rects_list.append(rects)
        for rects in rects_list:
            for rect in rects:
                x, y, w, h = rect
                face = img[y:y+h, x:x+w]
                dst = cv2.GaussianBlur(face, (25, 25), 10)
                img[y:y+h, x:x+w] = dst
        return img, rects
                
    cap = cv2.VideoCapture('./img.MOV')
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    out = cv2.VideoWriter('output.m4v', fourcc, fps, (height, width))
    
    rects_list = []
    while True:
        ret, frame = cap.read()
        if ret:
            frame270 = np.rot90(frame, 3)
            mosaiced_frame, rects = mosaic(frame270, rects_list[-15:])
            rects_list.append(rects)
            out.write(mosaiced_frame)
        else:
            break
    
    cap.release()
    out.release()
    

    結果は以下の通りです。


    無事モザイクをかけることができました。

    Python: OpenCVを使用して動画を読み込んで、何もせず書き込む2

    Python: OpenCVを使用して動画を読み込んで、何もせず書き込むの結果、動画の縦横が逆になってしまったので、修正していきます。

    import cv2
    import numpy as	np
    
    cap = cv2.VideoCapture('./img.MOV')
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    out = cv2.VideoWriter('output.m4v', fourcc, fps, (height, width))
    
    while True:
        ret, frame = cap.read()
        if ret:
            out.write(np.rot90(frame, 3))
        else:
            break
    
    cap.release()
    out.release()
    

    (width, height)を(height, width)に変更しました。
    また、書き込む際に、out.write(np.rot90(frame, 3))として270度回転しました。
    これで、もとの動画とまったく同じ動画が出力できました。

    Python: OpenCVを使用して動画を読み込んで、何もせず書き込む

    インストール

    Python3とOpenCVをインストールします。

    sudo yum install -y python3
    sudo pip3 install opencv-python
    

    動画を読み込んで、何もせず書き込む

    import cv2
    
    cap = cv2.VideoCapture('./img.MOV')
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    out = cv2.VideoWriter('output.m4v', fourcc, fps, (width, height))
    
    while True:
        ret, frame = cap.read()
        if ret:
            out.write(frame)
        else:
            break
    
    cap.release()
    out.release()
    

    とりあえず、これでうまくいきました。縦横が逆になっていますが…

    Python: AWS Lambdaでメールを送信する

    「AWS Lambda」->「関数の作成」->「一から作成」で関数を作成する。

    import boto3
    
    def send_email(source, to, subject, body):
        client = boto3.client('ses')
        response = client.send_email(
        Destination={
    	'ToAddresses': [
                to
    	],
        },
        Message={
    	'Body': {
                'Text': {
    		'Charset': 'UTF-8',
    		'Data': body,
                },
    	},
            'Subject': {
                'Charset': 'UTF-8',
                'Data': subject,
    	},
        },
        Source=source
        )
        return response
    
    def lambda_handler(event, context):
        source = '[メールアドレス]'
        to = '[メールアドレス]'
        subject = 'TEST'
        body = 'TEST\nTEST'
        r = send_email(source, to, subject, body)
        return r
    

    IAMロールの設定を正しく行っていないと、以下のようなエラーが出力される。

    {
      "errorMessage": "An error occurred (AccessDenied) when calling the SendEmail operation: User `XXXXXXXX’ is not authorized to perform `ses:SendEmail' on resource `XXXXXXXX’”,
      "errorType": "ClientError",
      "stackTrace": [
        [
          "/var/task/lambda_function.py",
          32,
          "lambda_handler",
          "r = send_email(source, to, subject, body)"
        ],
        [
          "/var/task/lambda_function.py",
          23,
          "send_email",
          "Source=source"
        ],
        [
          "/var/runtime/botocore/client.py",
          314,
          "_api_call",
          "return self._make_api_call(operation_name, kwargs)"
        ],
        [
          "/var/runtime/botocore/client.py",
          612,
          "_make_api_call",
          "raise error_class(parsed_response, operation_name)"
        ]
      ]
    }
    

    IAMロールにAmazonSESFullAccessポリシーをアタッチすることで解決。

    Python: Boto3を使用してメールを送信する

    boto3のインストール

    pip install boto3
    

    メールの送信

    import boto3
    
    def send_email(source, to, subject, body):
        client = boto3.client('ses')
        response = client.send_email(
        Destination={
    	'ToAddresses': [
                to
    	],
        },
        Message={
    	'Body': {
                'Text': {
    		'Charset': 'UTF-8',
    		'Data': body,
                },
    	},
            'Subject': {
                'Charset': 'UTF-8',
                'Data': subject,
    	},
        },
        Source=source
        )
        return response
    
    if __name__ == '__main__':
        source = '[送信元]'
        to = '[送信先]'
        subject = '[タイトル]'
        body = '[本文]'
        r = send_email(source, to, subject, body)
        print(r)
    

    以下のようなエラーが出力される場合には、「Simple Email Service」->「Email Addresses」でメールアドレスを承認しておく。

    Traceback (most recent call last):
      File "test.py", line 32, in 
        r = send_email(source, to, subject, body)
      File "[ファイル].py", line 23, in send_email
        Source=source
      File "/usr/local/lib/python3.7/site-packages/botocore/client.py", line 357, in _api_call
        return self._make_api_call(operation_name, kwargs)
      File "/usr/local/lib/python3.7/site-packages/botocore/client.py", line 661, in _make_api_call
        raise error_class(parsed_response, operation_name)
    botocore.errorfactory.MessageRejected: An error occurred (MessageRejected) when calling the SendEmail operation: Email address is not verified. The following identities failed the check in region US-EAST-1: [メールアドレス]
    

    React: react-helmetを使用してtitleタグを書き換える

    インストール

    yarn add react-helmet
    

    titleタグを書き換える

    import React, { Component } from 'react';
    import { Helmet } from 'react-helmet';
    
    class App extends Component {
      render() {
        return (
          <div>
            <Helmet title="[タイトル]" />
          </div>
        );
      }
    }
    
    export default App;
    

    もしくは、

    import React, { Component } from 'react';
    import { Helmet } from 'react-helmet';
    
    class App extends Component {
      render() {
        return (
          <div>
            <Helmet>
              <title>
                [タイトル]
              </title>
          </Helmet>
          </div>
        );
      }
    }
    
    export default App;
    

    Python: OpenCVを使用して顔検出をする

    OpenCVを使うと、簡単に顔を検出することができます。

    import cv2
    
    cascade = cv2.CascadeClassifier('/usr/local/lib64/python3.7/site-packages/cv2/data/haarcascade_frontalface_default.xml')
    img = cv2.imread('img/img.jpg')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    rects = cascade.detectMultiScale(gray, 1.1, 3,0, (20,20))
    
    if len(rects) > 0:
        for rect in rects:
            cv2.rectangle(img, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (0, 0,255), thickness=2)
    else:
        print("検出なし")
    
    cv2.imwrite('img/img_cascade.jpg', img)
    

    分類器は、

    cascade = cv2.CascadeClassifier('/usr/local/lib64/python3.7/site-packages/cv2/data/haarcascade_frontalface_default.xml')
    

    として指定していますが、分類器のファイルの場所は以下のようにして確認できます。

    import cv2
    print(cv2.__path__)
    #['/usr/local/lib64/python3.7/site-packages/cv2']
    

    フォルダの中を確認してみますと、たくさんの分類器があることがわかります。今回はそのうち、「haarcascade_frontalface_default.xml」を使用していきます。

    cd /usr/local/lib64/python3.7/site-packages/cv2/data
    ls
    #__init__.py					haarcascade_frontalface_alt2.xml		haarcascade_profileface.xml
    #__pycache__					haarcascade_frontalface_alt_tree.xml		haarcascade_righteye_2splits.xml
    #haarcascade_eye.xml				haarcascade_frontalface_default.xml		haarcascade_russian_plate_number.xml
    #haarcascade_eye_tree_eyeglasses.xml		haarcascade_fullbody.xml			haarcascade_smile.xml
    #haarcascade_frontalcatface.xml			haarcascade_lefteye_2splits.xml			haarcascade_upperbody.xml
    #haarcascade_frontalcatface_extended.xml		haarcascade_licence_plate_rus_16stages.xml
    #haarcascade_frontalface_alt.xml			haarcascade_lowerbody.xml
    

    分類器のパスを間違えていると、以下のようなエラーが出ます。

    Traceback (most recent call last):
      File "[*.py]", line 6, in 
        rects = cascade.detectMultiScale(gray, 1.1, 3,0, (20,20))
    cv2.error: OpenCV(4.0.0) /io/opencv/modules/objdetect/src/cascadedetect.cpp:1658: error: (-215:Assertion failed) !empty() in function 'detectMultiScale'
    

    顔検出結果

    顔検出の結果は以下の通りです。画像は写真素材 足成からいただきました。

    元画像1

    顔検出結果1

    元画像2

    顔検出結果2

    元画像3

    顔検出結果3

    モザイク処理をする

    顔検出した箇所についてぼかしを入れることもできます。

    import cv2
    
    cascade = cv2.CascadeClassifier('/usr/local/lib64/python3.7/site-packages/cv2/data/haarcascade_frontalface_default.xml')
    img = cv2.imread('img/img.jpg')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    rects = cascade.detectMultiScale(gray, 1.1, 3,0, (20,20))
    
    if len(rects) > 0:
        for rect in rects:
            x, y, w, h = rect
    	face = img[y:y+h, x:x+w]
            dst = cv2.GaussianBlur(face, (25, 25), 0)
            img[y:y+h, x:x+w] = dst
    else:
        print("検出なし")
    
    cv2.imwrite('img/img_mosaic.jpg', img)
    

    モザイク処理結果1

    モザイク処理結果2

    モザイク処理結果3

    Python: OpenCVを使用してエッジを検出する

    画像からエッジを検出する代表的な手法として、Cannyエッジ検出というものがあります。OpenCVを使用すると、簡単にCannyエッジ検出を行うことができます。

    cv2.Canny(image, threshold1, threshold2)
    

    Cannyエッジ検出では、画像データに加えて、2つの引数threshold1とthreshold2が必須です。この2つの値はエッジ検出の際に以下のように使用されます。

    threshold2より大きい場合: エッジとして検出される
    threshold1より大きくthreshold2より小さい場合: エッジと隣接している場合には、エッジとして検出される
    threshold1より小さい場合: エッジとして検出されない

    エッジ検出結果

    元画像

    threshold1=100, threshold2=100

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.Canny(img, 100, 100)
    cv2.imwrite('img/img_canny.jpg', dst)
    

    結果

    少し線が多すぎる印象です。

    threshold1=100, threshold2=300

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.Canny(img, 100, 300)
    cv2.imwrite('img/img_canny.jpg', dst)
    

    結果

    いい感じにエッジが検出できました。

    threshold1=100, threshold2=600

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.Canny(img, 100, 600)
    cv2.imwrite('img/img_canny.jpg', dst)
    

    結果

    少し線が少ないでしょうか。

    threshold1=600, threshold2=600

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.Canny(img, 600, 600)
    cv2.imwrite('img/img_canny.jpg', dst)
    

    結果

    threshold1を上げると、線のつながりがなくなってしまいました。

    Python: OpenCVのGaussianBlurを使用して画像にぼかしをかける

    ガウシアンフィルタは、画像の平滑化に使われるフィルタの1つです。ガウス分布を利用して、注目画素からの距離に応じて近傍の画素値に重みをかけます。

    インストール

    OpenCVをまだインストールしていない場合には以下のコマンドでインストールできます。

    pip install opencv-python
    

    ぼかしをかける

    元画像

    カーネルサイズの調整

    カーネル=(1, 1)、標準偏差3

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.GaussianBlur(img, (1, 1), 3)
    cv2.imwrite('img/img_gauss.jpg', dst)
    

    結果

    カーネル=(5, 5)、標準偏差3

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.GaussianBlur(img, (5, 5), 3)
    cv2.imwrite('img/img_gauss.jpg', dst)
    

    結果

    カーネル=(15, 15)、標準偏差3

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.GaussianBlur(img, (15, 15), 3)
    cv2.imwrite('img/img_gauss.jpg', dst)
    

    結果

    カーネルサイズを大きくすると、ぼかしの具合が強くなります。

    標準偏差の調整

    カーネル=(15, 15)、標準偏差1

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.GaussianBlur(img, (15, 15), 1)
    cv2.imwrite('img/img_gauss.jpg', dst)
    

    結果

    カーネル=(15, 15)、標準偏差2

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.GaussianBlur(img, (15, 15), 2)
    cv2.imwrite('img/img_gauss.jpg', dst)
    

    結果

    カーネル=(15, 15)、標準偏差3

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.GaussianBlur(img, (15, 15), 3)
    cv2.imwrite('img/img_gauss.jpg', dst)
    

    結果

    標準偏差を大きくしていくと、ぼかしの具合が強くなります。

    カーネル=(15, 15)、標準偏差0

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.GaussianBlur(img, (15, 15), 0)
    cv2.imwrite('img/img_gauss.jpg', dst)
    

    結果

    標準偏差に0を指定すると、カーネルサイズから標準偏差が自動計算されます。特に細かな調整が必要なければ、標準偏差は0にしてカネールサイズだけ変更すれば十分かと思います。

    Python: OpenCVを使用して画像のサイズを変更する

    OpenCVのインストール

    pip install opencv-python
    

    リサイズ

    元の画像

    画像の倍率を指定してリサイズ

    import cv2
    
    img = cv2.imread('img/img.jpg')
    dst = cv2.resize(img, None, fx=0.5, fy=1.0)                                                                                     
    cv2.imwrite('img/img_resized.jpg', dst)
    

    実行結果

    画像のサイズを指定してリサイズ

    import cv2
    
    img = cv2.imread('img/img.jpg')                                                                                                 
    dst = cv2.resize(img, (300, 300))
    cv2.imwrite('img/img_resized.jpg', dst)
    

    実行結果