Ich möchte einen LINE-Bot, der das im Hintergrund reflektierte Gesicht erkennt und eine Mosaikverarbeitung durchführt! Also habe ich mit Python einen Bot erstellt, mit dem Sie das Gesicht auswählen können, das Sie mosaikieren möchten.
Dieses Mal habe ich es mit Flask, Line-Bot-SDK, Heroku, OpenCV, Pillow usw. implementiert.
Wenn Sie Probleme oder Verbesserungen haben, kommentieren Sie bitte!
Flask==1.1.2
gunicorn==20.0.4
line-bot-sdk==1.16.0
matplotlib==3.3.2
numpy==1.18.5
opencv-python-headless==4.2.0.32
Pillow==7.2.0
python-3.8.5
・ LINE Bot-Kanal erstellen ・ Auf Heroku bereitstellen
Die Vorgehensweise ist im folgenden Artikel sehr einfach zu verstehen. Bitte beziehen Sie sich darauf. Ich habe mit Python + Heroku --Qiita einen LINE BOT erstellt
Dies ist ein Artikel über die Rückgabe von LINE Bot durch Papageien. Die meisten LINE Bot werden jedoch abgeschlossen, wenn Sie den Quellcode ändern und mit demselben Verfahren bereitstellen. Wenn Sie mit der Erstellung von LINE Bot mit Python noch nicht vertraut sind, sollten Sie versuchen, einen Papagei einmal dazu zu bringen, Bot zurückzugeben, und prüfen, ob er ordnungsgemäß funktioniert. Wenn Sie beim "Reflektieren von Änderungen" im obigen Artikel in einem Fehler stecken bleiben, diese Site -be-a-git-repository /) kann hilfreich sein.
Wenn Sie ein Bild senden, erkennt es Ihr Gesicht, indiziert es und listet es auf. Versuchen wir das Foto der ministeriellen Personalangelegenheiten des Kan-Kabinetts <Quelle ist hier>.
Wenn Sie einen Index angeben, wird nur die angegebene Fläche mit einem Mosaik zurückgegeben. Wenden wir ein Mosaik auf Umweltminister Koizumi an.
Sie können ein Mosaik auch auf eine andere als die angegebene Fläche anwenden. Wenden wir ein Mosaik auf andere Minister als Premierminister Kan an. Auf diese Weise können Sie jedem Gesicht ein Mosaik hinzufügen.
Bitte verwenden Sie den folgenden QR-Code, um diesem LINE-Bot Freunde hinzuzufügen.
Der Quellcode wurde an GitHub gesendet und kann von der unten angegebenen URL heruntergeladen werden. GitHub - Kitsuya0828/face_mosaic_linebot: A LINE Bot which recognizes faces on the picture and blur any of them you like
Bereiten Sie den Pfad des zu lesenden Bildes und den Pfad des Speicherziels vor.
src :Pfad des zu lesenden Bildes
desc :Weg zum Ziel
Schließlich wird das Gesicht erkannt. Sie müssen jedoch nur die Kaskadendatei an den OpenCV-Kaskadenklassifizierer übergeben und das Graustufenbild angeben.
#Angeben des Pfads der Kaskadendatei (Klassifizierer der Feature-Trainingsdaten)
cascade_file = './cascade/haarcascade_frontalface_alt2.xml'
#Bild wird geladen
image = cv2.imread(str(src))
#In Graustufen konvertieren
image_gs = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#Lesen der Feature-Betragsdatei zur Gesichtserkennung
cascade = cv2.CascadeClassifier(cascade_file)
#Gesichtserkennung
face_list = cascade.detectMultiScale(image_gs,
scaleFactor=1.1,
minNeighbors=1,
minSize=(20,20)) #Bereiche unter 20 x 20 Pixel ignorieren. Damit der Hintergrund nicht mit einem Gesicht verwechselt wird
Die Liste `face_list``` enthält jetzt die Koordinaten des erkannten Gesichts. Schneiden Sie als Nächstes das Bild des Gesichts basierend auf den Koordinaten aus, an denen das Gesicht erkannt wurde, und zeigen Sie die Liste mit matplotlib an. Da es so angeordnet ist, dass es als Ganzes ein quadratisches Bild wird, wird der verbleibende Teil mit einem leeren Bild (
white.jpg``
) gefüllt.
length = len(face_list)
#Ordnen Sie die Blätter von pm x pm in einer Kachel an
pm = 1
while pm**2 < length:
pm += 1
#Listen Sie Bilder in Kacheln auf
fig, ax = plt.subplots(pm, pm, figsize=(10, 10))
fig.subplots_adjust(hspace=0, wspace=0)
for k in range(pm**2):
i = k // pm #Vertikal
j = k % pm #Seite
ax[i, j].xaxis.set_major_locator(plt.NullLocator())
ax[i, j].yaxis.set_major_locator(plt.NullLocator())
if k < length:
x,y,w,h = face_list[k]
#Schneiden Sie ein Gesicht mit Array-Zugriff aus
#Der Bildtyp ist ein Array von Numpy(numpy.ndarray).. Einfach zu verwenden
face_img = image[y:y+h,x:x+w]
face_img = np.asarray(face_img)
face_img = cv2.resize(face_img, (300, 300), cv2.INTER_LANCZOS4)
face_img = cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB)
ax[i, j].text(30, 60, str(pm*i+j), fontsize=30, color='red')
ax[i, j].imshow(face_img)
else:
img = cv2.imread('./white.jpg')
img = np.asarray(img)
img = cv2.resize(img, (300, 300), cv2.INTER_LANCZOS4)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
ax[i, j].imshow(img)
plt.savefig(desc)
Wenn die Gesichtserkennung mehrmals durchgeführt wird, ist die Reihenfolge der ausgegebenen Gesichter jedes Mal anders, sodass das Gesicht, das der Benutzer nicht erwartet hat, mosaikiert wird. Daher ist es notwendig, die beim ersten Mal erkannten Koordinaten in der Textdatei (angegeben durch `` `face_coordinates_path```) zu speichern, die durch die Benutzer-ID in der Reihenfolge unterschieden wird.
#Speichern Sie die Textdatei der Gesichtskoordinate
with open(face_coordinates_path, "w", encoding='utf-8') as f:
for i in range(len(face_list)):
f.write(" ".join([str(x) for x in face_list[i]]) + "\n")
Halten Sie die folgenden Artikel bereit.
src :Pfad des zu lesenden Bildes
desc :Weg zum Ziel
numberslist :Liste der vom Benutzer eingegebenen Nummern
face_list : detect_and_Liste der von der Aufstellung erkannten Gesichtskoordinaten
Wenden wir nun mit OpenCV ein Mosaik an. Wenn es mit der vom Benutzer eingegebenen Gesichtsnummer übereinstimmt, wird der Mosaikprozess ausgeführt.
for i,f in enumerate(face_list):
x,y,w,h = f
if i not in numberslist:
continue
#Reduzieren Sie das zugeschnittene Bild mit der angegebenen Vergrößerung
face_img = image[y:y+h,x:x+w]
face_img = cv2.resize(face_img, (min(10,w//10),min(10,h//10)))
#Stellen Sie das verkleinerte Bild auf seine ursprüngliche Größe wieder her
#Geben Sie an, wie die Größe mit der Argumentinterpolation geändert werden soll (vgl.INTER_LINEAR hat unauffällige Mosaikecken)
face_img = cv2.resize(face_img,(w,h),interpolation=cv2.INTER_AREA)
#In das Originalbild einfügen
image[y:y+h,x:x+w] = face_img
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
im = Image.fromarray(image)
im.save(desc)
Wenn Sie eine Bildnachricht mit LINE Bot senden, müssen Sie die URLs sowohl des Originalbilds als auch des Vorschaubilds vorbereiten.
main_image_path = MAIN_IMAGE_PATH.format(user_id)
preview_image_path = PREVIEW_IMAGE_PATH.format(user_id)
Die grundlegende Methode zum Senden von Bildern lautet wie folgt.
#Bild senden
image_message = ImageSendMessage(original_content_url=f"https://<Mein Anwendungsname>.herokuapp.com/{main_image_path}",
preview_image_url=f"https://<Mein Anwendungsname>.herokuapp.com/{preview_image_path}",)
.
│ Aptfile
│ detect_and_lineup.py
│ main.py
│ mosaic.py
│ Procfile
│ requirements.txt
│ runtime.txt
│ white.jpg
│
├─cascade
│ haarcascade_frontalface_alt2.xml
│
└─static
└─images
Hoge
import os
from pathlib import Path
from typing import List
from flask import Flask, abort, request
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (ImageMessage, ImageSendMessage, MessageEvent, TextMessage, TextSendMessage)
from detect_and_lineup import detect_and_lineup
from mosaic import mosaic
app = Flask(__name__,static_url_path="/static")
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)
SRC_IMAGE_PATH = "static/images/{}.jpg "
MAIN_IMAGE_PATH = "static/images/{}_main.jpg "
PREVIEW_IMAGE_PATH = "static/images/{}_preview.jpg "
FACE_COORDINATES_PATH = "{}.txt"
@app.route("/")
def hello_world():
return "hello world!"
@app.route("/callback", methods=["POST"])
def callback():
# get X-Line-Signature header value
signature = request.headers["X-Line-Signature"]
# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
# handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return "OK"
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
profile = line_bot_api.get_profile(event.source.user_id)
user_id = profile.user_id
if event.message.text == 'Rezension':
line_bot_api.reply_message(
event.reply_token, messages=[TextSendMessage(text="<Überprüfen Sie die Site-URL>")]
)
else:
src_image_path = Path(SRC_IMAGE_PATH.format(user_id)).absolute()
main_image_path = MAIN_IMAGE_PATH.format(user_id*2)
preview_image_path = PREVIEW_IMAGE_PATH.format(user_id*2)
face_coordinates_path = FACE_COORDINATES_PATH.format(user_id)
numberslist = list(map(int,str(event.message.text).split()))
with open(face_coordinates_path) as f:
face_list = [list(map(int,s.strip().split())) for s in f.readlines()]
mosaic(src=src_image_path, desc=Path(main_image_path).absolute(),numberslist=numberslist,face_list=face_list)
mosaic(src=src_image_path, desc=Path(preview_image_path).absolute(),numberslist=numberslist,face_list=face_list)
image_message = ImageSendMessage(
original_content_url=f"https://<Mein Anwendungsname>.herokuapp.com/{main_image_path}",
preview_image_url=f"https://<Mein Anwendungsname>.herokuapp.com/{preview_image_path}",
)
app.logger.info(f"https://<Mein Anwendungsname>.herokuapp.com/{main_image_path}")
line_bot_api.reply_message(
event.reply_token, messages=[image_message,TextSendMessage(text="Es tut mir leid, wenn es dir nicht gefällt")]
)
src_image_path.unlink()
@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
message_id = event.message.id
profile = line_bot_api.get_profile(event.source.user_id)
user_id = profile.user_id
src_image_path = Path(SRC_IMAGE_PATH.format(user_id)).absolute()
main_image_path = MAIN_IMAGE_PATH.format(user_id)
preview_image_path = PREVIEW_IMAGE_PATH.format(user_id)
face_coordinates_path = FACE_COORDINATES_PATH.format(user_id)
#Bild speichern
save_image(message_id, src_image_path)
try:
face_list = detect_and_lineup(src=src_image_path, desc=Path(main_image_path).absolute())
detect_and_lineup(src=src_image_path, desc=Path(preview_image_path).absolute())
#Bild senden
image_message = ImageSendMessage(
original_content_url=f"https://<Mein Anwendungsname>.herokuapp.com/{main_image_path}",
preview_image_url=f"https://<Mein Anwendungsname>![S__54403102.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/832523/9269110c-f218-c528-858d-e3223283b588.jpeg)
.herokuapp.com/{preview_image_path}",
)
app.logger.info(f"https://alvinface2.herokuapp.com/{main_image_path}")
line_bot_api.reply_message(event.reply_token, messages=[image_message, TextSendMessage(text="Können Sie die Nummer des Gesichtes eingeben, das Sie mosaikieren möchten, getrennt durch Leerzeichen halber Breite?\n Beispiel) Ich möchte ein Mosaik auf die 1. und 3. Fläche anwenden\n ☞ Geben Sie "1 3" ein\n\n'-1'Sie können auch die Nummer des Gesichts angeben, das Sie behalten möchten, indem Sie ihm ein Präfix voranstellen.\n Beispiel) Ich möchte ein Mosaik auf andere Flächen als Nr. 0 und Nr. 2 anwenden.\n☞「-1 0 2 eingeben "")])
#Speichern Sie die Textdatei der Gesichtskoordinate
with open(face_coordinates_path, "w", encoding='utf-8') as f:
for i in range(len(face_list)):
f.write(" ".join([str(x) for x in face_list[i]]) + "\n")
except Exception:
line_bot_api.reply_message(
event.reply_token, TextSendMessage(text='Ich konnte kein erkennbares Gesicht finden')
)
def public_attr(obj) -> List[str]:
return [x for x in obj.__dir__() if not x.startswith("_")]
def save_image(message_id: str, save_path: str) -> None:
"""sparen"""
message_content = line_bot_api.get_message_content(message_id)
with open(save_path, "wb") as f:
for chunk in message_content.iter_content():
f.write(chunk)
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(host="0.0.0.0", port=port)
import numpy as np
from matplotlib import pyplot as plt
from PIL import Image, ImageDraw, ImageFont
import cv2
def detect_and_lineup(src: str, desc: str) -> None:
"""Gesichter finden und auflisten
:params src:
Pfad des zu lesenden Bildes
:params desc:
Weg zum Ziel
"""
#Angeben des Pfads der Kaskadendatei (Klassifizierer der Feature-Trainingsdaten)
cascade_file = './cascade/haarcascade_frontalface_alt2.xml'
#Bild wird geladen
image = cv2.imread(str(src))
#In Graustufen konvertieren
image_gs = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#Lesen der Feature-Betragsdatei zur Gesichtserkennung
cascade = cv2.CascadeClassifier(cascade_file)
#Gesichtserkennung
face_list = cascade.detectMultiScale(image_gs,
scaleFactor=1.1,
minNeighbors=1,
minSize=(20,20)) #Bereiche unter 20 x 20 Pixel ignorieren. Damit der Hintergrund nicht mit einem Gesicht verwechselt wird
length = len(face_list)
#Ordnen Sie die Blätter von pm x pm in einer Kachel an
pm = 1
while pm**2 < length:
pm += 1
#Listen Sie Bilder in Kacheln auf
fig, ax = plt.subplots(pm, pm, figsize=(10, 10))
fig.subplots_adjust(hspace=0, wspace=0)
for k in range(pm**2):
i = k // pm #Vertikal
j = k % pm #Seite
ax[i, j].xaxis.set_major_locator(plt.NullLocator())
ax[i, j].yaxis.set_major_locator(plt.NullLocator())
if k < length:
x,y,w,h = face_list[k]
#Schneiden Sie ein Gesicht mit Array-Zugriff aus
#Der Bildtyp ist ein Array von Numpy(numpy.ndarray).. Einfach zu verwenden
face_img = image[y:y+h,x:x+w]
face_img = np.asarray(face_img)
face_img = cv2.resize(face_img, (300, 300), cv2.INTER_LANCZOS4)
face_img = cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB)
ax[i, j].text(30, 60, str(pm*i+j), fontsize=30, color='red')
ax[i, j].imshow(face_img)
else:
img = cv2.imread('./white.jpg')
img = np.asarray(img)
img = cv2.resize(img, (300, 300), cv2.INTER_LANCZOS4)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
ax[i, j].imshow(img)
plt.savefig(desc)
return face_list
import numpy as np
from matplotlib import pyplot as plt
from PIL import Image, ImageDraw, ImageFont
import cv2
def mosaic(src: str, desc: str, numberslist=[], face_list=[]) -> None:
"""
:params src:
Pfad des zu lesenden Bildes
:params desc:
Weg zum Ziel
:numberslist:
Liste der vom Benutzer eingegebenen Nummern
:face_list:
detect_and_Liste der von der Aufstellung erkannten Gesichtskoordinaten
"""
#Bild wird geladen
image = cv2.imread(str(src))
#Wenn der Benutzer die Nummer des Gesichts angibt, das er behalten möchte
new_numberslist = []
if numberslist[0] == -1:
for num in range(len(face_list)):
if num not in numberslist:
new_numberslist.append(num)
numberslist = new_numberslist
for i,f in enumerate(face_list):
x,y,w,h = f
if i not in numberslist:
continue
#Reduzieren Sie das zugeschnittene Bild mit der angegebenen Vergrößerung
face_img = image[y:y+h,x:x+w]
face_img = cv2.resize(face_img, (min(10,w//10),min(10,h//10)))
#Stellen Sie das verkleinerte Bild auf seine ursprüngliche Größe wieder her
#Geben Sie an, wie die Größe mit der Argumentinterpolation geändert werden soll (vgl.INTER_LINEAR hat unauffällige Mosaikecken)
face_img = cv2.resize(face_img,(w,h),interpolation=cv2.INTER_AREA)
#In das Originalbild einfügen
image[y:y+h,x:x+w] = face_img
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
im = Image.fromarray(image)
im.save(desc)
return True
Wenn ich opencv-python zu require.txt hinzufüge und es für Heroku bereitstelle, obwohl das Programm in der lokalen Umgebung ausgeführt werden konnte
ImportError: libSM.so.6: cannot open shared object file: No such file or directory
Ich bekomme so einen Fehler. Ich konnte den Fehler durch zwei Dinge beheben:
Fügen Sie https://github.com/heroku/heroku-buildpack-apt zu Herokus Buildpacks hinzu und fügen Sie das Aptfile Ihrem Projektordner hinzu. ..
Aptfile
libsm6
libxrender1
libfontconfig1
libice6
Es gibt kein Problem, wenn Sie dem folgenden Artikel folgen. Verwenden von OpenCV mit Heroku [Python3] - Qiita
opencv-python-headless==4.2.0.32
Ich habe auf der folgenden Seite eine Person gefunden, die das gleiche Problem hat und es lösen konnte. https://stackoverflow.com/questions/49469764/how-to-use-opencv-with-heroku/51004957
Die Inkompatibilität zwischen Heroku und OpenCV hat mich am meisten geärgert, aber ich habe es geschafft. Warum erstellen Sie keinen LINE-Bot, den Sie für nützlich halten, wenn Sie Ideen haben? Habe ein gutes Programmierleben.
☟ Es ist wirklich leicht zu verstehen, wie man mit Python einen LINE-Bot erstellt. Ich habe mit Python + Heroku --Qiita einen LINE BOT erstellt
☟ Die Methode zum Speichern und Senden von Bildern war sehr hilfreich. [Python] Ich habe einen LINE-Bot erstellt, der Fotos datiert - Qiita