[PYTHON] Von der weißen Maske zur Monstermaske: Entwicklung einer serverlosen LINE-Fotoverarbeitungsanwendung auf AWS

0. Einführung

Schön dich kennenzulernen, ich bin Pong aus China. Ich bin ein neuer Ingenieur und arbeite am Nomura Research Institute. Ich bin immer noch nicht gut in Japanisch, also bitte vergib mir, wenn du seltsames Japanisch bekommst. Vielen Dank.

Während der Corona-Zeit gibt es zu viele weiße Masken für Reisefotos. Du kannst es nicht mehr aushalten, oder? Im Moment gibt es Schulungen für Neuankömmlinge, und ich habe dies zu einem Schulungsthema gemacht. Um dies zu lösen, haben wir eine Anwendung entwickelt, die die weiße Maske auf dem Foto in eine Monstermaske umwandelt. Basierend auf dem Konzept "den Arbeitsaufwand so weit wie möglich reduzieren" Es wurde als serverlose LINE-Fotoverarbeitungs-App unter Verwendung verschiedener AWS-Services entwickelt. ** Für diejenigen, die es satt haben, maskierte Fotos zu machen ** und ** für diejenigen, die sich für Serverless interessieren ** Bitte genießen Sie diesen Entwicklungsbericht.

1. 1. Warum diese App entwickeln?

Ich reiste mit ihr während der Sommerferien nach Choshi in Chiba. Ich spielte im Meer, kletterte auf den Leuchtturm und machte viele Erinnerungsfotos. Leider war der Protagonist des Fotos kein Mensch oder eine Landschaft, sondern eine weiße Maske. Auf den Fotos der Corona-Ära (Zeit) ist die Erscheinungsrate weißer Masken am höchsten und sie erscheinen überall. Als sie ein solches Bild sah, beschwerte sie sich, dass sie die weiße Maske nicht mehr sehen wollte. Warum also nicht die weiße Maske auf dem Bild in etwas anderes umwandeln?

So wie ich und sie Superheldenfilme lieben, liebe ich die Monstermaske (z. B. Batmans Monster Bane) darin. Wäre es nicht schön, wenn die weiße Maske zu einer Monstermaske würde?

※This work is a derivative of "[Bane](https://www.flickr.com/photos/istolethetv/30216006787/)"by[istolethetv](https://www.flickr.com/people/istolethetv/),usedunder[CCBY2.0](https://creativecommons.org/licenses/by/2.0/)

Deshalb bin ich auf die Idee gekommen und habe beschlossen, diese Fotoverarbeitungs-App zu entwickeln. Aber vor mir liegen drei Probleme.

Zunächst gibt es verschiedene Möglichkeiten für das Antragsformular. Web-App als Webseite? Eine iOS- oder Android-App nur für Smartphones? Sie müssen nicht nur die Backend-Verarbeitung, sondern auch die Frontend-Oberfläche entwerfen. In Anbetracht verschiedener Dinge halte ich die LINE-App für am besten geeignet. Es gibt drei Gründe:

  1. Einfach zu bedienen: Fast jeder hat LINE, und die LINE-App (Bot) ist sehr einfach zu senden und zu empfangen, und jeder kann sie verwenden.
  2. Weniger Arbeit: Für Web-Apps und iOS-Apps ist es ärgerlich, ehrlich zu sein, einschließlich des Interface-Designs. Aber mit der LINE-App müssen Sie nicht über sie nachdenken und es wird einfacher.
  3. Einfach zu teilen: Das Merkmal von SNS ist, dass es einfach zu teilen ist. Nicht nur die konvertierten Fotos, sondern auch diese App wird einfacher zu teilen sein.

Deshalb ** habe ich mich entschieden, die LINE App als Bewerbungsformular zu verwenden! ** ** **

Die nächste Herausforderung besteht darin, den Server einzurichten Wird es auf einer physischen Maschine wie Raspberry Pi gebaut? Verwenden Sie einen Cloud-Server wie AWS EC2? Darüber hinaus muss der Server später nicht nur erstellt, sondern auch gewartet werden. Ich möchte das nicht tun, weil ich faul bin und die Idee habe, "den Arbeitsaufwand so weit wie möglich zu reduzieren". .. .. Warum dann nicht ohne Server entwickeln, ohne einen Server zu benötigen? Nach meinen Recherchen ** Mit AWS API Gateway und Lambda kann Serverless erreicht werden, und es gibt kein Serverkonstruktions- und Wartungsmanagement **! Okay, ich habe mich für dich entschieden! !!

Schließlich benötigen wir diesmal eine Gesichtserkennungs-KI, um das Gesichtsfoto zu verarbeiten. Infolgedessen kamen immer mehr Fragen wie "Welche KI-Modellstruktur verwenden Sie?", "Woher erhalten Sie die Trainingsdaten?" Und "Welche Art von Etikett möchten Sie die Daten beschriften?". Ich dachte "Ich wünschte, ich hätte eine Gesichtserkennungs-KI, die sofort verwendet werden könnte", also habe ich sie bei AWS nachgeschlagen und die Ergebnisse kamen wirklich heraus! Es gibt einen AWS-Service, der Bilder oder Videos analysiert, der als Erkennung (nicht Erkennung) bezeichnet wird. ** Sie müssen keine KI erstellen, rufen Sie einfach Rekognition an, um das Gesicht auf dem Foto zu erkennen und zu analysieren **. Auf diese Weise können Sie "den Arbeitsaufwand so weit wie möglich reduzieren".

Vor diesem Hintergrund haben wir uns entschlossen, eine serverlose LINE-Fotoverarbeitungs-App für AWS zu entwickeln!

2. Übersicht über das System

Wir haben uns bereits für das Antragsformular entschieden, also bauen wir das System jetzt auf! Das Gesamtbild des diesmal erstellten Systems sieht wie folgt aus:

Hierbei wird davon ausgegangen, dass die Kommunikation mit dem Benutzer ein Smartphone ist. (PC-Version LINE ist ebenfalls verfügbar) Das Frontend ist LINE Bot. Alle Backends werden von AWS Cloud verarbeitet. Um Serverless zu realisieren, wird die Verarbeitung in drei Lambdas ausgeführt: "Controller", "Gesichtserkennung" und "Neue Bilderzeugung". In Anbetracht des Verarbeitungsablaufs kann dieses System in 5 Teile unterteilt werden, wie in der folgenden Abbildung dargestellt:

Dann werde ich diese fünf Teile aus dem Ablauf der Verarbeitung erklären.

3. 3. Erklärung für jedes Teil

3-1 Bildeingabeteil

Prozessablauf

Der erste Teil ist der Eingabeteil. Die Funktion besteht buchstäblich darin, das Bild zu laden, das der Benutzer an den LINE-Bot gesendet hat. Die mit diesem Teil verbundenen Entitäten sind "LINE Bot", "API Gateway" und "Controller Lambda". Der Verarbeitungsablauf ist wie folgt:

Zunächst sendet der Benutzer ein Foto an LINE Bot. Der LINE-Bot verpackt das Bild dann in line_event und sendet es an das API-Gateway. API Gateway sendet ein Ereignis ohne Änderungen an den Controller Lambda.

LINE Bot erstellen

Um diesen Teil zu erstellen, erstellen Sie zunächst einen LINE-Bot (messagingApi) als Eingangstür. Klicken Sie hier, um Folgendes zu erstellen: Offizielles LINE-Dokument: Erste Schritte mit der Messaging-API Nach dem Erstellen des Kanals sind noch zwei Einstellungen erforderlich. Die erste besteht darin, ein "Kanalzugriffstoken" zur Authentifizierung bei Lambda auszustellen. Die zweite besteht darin, die Antwortfunktion der Messaging-API auszuschalten und die Webhook-Funktion einzuschalten. Geben Sie jetzt nicht die Webhook-URL ein, sondern nachdem Sie das API-Gateway konfiguriert haben.

Erstellen Sie den Controller Lambda

Als Nächstes wird eine IAM-Rolle erstellt, in der Dienste wie Lambda ausgeführt werden. Geben Sie den IAM-Dienst über das Dashboard ein und erstellen Sie eine neue Rolle. Die neue IAM-Rolle nennt Serverless-Linebot usw. und der verwendete Dienst ist Lambda. Die Richtlinien lauten "Amazon S3FullAccess", "AmazonRekognitionFullAccess" und "CloudWatchLogsFullAccess". Da der Controller Lambda einen anderen Lambda aufruft, fügen Sie die folgende Richtlinie hinzu:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction",
                "lambda:InvokeAsync"
            ],
            "Resource": [
                "Gesichtserkennung Lambda arn",
                "Neue Bilderzeugung Lambda Arn"
            ]
        }
    ]
}

"Gesichtserkennung Lambda arn" und "Neue Bilderzeugung Lambda arn" sind noch nicht verfügbar. Vergessen Sie daher nicht, sie nach dem Erstellen der Lambda-Funktion neu zu schreiben. Die gesamte Verarbeitung wird in dieser Rolle ausgeführt.

Erstellen Sie die Lambda-Funktion des Controllers

Da API Gateway eine "Verbindung" ist, müssen wir den LINE Bot an beiden Enden und die Controller-Lambda-Funktion erstellen, bevor wir sie erstellen. Als Nächstes erstellen wir die Controller-Lambda-Funktion. Da dieses Mal Python für die Funktionserstellung verwendet wird, wählen Sie python3.x (3.6 ~ 3.8) als Laufzeit aus. Die auszuführende IAM-Rolle ist die, die Sie zuvor erstellt haben.

Stellen Sie nach dem Erstellen zunächst den Speicher auf 512 MB und das Zeitlimit in den "Grundeinstellungen" auf 1 Minute ein. Stellen Sie dann die folgenden Umgebungsvariablen ein:

Schlüssel Wert
LINE_CHANNEL_ACCESS_TOKEN LINE Bot Channel Access Token
LINE_CHANNEL_SECRET LINE Bot Channel Geheimnis

In Bezug auf den Inhalt der Lambda-Funktion kommuniziert der Controller Lambda mit dem LINE-Bot, sodass das Paket "line-bot-sdk" erforderlich ist. Um auf Lambda zu installieren, installieren Sie zuerst line-bot-sdk lokal im neuen Ordner mit dem folgenden Befehl:

python -m pip install line-bot-sdk -t <new_folder>

Erstellen Sie anschließend eine Datei lambda_function.py (Lambda erkennt dies als Hauptfunktion mit diesem Namen, benennen Sie ihn also unbedingt) im selben Ordner und geben Sie den folgenden Code ein:

lambda_function_for_controller.py


import os
import sys
import logging

import boto3
import json

from linebot import LineBotApi, WebhookHandler
from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageMessage, ImageSendMessage
from linebot.exceptions import LineBotApiError, InvalidSignatureError


logger = logging.getLogger()
logger.setLevel(logging.ERROR)

#Lesen Sie Zugriffstoken und Geheimnisse für Leitungsbotkanäle aus Umgebungsvariablen
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

# api&Handler generieren
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

#Mit S3-Schaufel verbinden
s3 = boto3.client("s3")
bucket = "<S3 Bucket Name>"

#Lambdas Hauptfunktion
def lambda_handler(event, context):

    #X zur Authentifizierung-Line-Signatur-Header
    signature = event["headers"]["X-Line-Signature"]

    body = event["body"]

    #Rückgabewert einstellen
    ok_json = {"isBase64Encoded": False,
               "statusCode": 200,
               "headers": {},
               "body": ""}
    error_json = {"isBase64Encoded": False,
                  "statusCode": 403,
                  "headers": {},
                  "body": "Error"}

    @handler.add(MessageEvent, message=ImageMessage)
    def message(line_event):

        #Benutzerprofil
        profile = line_bot_api.get_profile(line_event.source.user_id)

        #Extrahieren Sie die ID des Benutzers, der gesendet hat(push_Verwenden Sie die if-Nachricht,Für die Antwort nicht erforderlich)
        # user_id = profile.user_id

        #Nachrichten-ID extrahieren
        message_id = line_event.message.id

        #Bilddatei extrahieren
        message_content = line_bot_api.get_message_content(message_id)
        content = bytes()
        for chunk in message_content.iter_content():
            content += chunk

        #Bilddatei speichern
        key = "origin_photo/" + message_id
        new_key = message_id[-3:]
        s3.put_object(Bucket=bucket, Key=key, Body=content)

        #Rufen Sie das Gesichtserkennungs-Lambda an
        lambdaRekognitionName = "<Dies ist eine Art Gesichtserkennung Lambda>"
        params = {"Bucket": bucket, "Key": key}  #Informationen zum Bilddateipfad
        payload = json.dumps(params)
        response = boto3.client("lambda").invoke(
            FunctionName=lambdaRekognitionName, InvocationType="RequestResponse", Payload=payload)
        response = json.load(response["Payload"])

        #Rufen Sie das Lambda für die neue Bilderzeugung auf
        lambdaNewMaskName = "<Hier ist die neue Bilderzeugung Lambda Arn>"
        params = {"landmarks": str(response),
                  "bucket": bucket,
                  "photo_key": key,
                  "new_photo_key": new_key}
        payload = json.dumps(params)
        boto3.client("lambda").invoke(FunctionName=lambdaNewMaskName,
                                      InvocationType="RequestResponse", Payload=payload)

        #Signierte URL-Generierung
        presigned_url = s3.generate_presigned_url(ClientMethod="get_object", Params={
                                                  "Bucket": bucket, "Key": new_key}, ExpiresIn=600)

        #Auf neue Bildnachricht antworten
        line_bot_api.reply_message(line_event.reply_token, ImageSendMessage(
            original_content_url=presigned_url, preview_image_url=presigned_url))

    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.message))
        return error_json
    except InvalidSignatureError:
        return error_json

    return ok_json

Oben ist die gesamte Lambda-Reglerfunktion dargestellt, die allen fünf Teilen zugeordnet ist. Der Teil über diesen ersten Teil ist:

lambda_function_for_controller.py


#Lesen Sie Zugriffstoken und Geheimnisse für Leitungsbotkanäle aus Umgebungsvariablen
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

# api&Handler generieren
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

lambda_function_for_controller.py


    #X zur Authentifizierung-Line-Signatur-Header
    signature = event["headers"]["X-Line-Signature"]

    body = event["body"]

Sie haben jetzt Ihren LINE Bot authentifiziert und die Ereignisdetails erhalten. Danach komprimieren Sie den Inhalt dieses Ordners in eine Zip und Hochladen durch Auswahl von "Funktionscode" -> "Aktion" -> "ZIP-Datei hochladen" von Lambda.

API-Gateway erstellen

Das letzte ist die Erstellung von API Gateway als Verbindung. Der hier erstellte Typ des API-Gateways ist die REST-API. Erstellen Sie nach dem Erstellen der API die Ressourcen und Methoden. Die Methode ist die POST-Methode, der Integrationstyp ist die Lambda-Funktion und die Verwendung der Lambda-Proxy-Integration ist ebenfalls aktiviert. Die Lambda-Funktion wählt die Controller-Lambda-Funktion aus.

Auch über die Einstellung der POST-Methodenanforderung Wählen Sie zunächst "Abfragezeichenfolgenparameter und -header überprüfen" aus, um die Anforderung zu authentifizieren. Fügen Sie dem HTTP-Anforderungsheader den folgenden Header hinzu:

Name Verpflichtend Zwischenspeicher
X-Line-Signature

Einmal eingestellt, lassen Sie uns bereitstellen. Kopieren Sie nach Abschluss der Bereitstellung die Methodenaufruf-URL auf der Bühne und Fügen Sie es in die LINE Bot-Webhook-URL ein. Damit ist der erste Teil abgeschlossen.

3-2 Bildspeicherteil

Prozessablauf

Der zweite Teil ist der Bildspeicherteil. Dieser Teil ist sehr einfach. Speichern Sie einfach das vom Controller Lambda geladene Bild im S3-Bucket. Der Verarbeitungsablauf ist wie folgt:

Erstellen Sie einen S3-Bucket

Erstellen Sie zunächst einen S3-Bucket. Wenn der Bucket-Name in diesem Projekt zu lang ist, tritt ein "Problem mit der Länge der signierten URL" auf (Einzelheiten finden Sie unter [3-5](#signed url)). Machen Sie den Bucket-Namen so kurz wie möglich (in meinem Fall 4 englische Zeichen). Außerdem möchten Sie nicht, dass andere Ihr Bild sehen, oder? Um Ihre Privatsphäre zu schützen Aktivieren Sie in den Berechtigungseinstellungen "Alle öffentlichen Zugriffe blockieren", um einen Bucket zu erstellen. Nach dem Erstellen eines Ordners mit dem Namen "origin_photo" zum Speichern der vom Benutzer hochgeladenen Fotos. Erstellen Sie einen Ordner mit dem Namen "Masken", um die Maskenbilder zu speichern. Damit ist die Arbeit auf der S3-Seite abgeschlossen.

Controller Lambda-Funktion

Die Controller-Lambda-Funktion wurde im ersten Teil ausgefüllt, daher gibt es hier nichts Besonderes zu tun. Erklären Sie einfach den Code für diesen Teil und der Inhalt ist:

lambda_function_for_controller.py


#Mit S3-Schaufel verbinden
s3 = boto3.client("s3")
bucket = "<S3 Bucket Name>"

lambda_function_for_controller.py


        #Nachrichten-ID extrahieren
        message_id = line_event.message.id

        #Bilddatei extrahieren
        message_content = line_bot_api.get_message_content(message_id)
        content = bytes()
        for chunk in message_content.iter_content():
            content += chunk

        #Bilddatei speichern
        key = "origin_photo/" + message_id
        new_key = message_id[-3:]
        s3.put_object(Bucket=bucket, Key=key, Body=content)

Benennen Sie hier die Bilddatei mit der LINE-Nachrichten-ID um. Mehrere Benutzer können unterscheiden.

3-3 Gesichtserkennungsteil

Der dritte Teil ist die Erkennung gespeicherter Fotos. Insbesondere erkennt es die Kontur des Gesichts und die Positionen der Augen und der Nase und verwendet sie später zum Kombinieren mit dem Maskenbild. Mit dem Konzept "den Arbeitsaufwand so weit wie möglich reduzieren" Ich möchte die Gesichtserkennungs-KI nicht von Grund auf neu trainieren Gesichter werden mithilfe eines Dienstes namens "Erkennung" in AWS erkannt.

Was ist Anerkennung?

Rekognition ist ein Dienst, der "maschinelles Lernen verwendet, um die Analyse von Bildern und Videos zu automatisieren". Einfach ausgedrückt, es fühlt sich an wie "Verwenden Sie die trainierte KI so wie sie ist". Hier ist eine Einführung in die Anerkennung: Amazon Rekognition

Die Erkennung hat verschiedene Funktionen wie Objekt- und Szenenerkennung und Gesichtsvergleich und kann nicht nur Bilder, sondern auch Videos verarbeiten. Dieses Mal verwenden wir die Funktion "Gesichtserkennung", um die Position des Gesichts zu ermitteln. Die Standortinformationen, die Sie erhalten möchten, werden als "Orientierungspunkt" bezeichnet. Die folgende Abbildung zeigt ein Wahrzeichen:

Analyseergebnis dieser Abbildung:

Erkennungserkennungsergebnis
{
    "FaceDetails": [
        {
            "AgeRange": {
                "High": 43,
                "Low": 26
            },
            "Beard": {
                "Confidence": 97.48941802978516,
                "Value": true
            },
            "BoundingBox": {
                "Height": 0.6968063116073608,
                "Left": 0.26937249302864075,
                "Top": 0.11424895375967026,
                "Width": 0.42325547337532043
            },
            "Confidence": 99.99995422363281,
            "Emotions": [
                {
                    "Confidence": 0.042965151369571686,
                    "Type": "DISGUSTED"
                },
                {
                    "Confidence": 0.002022328320890665,
                    "Type": "HAPPY"
                },
                {
                    "Confidence": 0.4482877850532532,
                    "Type": "SURPRISED"
                },
                {
                    "Confidence": 0.007082826923578978,
                    "Type": "ANGRY"
                },
                {
                    "Confidence": 0,
                    "Type": "CONFUSED"
                },
                {
                    "Confidence": 99.47616577148438,
                    "Type": "CALM"
                },
                {
                    "Confidence": 0.017732391133904457,
                    "Type": "SAD"
                }
            ],
            "Eyeglasses": {
                "Confidence": 99.42405700683594,
                "Value": false
            },
            "EyesOpen": {
                "Confidence": 99.99604797363281,
                "Value": true
            },
            "Gender": {
                "Confidence": 99.722412109375,
                "Value": "Male"
            },
            "Landmarks": [
                {
                    "Type": "eyeLeft",
                    "X": 0.38549351692199707,
                    "Y": 0.3959200084209442
                },
                {
                    "Type": "eyeRight",
                    "X": 0.5773905515670776,
                    "Y": 0.394561767578125
                },
                {
                    "Type": "mouthLeft",
                    "X": 0.40410104393959045,
                    "Y": 0.6479480862617493
                },
                {
                    "Type": "mouthRight",
                    "X": 0.5623446702957153,
                    "Y": 0.647117555141449
                },
                {
                    "Type": "nose",
                    "X": 0.47763553261756897,
                    "Y": 0.5337067246437073
                },
                {
                    "Type": "leftEyeBrowLeft",
                    "X": 0.3114689588546753,
                    "Y": 0.3376390337944031
                },
                {
                    "Type": "leftEyeBrowRight",
                    "X": 0.4224424660205841,
                    "Y": 0.3232649564743042
                },
                {
                    "Type": "leftEyeBrowUp",
                    "X": 0.36654090881347656,
                    "Y": 0.3104579746723175
                },
                {
                    "Type": "rightEyeBrowLeft",
                    "X": 0.5353175401687622,
                    "Y": 0.3223199248313904
                },
                {
                    "Type": "rightEyeBrowRight",
                    "X": 0.6546239852905273,
                    "Y": 0.3348073363304138
                },
                {
                    "Type": "rightEyeBrowUp",
                    "X": 0.5936762094497681,
                    "Y": 0.3080498278141022
                },
                {
                    "Type": "leftEyeLeft",
                    "X": 0.3524211347103119,
                    "Y": 0.3936865031719208
                },
                {
                    "Type": "leftEyeRight",
                    "X": 0.4229775369167328,
                    "Y": 0.3973258435726166
                },
                {
                    "Type": "leftEyeUp",
                    "X": 0.38467878103256226,
                    "Y": 0.3836822807788849
                },
                {
                    "Type": "leftEyeDown",
                    "X": 0.38629674911499023,
                    "Y": 0.40618783235549927
                },
                {
                    "Type": "rightEyeLeft",
                    "X": 0.5374732613563538,
                    "Y": 0.39637991786003113
                },
                {
                    "Type": "rightEyeRight",
                    "X": 0.609208345413208,
                    "Y": 0.391626238822937
                },
                {
                    "Type": "rightEyeUp",
                    "X": 0.5750962495803833,
                    "Y": 0.3821527063846588
                },
                {
                    "Type": "rightEyeDown",
                    "X": 0.5740782618522644,
                    "Y": 0.40471214056015015
                },
                {
                    "Type": "noseLeft",
                    "X": 0.4441811740398407,
                    "Y": 0.5608476400375366
                },
                {
                    "Type": "noseRight",
                    "X": 0.5155643820762634,
                    "Y": 0.5569332242012024
                },
                {
                    "Type": "mouthUp",
                    "X": 0.47968366742134094,
                    "Y": 0.6176465749740601
                },
                {
                    "Type": "mouthDown",
                    "X": 0.4807897210121155,
                    "Y": 0.690782368183136
                },
                {
                    "Type": "leftPupil",
                    "X": 0.38549351692199707,
                    "Y": 0.3959200084209442
                },
                {
                    "Type": "rightPupil",
                    "X": 0.5773905515670776,
                    "Y": 0.394561767578125
                },
                {
                    "Type": "upperJawlineLeft",
                    "X": 0.27245330810546875,
                    "Y": 0.3902156949043274
                },
                {
                    "Type": "midJawlineLeft",
                    "X": 0.31561678647994995,
                    "Y": 0.6596118807792664
                },
                {
                    "Type": "chinBottom",
                    "X": 0.48385748267173767,
                    "Y": 0.8160444498062134
                },
                {
                    "Type": "midJawlineRight",
                    "X": 0.6625112891197205,
                    "Y": 0.656606137752533
                },
                {
                    "Type": "upperJawlineRight",
                    "X": 0.7042999863624573,
                    "Y": 0.3863988518714905
                }
            ],
            "MouthOpen": {
                "Confidence": 99.83820343017578,
                "Value": false
            },
            "Mustache": {
                "Confidence": 72.20288848876953,
                "Value": false
            },
            "Pose": {
                "Pitch": -4.970901966094971,
                "Roll": -1.4911699295043945,
                "Yaw": -10.983647346496582
            },
            "Quality": {
                "Brightness": 73.81391906738281,
                "Sharpness": 86.86019134521484
            },
            "Smile": {
                "Confidence": 99.93638610839844,
                "Value": false
            },
            "Sunglasses": {
                "Confidence": 99.81478881835938,
                "Value": false
            }
        }
    ]
}

Was ich dieses Mal bekommen möchte, ist das "Wahrzeichen" -Element in diesem. "Typ" ist der Name des Punktes (siehe Bild oben). X und y sind jedoch nicht die Koordinaten bestimmter Pixelpunkte. Zeigt das Verhältnis zur Breite des Bildes an.

Prozessablauf

Der Verarbeitungsablauf des dritten Teils ist wie folgt: Die Erkennung hat zwei Mechanismen zum Lesen von Bildern. Das erste ist das Laden mit dem S3-Bucket oder der Bild-URL im Internet. Die zweite besteht darin, die Datei zu senden und direkt zu lesen. Dieses Mal verwenden wir die erste URL-Methode. Daher wird nicht das Bild vom Controller Lambda an das Gesichtserkennungs-Lambda übergeben, sondern die Speicherortinformationen der Datei. Gleiches gilt für die Gesichtserkennung, die Lambda an Rekognition weitergibt.

Die IAM-Rolle, die hier die Gesichtserkennung Lambda ausführt, ist die im ersten Teil erstellte Rolle. Ich habe die Berechtigung, S3 und Rekognition zu verwenden Selbst wenn der S3-Bucket privat ist, kann Rekognition die darin enthaltenen Bilder lesen und es gibt kein Problem.

Und das von Rekognition zurückgegebene Ergebnis scheint ein Beispiel für das obige Ergebnis zu sein. Es gibt verschiedene Dinge wie "Alter" und "Geschlecht" darin, Ich möchte diesmal nur "Orientierungspunkte" verwenden. Daher extrahiert die Gesichtserkennung Lambda Orientierungspunkte aus den Ergebnissen.

Es gibt auch viele Sehenswürdigkeiten, Es gibt einige Punkte (Mund usw.), die aufgrund der Maske nicht gut erkannt werden können, und es gibt einige zusätzliche Punkte (Augen usw.), die zu fein sind. Daher werden nur die folgenden 5 Orientierungspunkte extrahiert und an den Controller Lambda zurückgegeben.

Name des Wahrzeichens Position
eyeLeft linkes Auge
eyeRight rechtes Auge
upperJawlineLeft Verließ Komekami
upperJawlineRight Richtiger Reis
chinBottom Kinn

Erstellen Sie eine Lambda-Funktion zur Gesichtserkennung

Um die Rollen zu trennen, erstellen Sie zusätzlich zur Lambda-Funktion des Controllers eine weitere Lambda-Funktion zur Gesichtserkennung. Beim Erstellen, genau wie bei der Controller-Lambda-Funktion, Wählen Sie python3.x aus und die Ausführungsrolle ist dieselbe. Stellen Sie das Zeitlimit von 1 Minute und den Speicher von 512 MB auf die gleiche Weise in den "Grundeinstellungen" ein.

Nach dem Erstellen gibt es hier kein Paket, das vorgestellt werden kann Keine Notwendigkeit, zip hochzuladen, Sie müssen lediglich den unten generierten automatisch generierten Code Lambda_function.py eingeben.

lambda_function_for_rekognition.py


import json
import boto3

rekognition = boto3.client("rekognition")


def lambda_handler(event, context):

    #Ruft den Pfad der Bilddatei vom Ereignis ab
    bucket = event["Bucket"]
    key = event["Key"]

    #Rufen Sie die Erkennung an, um die Gesichtserkennung durchzuführen
    response = rekognition.detect_faces(
        Image={'S3Object': {'Bucket': bucket, 'Name': key}}, Attributes=['ALL'])

    #Wie viele Personen sind auf dem Foto
    number_of_people = len(response["FaceDetails"])

    #Erstellen Sie eine Liste aller erforderlichen Orientierungspunkte
    all_needed_landmarks = []
    #Prozess nach Anzahl der Personen
    for i in range(number_of_people):
        #Dies ist eine Liste von Wörterbüchern
        all_landmarks_of_one_person = response["FaceDetails"][i]["Landmarks"]
        #Diesmal eyeLeft, eyeRight, upperJawlineLeft, upperJawlineRight,Verwenden Sie nur chinBottom
        # needed_Auszug zu Sehenswürdigkeiten
        needed_landmarks = []
        for type in ["eyeLeft", "eyeRight", "upperJawlineLeft", "upperJawlineRight", "chinBottom"]:
            landmark = next(
                item for item in all_landmarks_of_one_person if item["Type"] == type)
            needed_landmarks.append(landmark)
        all_needed_landmarks.append(needed_landmarks)

    return all_needed_landmarks

Controller Lambda-Funktion

Da die Lambda-Funktion des Controllers bereits ausgefüllt wurde, Dies ist nur eine Beschreibung des Codes für Teil 3.

lambda_function_for_controller.py


        lambdaRekognitionName = "<Dies ist eine Art Gesichtserkennung Lambda>"
        params = {"Bucket": bucket, "Key": key}  #Informationen zum Bilddateipfad
        payload = json.dumps(params)
        response = boto3.client("lambda").invoke(
            FunctionName=lambdaRekognitionName, InvocationType="RequestResponse", Payload=payload)
        response = json.load(response["Payload"])

3-4 Neuer Teil der Bilderzeugung

Prozessablauf

Der vierte Teil ist der neue Teil der Bilderzeugung. Mit anderen Worten, es ist der Teil, der das fotografische Bild und das folgende neue Maskenbild kombiniert:

Name Bane Joker Immortan Joe
Maskenbild
※1

※2

※3
Quelle Dunkle Nacht steigt auf dunklerRitter MadMaxAngryDeathRoad

※1:This work is a derivative of "Bane"byistolethetv,usedunderCCBY2.0. ※2:This work is a derivative of this photo,usedunderCC01.0. ※3:This work, "joe's mask" is a derivative of "File:Fan_Expo_2015_-Immortan_Joe(21147179383).jpg"byGabboT,usedunderCCBY-SA2.0."joe'smask"islicensedCCBY-SA2.0 by y2-peng.

Der Verarbeitungsablauf in AWS ist wie folgt:

※This work is a derivative of "[Bane](https://www.flickr.com/photos/istolethetv/30216006787/)"by[istolethetv](https://www.flickr.com/people/istolethetv/),usedunder[CCBY2.0](https://creativecommons.org/licenses/by/2.0/).

Das ist alles für die Verarbeitung.

Erstellen Sie eine neue Bilderzeugung Lambda

Erstellen Sie zunächst eine neue Lambda-Funktion in AWS Lambda. Die Laufzeit- und Ausführungsrollen sind dieselben wie zuvor. Stellen Sie außerdem wie zuvor den Speicher und das Timeout unter "Grundeinstellungen" ein.

Diesmal erfordert die Bildkombination zwei Python-Pakete, "Pillow" und "Numpy". Erstellen Sie daher zuerst einen neuen Ordner und installieren Sie das Paket mit dem folgenden Befehl.

python -m pip install pillow numpy -t <new_folder>

Erstellen Sie dann "lambda_function.py" in diesem Ordner und geben Sie den folgenden Code ein.

lambda_function_for_new_image_gengeration.py


import json
import boto3

import numpy as np

from PIL import Image, ImageFile
from operator import sub
from io import BytesIO
from random import choice

s3 = boto3.client("s3")


class NewPhotoMaker:
    def __init__(self, all_landmarks, bucket, photo_key, new_photo_key):
        self.all_landmarks = eval(all_landmarks)
        self.bucket = bucket
        self.photo_key = photo_key
        self.new_photo_key = new_photo_key

    #Fotobild laden
    def load_photo_image(self):
        s3.download_file(self.bucket, self.photo_key, "/tmp/photo_file")
        self.photo_image = Image.open("/tmp/photo_file")

    #Laden Sie das Maskenbild
    def load_mask_image(self):
        #Fluch (Batman),Joker (Batman),Zufällige Auswahl von Immortan Joe (Mad Max)
        mask_key = "masks/" + choice(["bane", "joker", "joe"]) + ".png "
        s3.download_file(self.bucket, mask_key, "/tmp/mask_file")
        self.mask_image = Image.open("/tmp/mask_file")

    #Wechseln Sie von einem Orientierungspunkt (Verhältnis) zu einem bestimmten Punkt
    def landmarks_to_points(self):
        upperJawlineLeft_landmark = next(
            item for item in self.landmarks if item["Type"] == "upperJawlineLeft")
        upperJawlineRight_landmark = next(
            item for item in self.landmarks if item["Type"] == "upperJawlineRight")
        eyeLeft_landmark = next(
            item for item in self.landmarks if item["Type"] == "eyeLeft")
        eyeRight_landmark = next(
            item for item in self.landmarks if item["Type"] == "eyeRight")

        self.upperJawlineLeft_point = [int(self.photo_image.size[0] * upperJawlineLeft_landmark["X"]), 
                                       int(self.photo_image.size[1] * upperJawlineLeft_landmark["Y"])]
        self.upperJawlineRight_point = [int(self.photo_image.size[0] * upperJawlineRight_landmark["X"]), 
                                        int(self.photo_image.size[1] * upperJawlineRight_landmark["Y"])]
        self.eyeLeft_point = [int(self.photo_image.size[0] * eyeLeft_landmark["X"]),
                              int(self.photo_image.size[1] * eyeLeft_landmark["Y"])]
        self.eyeRight_point = [int(self.photo_image.size[0] * eyeRight_landmark["X"]),
                               int(self.photo_image.size[1] * eyeRight_landmark["Y"])]

    #Passen Sie die Größe des Maskenbilds an Ihre Gesichtsbreite an
    def resize_mask(self):
        face_width = int(np.linalg.norm(list(map(sub, self.upperJawlineLeft_point, self.upperJawlineRight_point))))
        new_hight = int(self.mask_image.size[1]*face_width/self.mask_image.size[0])
        self.mask_image = self.mask_image.resize((face_width, new_hight))

    #Drehen Sie das Maskenbild entsprechend dem Gesichtswinkel (nicht die diagonale Fläche aufgrund der Drehung des Halses).
    def rotate_mask(self):
        angle = np.arctan2(self.upperJawlineRight_point[1] - self.upperJawlineLeft_point[1],
                           self.upperJawlineRight_point[0] - self.upperJawlineLeft_point[0])
        angle = -np.degrees(angle)  # radian to dgree
        self.mask_image = self.mask_image.rotate(angle, expand=True)

    #Kombinieren Sie fotografisches Bild und Maskenbild
    def match_mask_position(self):
        #Matching mit Augenposition
        face_center = [int((self.eyeLeft_point[0] + self.eyeRight_point[0])/2),
                       int((self.eyeLeft_point[1] + self.eyeRight_point[1])/2)]
        mask_center = [int(self.mask_image.size[0]/2),
                       int(self.mask_image.size[1]/2)]
        x = face_center[0] - mask_center[0]
        y = face_center[1] - mask_center[1]
        self.photo_image.paste(self.mask_image, (x, y), self.mask_image)

    #Speichern Sie die neue Bilddatei in S3
    def save_new_photo(self):
        new_photo_byte_arr = BytesIO()
        self.photo_image.save(new_photo_byte_arr, format="JPEG")
        new_photo_byte_arr = new_photo_byte_arr.getvalue()
        s3.put_object(Bucket=self.bucket, Key=self.new_photo_key,
                      Body=new_photo_byte_arr)

    #Lauf
    def run(self):

        self.load_photo_image()

        #Verarbeitung für die Anzahl der Personen
        for i in range(len(self.all_landmarks)):
            self.load_mask_image()  #Laden Sie jedes Mal eine neue Maske
            self.landmarks = self.all_landmarks[i]
            self.landmarks_to_points()
            self.resize_mask()
            self.rotate_mask()
            self.match_mask_position()

        self.save_new_photo()

#Lambda Hauptfunktion


def lambda_handler(event, context):
    landmarks = event["landmarks"]
    bucket = event["bucket"]
    photo_key = event["photo_key"]
    new_photo_key = event["new_photo_key"]

    photo_maker = NewPhotoMaker(landmarks, bucket, photo_key, new_photo_key)
    photo_maker.run()

Zum Schluss komprimieren Sie den gesamten Inhalt des Ordners und laden ihn auf Lambda hoch. Damit ist die Erstellung der neuen Bilderzeugung abgeschlossen.

Controller Lambda-Funktion

Der Code für die Steuerung Lambda für diesen Teil lautet:

lambda_function_for_controller.py


        #Rufen Sie das Lambda für die neue Bilderzeugung auf
        lambdaNewMaskName = "<Hier ist die neue Bilderzeugung Lambda Arn>"
        params = {"landmarks": str(response),
                  "bucket": bucket,
                  "photo_key": key,
                  "new_photo_key": new_key}
        payload = json.dumps(params)
        boto3.client("lambda").invoke(FunctionName=lambdaNewMaskName,
                                      InvocationType="RequestResponse", Payload=payload)

3-5 Neuer Bildausgabeteil

Bildausgabe auf LINE Bot

Der letzte Teil ist der Ausgabeteil des neuen Bildes. Diese App verwendet LINE Bot zum Eingeben und Ausgeben von Bildern. Bei der Eingabe wird die Bilddatei direkt übergeben. Die Ausgabe kann die Bilddatei nicht direkt senden.

Bildnachrichtendokument in der LINE Bot Messageing-API ist eine Bildübertragungsmethode für den Benutzer. Ist festgelegt. Es ist nicht die Bilddatei, die die API empfängt, sondern die URL des Bildes. Gemäß der Dokumentation erfolgt die Kommunikation zwischen dem Benutzer und dem LINE-Bot über die LINE-Plattform. ](Https://developers.line.biz/en/docs/messaging-api/overview/#how-it-works "wie es funktioniert") So ist dieser Übertragungsprozess

  1. "Bild-URL vom LINE-Bot an die LINE-Plattform senden"
  2. "LINE-Plattform lädt Bilder, die im S3-Bucket gespeichert sind"
  3. "Die LINE-Plattform sendet Bilder an Benutzer"

Es ist geworden. Dieser Prozess macht jedoch ** S3-Bucket-Zugriffsrechte zu einem Problem **. Wenn das Zugriffsrecht auf "privat" gesetzt ist, kann die LINE-Plattform das Bild nicht lesen, und das vom Benutzer angegebene Bild sieht folgendermaßen aus: Wenn das Zugriffsrecht auf "öffentlich" gesetzt ist, kann jeder darauf zugreifen, indem er die S3-Objekt-URL des Bildes kennt. Dies bedeutet, dass Ihre Fotos von anderen gesehen werden können, was ein Datenschutzproblem darstellt.

Vorerst habe ich darüber nachgedacht, DynamoDB usw. zur Authentifizierung von LINE-Benutzern zu verwenden. Der Arbeitsaufwand hat erheblich zugenommen und kollidiert mit dem Konzept, "den Arbeitsaufwand so weit wie möglich zu reduzieren". Um ehrlich zu sein, ich möchte es nicht tun.

Nach vielen Recherchen habe ich endlich einen guten Weg gefunden. Es ist eine "signierte URL".

Signierte URL

Um Ihre Privatsphäre zu schützen, machen Sie den Zugriff auf den S3-Bucket "privat". Selbst wenn ich die S3-Objekt-URL des Bildes kenne, kann ich nicht darauf zugreifen. Wenn Sie jedoch die signierte URL verwenden, die mit der Berechtigung der IAM-Rolle ausgestellt wurde, ist sie privat. Der Zugriff auf bestimmte Objekte im S3-Bucket ist möglich. Es sieht aus wie eine Konferenz-URL mit einem Zoom-Passwort.

Sie können auch ein Ablaufdatum für diese signierte URL festlegen. Wenn es abläuft, wird die URL unbrauchbar, was sie einen Schritt sicherer macht: Zu beachten ist jedoch die Länge der signierten URL. Die signierte URL, die mit der Berechtigung der IAM-Rolle ausgestellt wurde, enthält Token-Informationen für den temporären Zugriff, sodass die URL ziemlich lang ist. Gemäß den Regeln der Image Message API von LINE Bot kann jedoch maximal 1000 Zeichen URL empfangen werden. Wenn der Name des S3-Buckets, der Pfad der Bilddatei und der Name der Bilddatei zu lang sind, überschreitet die URL daher 1000 Zeichen und kann nicht gesendet werden. Als ich den S3-Bucket für den zweiten Teil erstellte, sagte ich manchmal: "Der Bucket-Name sollte so kurz wie möglich sein." Aus dem gleichen Grund sollte der neue Bilddateiname die letzten 3 Zeichen der Nachrichten-ID sein (verkürzen Sie den Dateinamen). Ich speichere auch die neue Bilddatei im Rollenordner des S3-Buckets (verkürzen Sie den Dateipfad). Dies löste das Problem mit der Länge der signierten URL.

Ergänzung: Es gibt tatsächlich eine andere Lösung für das Problem der Länge der signierten URL. Es wird die URL mit den Berechtigungen des IAM-Benutzers veröffentlicht, nicht mit der IAM-Rolle. Von IAM-Benutzern ausgegebene URLs erfordern keine Token und können gekürzt werden. Sie müssen die "Access Key ID" und den "Secret Access Key" des IAM-Benutzers verwenden. Aus Sicherheitsgründen empfehlen wir nicht, URLs als IAM-Benutzer auszugeben.

Prozessablauf

Nachdem wir das Problem mit den S3-Bucket-Berechtigungen behoben haben, implementieren wir diesen Teil. Der Ablauf dieses Teils ist wie folgt:

Zunächst übergibt die Controller-Lambda-Funktion die signierte URL des neuen Bildes an LINE Bot. Anschließend liest LINE Bot die Bilddatei aus dem S3-Bucket (das eigentliche Lesen erfolgt auf der LINE-Plattform). An den letzten Benutzer senden. Dies ist das Ende des Prozesses.

Controller Lambda-Funktion

Ähnlich wie im obigen Teil werde ich den Lambda-Funktionscode des Controllers für diesen Teil erläutern.

lambda_function_for_controller.py


        #Signierte URL-Generierung
        presigned_url = s3.generate_presigned_url(ClientMethod="get_object", Params={
                                                  "Bucket": bucket, "Key": new_key}, ExpiresIn=600)

lambda_function_for_controller.py


        #Auf neue Bildnachricht antworten
        line_bot_api.reply_message(line_event.reply_token, ImageSendMessage(
            original_content_url=presigned_url, preview_image_url=presigned_url))

4. Tatsächliche Ergebnis

Probieren wir die App aus, die wir gemacht haben!

Schnittstelle

Das erste ist das Senden und Empfangen über die LINE-Schnittstelle. Es gibt einen QR-Code für Bot aus den "Messaging-API-Einstellungen" von LINE Bot, mit dem Sie ihn Ihren Freunden hinzufügen können. Ich werde es später senden. .. .. ※This work, "wearing joe's mask" is a derivative of "File:Fan_Expo_2015_-Immortan_Joe(21147179383).jpg"byGabboT,usedunderCCBY-SA2.0."wearingjoe'smask"islicensedCCBY-SA2.0 by y2-peng.

Du hast es geschafft! Lassen Sie uns nun herausfinden, welche Muster funktionieren und welche nicht!

Erfolgreiches Muster

description before after
1 Person vorne IMG_1593.jpg IMG_1603.JPG※1
1 Person vorne (mit Rotation) IMG_1597.jpg IMG_1604.JPG※2
Vor mehreren Personen 1.jpg 2.jpg※3
Auch wenn das Gesicht zu groß ist IMG_1606.jpg IMG_1608.JPG※4

※1:This work is a derivative of this photo,usedunderCC01.0. ※2:This work, "result 2" is a derivative of "File:Fan_Expo_2015_-Immortan_Joe(21147179383).jpg"byGabboT,usedunderCCBY-SA2.0."result2"islicensedCCBY-SA2.0 by y2-peng. ※3:This work, "masked 4" is a derivative of "File:Fan_Expo_2015_-Immortan_Joe(21147179383).jpg" by GabboT, used under CC BY-SA 2.0, "Bane"byistolethetv,usedunderCCBY2.0, and this photo,usedunderCC01.0. "masked 4" is licensed CC BY-SA 2.0 by y2-peng. ※4:This work is a derivative of "Bane"byistolethetv,usedunderCCBY2.0.

Muster, die nicht funktionieren

description before after
Diagonale Fläche 3.jpg 4.jpg※1
Gesicht zu klein (die Person hinten) 5.jpg 6.jpg※2
Unschärfe (die Person dahinter) 7.jpg 8.jpg※3

※1:This work, "standing 2" is a derivative of "File:Fan_Expo_2015_-Immortan_Joe(21147179383).jpg" by GabboT, used under CC BY-SA 2.0 and "Bane"byistolethetv,usedunderCCBY2.0. "standing 2" is licensed CC BY-SA 2.0 by y2-peng. ※2:This work, "standing 4" is a derivative of "File:Fan_Expo_2015_-Immortan_Joe(21147179383).jpg" by GabboT, used under CC BY-SA 2.0 and "Bane"byistolethetv,usedunderCCBY2.0. "standing 4" is licensed CC BY-SA 2.0 by y2-peng. ※3:This work is a derivative of "Bane"byistolethetv,usedunderCCBY2.0.

Analyse

Je nach Ergebnis kann die Verarbeitung grob durchgeführt werden, wenn sie vorne und klar ist. Wenn es eine Unschärfe gibt, kann das Gesicht nicht erkannt werden und die Verarbeitung wird nicht durchgeführt. Wenn das schräge Gesicht oder die schräge Fläche zu klein ist, wird es verarbeitet, aber das Ergebnis ist nicht korrekt.

5. Zusammenfassung und Eindrücke

Zusammenfassung

Dieses Mal haben wir eine LINE-Anwendung entwickelt, die die weiße Maske auf dem Foto in eine Monstermaske verwandelt. Durch die Nutzung von AWS-Diensten war es möglich, diese ohne Server zu realisieren, und wir konnten das Konzept der "Reduzierung des Arbeitsaufwands so weit wie möglich" gründlich umsetzen. Wenn das Foto vorne klar ist, ist der Konvertierungsprozess im Allgemeinen in Ordnung. Die Verarbeitung von diagonalen und unscharfen Gesichtern wird jedoch ein Problem für die Zukunft sein.

Zukünftige Aufgaben

  1. Diagonale Fläche: Derzeit ist die Behandlung von diagonalen Flächen falsch. Der Grund ist, dass die Maske nur für die Vorderseite ist, nicht für die diagonale Fläche. Als zukünftige Lösung denke ich daran, die 2D-Maske in das 3D-Koordinatensystem zu drehen und dann zu kombinieren oder ein Maskenbild für diagonale Flächen vorzubereiten.
  2. Gesicht zu klein oder verschwommen: Die aktuelle Gesichtserkennung verwendet AWS Rekognition und legt mit ihrer Leistung die Obergrenze für die Leistung dieser App fest. Wenn ich selbst ein genaueres Gesichtserkennungssystem entwickeln könnte, könnte ich dieses Problem lösen. (Aber es ist ein Konflikt mit "den Arbeitsaufwand so weit wie möglich reduzieren" :()
  3. Maskenauswahl: Derzeit verwende ich die Phantommaske zufällig unter den drei, möchte sie aber in Zukunft erhöhen. Außerdem möchte ich Benutzern die Auswahl ermöglichen, nicht nur die zufällige Auswahl. Kennzeichnen Sie die Maske so, dass sie alle Anforderungen des Benutzers erfüllt, z. B. "Ich möchte eine XX-Maske tragen" und "Ich möchte eine süße Maske".

Andere Eindrücke

  1. Bequemlichkeit von Serverless: Was ich diesmal am meisten empfand, war der Charme von Serverless. Wenn Sie einen Server haben, ist nicht nur die Erstellung der Umgebung, sondern auch das Wartungsmanagement erforderlich, was viel Zeit in Anspruch nimmt. Die serverlose Entwicklung konnte diese jedoch überspringen und Zeit sparen. Es kann für die agile Entwicklung verwendet werden. Die serverlose Verarbeitung in Lambd weist jedoch Leistungseinschränkungen auf. Wenn Sie also eine komplizierte Verarbeitung haben, starten wir den Server.
  2. AWS Ein Jahr kostenlos am besten! !! : Neue AWS-Konten haben eine einjährige "kostenlose Stufe" und jede Nutzung innerhalb eines bestimmten Bereichs ist kostenlos. Das für diese Entwicklung verwendete Lambda, API Gateway, S3, Rekognition und Cloudwatch wurden alle für 0 Yen hergestellt, was sehr viel war. Ich möchte in der freien Zeit der verbleibenden Monate verschiedene Dinge ausprobieren. Wenn Sie interessiert sind, tun Sie bitte! Es ist kostenlos!

6. Alle Codes

lambda_function_for_controller.py

lambda_function_for_controller.py


import os
import sys
import logging

import boto3
import json

from linebot import LineBotApi, WebhookHandler
from linebot.models import MessageEvent, TextMessage, TextSendMessage, ImageMessage, ImageSendMessage
from linebot.exceptions import LineBotApiError, InvalidSignatureError


logger = logging.getLogger()
logger.setLevel(logging.ERROR)

#Lesen Sie Zugriffstoken und Geheimnisse für Leitungsbotkanäle aus Umgebungsvariablen
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

# api&Handler generieren
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

#Mit S3-Schaufel verbinden
s3 = boto3.client("s3")
bucket = "<S3 Bucket Name>"

#Lambdas Hauptfunktion
def lambda_handler(event, context):

    #X zur Authentifizierung-Line-Signatur-Header
    signature = event["headers"]["X-Line-Signature"]

    body = event["body"]

    #Rückgabewert einstellen
    ok_json = {"isBase64Encoded": False,
               "statusCode": 200,
               "headers": {},
               "body": ""}
    error_json = {"isBase64Encoded": False,
                  "statusCode": 403,
                  "headers": {},
                  "body": "Error"}

    @handler.add(MessageEvent, message=ImageMessage)
    def message(line_event):

        #Benutzerprofil
        profile = line_bot_api.get_profile(line_event.source.user_id)

        #Extrahieren Sie die ID des Benutzers, der gesendet hat(push_Verwenden Sie die if-Nachricht,Für die Antwort nicht erforderlich)
        # user_id = profile.user_id

        #Nachrichten-ID extrahieren
        message_id = line_event.message.id

        #Bilddatei extrahieren
        message_content = line_bot_api.get_message_content(message_id)
        content = bytes()
        for chunk in message_content.iter_content():
            content += chunk

        #Bilddatei speichern
        key = "origin_photo/" + message_id
        new_key = message_id[-3:]
        s3.put_object(Bucket=bucket, Key=key, Body=content)

        #Rufen Sie das Gesichtserkennungs-Lambda an
        lambdaRekognitionName = "<Dies ist eine Art Gesichtserkennung Lambda>"
        params = {"Bucket": bucket, "Key": key}  #Informationen zum Bilddateipfad
        payload = json.dumps(params)
        response = boto3.client("lambda").invoke(
            FunctionName=lambdaRekognitionName, InvocationType="RequestResponse", Payload=payload)
        response = json.load(response["Payload"])

        #Rufen Sie das Lambda für die neue Bilderzeugung auf
        lambdaNewMaskName = "<Hier ist die neue Bilderzeugung Lambda Arn>"
        params = {"landmarks": str(response),
                  "bucket": bucket,
                  "photo_key": key,
                  "new_photo_key": new_key}
        payload = json.dumps(params)
        boto3.client("lambda").invoke(FunctionName=lambdaNewMaskName,
                                      InvocationType="RequestResponse", Payload=payload)

        #Signierte URL-Generierung
        presigned_url = s3.generate_presigned_url(ClientMethod="get_object", Params={
                                                  "Bucket": bucket, "Key": new_key}, ExpiresIn=600)

        #Auf neue Bildnachricht antworten
        line_bot_api.reply_message(line_event.reply_token, ImageSendMessage(
            original_content_url=presigned_url, preview_image_url=presigned_url))

    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.message))
        return error_json
    except InvalidSignatureError:
        return error_json

    return ok_json

lambda_function_for_rekognition.py

lambda_function_for_rekognition.py


import json
import boto3

rekognition = boto3.client("rekognition")


def lambda_handler(event, context):

    #Ruft den Pfad der Bilddatei vom Ereignis ab
    bucket = event["Bucket"]
    key = event["Key"]

    #Rufen Sie die Erkennung an, um die Gesichtserkennung durchzuführen
    response = rekognition.detect_faces(
        Image={'S3Object': {'Bucket': bucket, 'Name': key}}, Attributes=['ALL'])

    #Wie viele Personen sind auf dem Foto
    number_of_people = len(response["FaceDetails"])

    #Erstellen Sie eine Liste aller erforderlichen Orientierungspunkte
    all_needed_landmarks = []
    #Prozess nach Anzahl der Personen
    for i in range(number_of_people):
        #Dies ist eine Liste von Wörterbüchern
        all_landmarks_of_one_person = response["FaceDetails"][i]["Landmarks"]
        #Diesmal eyeLeft, eyeRight, upperJawlineLeft, upperJawlineRight,Verwenden Sie nur chinBottom
        # needed_Auszug zu Sehenswürdigkeiten
        needed_landmarks = []
        for type in ["eyeLeft", "eyeRight", "upperJawlineLeft", "upperJawlineRight", "chinBottom"]:
            landmark = next(
                item for item in all_landmarks_of_one_person if item["Type"] == type)
            needed_landmarks.append(landmark)
        all_needed_landmarks.append(needed_landmarks)

    return all_needed_landmarks

lambda_function_for_new_image_gengeration.py

lambda_function_for_new_image_gengeration.py


import json
import boto3

import numpy as np

from PIL import Image, ImageFile
from operator import sub
from io import BytesIO
from random import choice

s3 = boto3.client("s3")


class NewPhotoMaker:
    def __init__(self, all_landmarks, bucket, photo_key, new_photo_key):
        self.all_landmarks = eval(all_landmarks)
        self.bucket = bucket
        self.photo_key = photo_key
        self.new_photo_key = new_photo_key

    #Fotobild laden
    def load_photo_image(self):
        s3.download_file(self.bucket, self.photo_key, "/tmp/photo_file")
        self.photo_image = Image.open("/tmp/photo_file")

    #Laden Sie das Maskenbild
    def load_mask_image(self):
        #Fluch (Batman),Joker (Batman),Zufällige Auswahl von Immortan Joe (Mad Max)
        mask_key = "masks/" + choice(["bane", "joker", "joe"]) + ".png "
        s3.download_file(self.bucket, mask_key, "/tmp/mask_file")
        self.mask_image = Image.open("/tmp/mask_file")

    #Wechseln Sie von einem Orientierungspunkt (Verhältnis) zu einem bestimmten Punkt
    def landmarks_to_points(self):
        upperJawlineLeft_landmark = next(
            item for item in self.landmarks if item["Type"] == "upperJawlineLeft")
        upperJawlineRight_landmark = next(
            item for item in self.landmarks if item["Type"] == "upperJawlineRight")
        eyeLeft_landmark = next(
            item for item in self.landmarks if item["Type"] == "eyeLeft")
        eyeRight_landmark = next(
            item for item in self.landmarks if item["Type"] == "eyeRight")

        self.upperJawlineLeft_point = [int(self.photo_image.size[0] * upperJawlineLeft_landmark["X"]), 
                                       int(self.photo_image.size[1] * upperJawlineLeft_landmark["Y"])]
        self.upperJawlineRight_point = [int(self.photo_image.size[0] * upperJawlineRight_landmark["X"]), 
                                        int(self.photo_image.size[1] * upperJawlineRight_landmark["Y"])]
        self.eyeLeft_point = [int(self.photo_image.size[0] * eyeLeft_landmark["X"]),
                              int(self.photo_image.size[1] * eyeLeft_landmark["Y"])]
        self.eyeRight_point = [int(self.photo_image.size[0] * eyeRight_landmark["X"]),
                               int(self.photo_image.size[1] * eyeRight_landmark["Y"])]

    #Passen Sie die Größe des Maskenbilds an Ihre Gesichtsbreite an
    def resize_mask(self):
        face_width = int(np.linalg.norm(list(map(sub, self.upperJawlineLeft_point, self.upperJawlineRight_point))))
        new_hight = int(self.mask_image.size[1]*face_width/self.mask_image.size[0])
        self.mask_image = self.mask_image.resize((face_width, new_hight))

    #Drehen Sie das Maskenbild entsprechend dem Gesichtswinkel (nicht die diagonale Fläche aufgrund der Drehung des Halses).
    def rotate_mask(self):
        angle = np.arctan2(self.upperJawlineRight_point[1] - self.upperJawlineLeft_point[1],
                           self.upperJawlineRight_point[0] - self.upperJawlineLeft_point[0])
        angle = -np.degrees(angle)  # radian to dgree
        self.mask_image = self.mask_image.rotate(angle, expand=True)

    #Kombinieren Sie fotografisches Bild und Maskenbild
    def match_mask_position(self):
        #Matching mit Augenposition
        face_center = [int((self.eyeLeft_point[0] + self.eyeRight_point[0])/2),
                       int((self.eyeLeft_point[1] + self.eyeRight_point[1])/2)]
        mask_center = [int(self.mask_image.size[0]/2),
                       int(self.mask_image.size[1]/2)]
        x = face_center[0] - mask_center[0]
        y = face_center[1] - mask_center[1]
        self.photo_image.paste(self.mask_image, (x, y), self.mask_image)

    #Speichern Sie die neue Bilddatei in S3
    def save_new_photo(self):
        new_photo_byte_arr = BytesIO()
        self.photo_image.save(new_photo_byte_arr, format="JPEG")
        new_photo_byte_arr = new_photo_byte_arr.getvalue()
        s3.put_object(Bucket=self.bucket, Key=self.new_photo_key,
                      Body=new_photo_byte_arr)

    #Lauf
    def run(self):

        self.load_photo_image()

        #Verarbeitung für die Anzahl der Personen
        for i in range(len(self.all_landmarks)):
            self.load_mask_image()  #Laden Sie jedes Mal eine neue Maske
            self.landmarks = self.all_landmarks[i]
            self.landmarks_to_points()
            self.resize_mask()
            self.rotate_mask()
            self.match_mask_position()

        self.save_new_photo()

#Lambda Hauptfunktion


def lambda_handler(event, context):
    landmarks = event["landmarks"]
    bucket = event["bucket"]
    photo_key = event["photo_key"]
    new_photo_key = event["new_photo_key"]

    photo_maker = NewPhotoMaker(landmarks, bucket, photo_key, new_photo_key)
    photo_maker.run()

Recommended Posts

Von der weißen Maske zur Monstermaske: Entwicklung einer serverlosen LINE-Fotoverarbeitungsanwendung auf AWS
Vorgehensweise von der AWS CDK (Python) -Entwicklung bis zur AWS-Ressourcenkonstruktion * Für Anfänger