Je veux un robot LINE capable de détecter le visage reflété en arrière-plan et d'effectuer le traitement de la mosaïque! J'ai donc créé un Bot avec Python qui vous permet de choisir le visage que vous souhaitez mosaïquer.
Cette fois, je l'ai implémenté en utilisant Flask, line-bot-sdk, heroku, OpenCV, Pillow, etc.
Si vous avez des problèmes ou des améliorations, veuillez commenter!
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
・ Créer un canal de bot LINE ・ Déployer sur heroku
La procédure est très simple à comprendre dans l'article suivant, veuillez donc vous y référer. J'ai créé un LINE BOT avec Python + Heroku --Qiita
Ceci est un article sur parrot return LINE Bot, mais la plupart des LINE Bot seront complétés si vous modifiez le code source et déployez avec la même procédure. Si vous êtes nouveau dans la création de LINE Bot avec Python, vous devriez essayer de faire retourner un parrot Bot une fois et voir si cela fonctionne correctement. Si vous êtes bloqué dans une erreur lorsque vous "reflétez les changements" dans l'article ci-dessus, ce site -be-a-git-repository /) peut être utile.
Lorsque vous envoyez une image, elle détecte votre visage, l'indexe et la liste. Essayons la photo des affaires du personnel ministériel du cabinet Kan <source ici>.
Si vous spécifiez un index, seule la face spécifiée sera renvoyée avec une mosaïque. Appliquons une mosaïque au ministre de l'Environnement Koizumi.
Vous pouvez également appliquer une mosaïque à une face autre que celle spécifiée. Appliquons une mosaïque à des ministres autres que le premier ministre Kan. Vous pouvez appliquer une mosaïque sur n'importe quel visage de cette manière.
Veuillez utiliser le code QR ci-dessous pour ajouter des amis à ce bot LINE.
Le code source a été envoyé sur GitHub et peut être téléchargé à partir de l'URL ci-dessous. GitHub - Kitsuya0828/face_mosaic_linebot: A LINE Bot which recognizes faces on the picture and blur any of them you like
Préparez le chemin de l'image à lire et le chemin de la destination d'enregistrement.
src :Chemin de l'image à lire
desc :Chemin de destination
Enfin le visage est détecté. Cela dit, tout ce que vous avez à faire est de transmettre le fichier en cascade au classificateur de cascade OpenCV et de spécifier l'image en niveaux de gris.
#Spécification du chemin du fichier en cascade (classificateur des données d'apprentissage des fonctionnalités)
cascade_file = './cascade/haarcascade_frontalface_alt2.xml'
#Chargement d'image
image = cv2.imread(str(src))
#Convertir en échelle de gris
image_gs = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#Lecture du fichier de quantité de fonction pour la reconnaissance faciale
cascade = cv2.CascadeClassifier(cascade_file)
#Reconnaissance de visage
face_list = cascade.detectMultiScale(image_gs,
scaleFactor=1.1,
minNeighbors=1,
minSize=(20,20)) #Ignorez les plages inférieures à 20 x 20 pixels. Pour éviter que l'arrière-plan ne soit confondu avec un visage
La liste face_list '' contient désormais les coordonnées du visage détecté. Ensuite, découpez l'image du visage en fonction des coordonnées où le visage a été détecté et affichez la liste à l'aide de matplotlib. Puisqu'il est agencé de manière à devenir une image carrée dans son ensemble, la partie restante sera remplie d'une image vierge (
white.jpg` ``).
length = len(face_list)
#Disposer les feuilles pm x pm dans une tuile
pm = 1
while pm**2 < length:
pm += 1
#Lister les images dans les tuiles
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 #Verticale
j = k % pm #côté
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]
#Découpez une face à l'aide de l'accès au tableau
#Le type d'image est un tableau de Numpy(numpy.ndarray).. Facile à utiliser
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)
Si la détection de visage est effectuée plusieurs fois, l'ordre des visages sortis sera différent à chaque fois, de sorte que le visage auquel l'utilisateur ne s'attendait pas sera mosaïqué. Par conséquent, il est nécessaire de sauvegarder les coordonnées détectées la première fois dans le fichier texte (spécifié par `` face_coordinates_path '') distingué par l'ID utilisateur dans l'ordre.
#Enregistrer le fichier texte des coordonnées de face
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")
Préparez les éléments suivants.
src :Chemin de l'image à lire
desc :Chemin de destination
numberslist :Liste des numéros saisis par l'utilisateur
face_list : detect_and_Liste des coordonnées de visage reconnues par la gamme
Maintenant, appliquons une mosaïque en utilisant OpenCV. S'il correspond au numéro de visage saisi par l'utilisateur, le processus de mosaïque sera exécuté.
for i,f in enumerate(face_list):
x,y,w,h = f
if i not in numberslist:
continue
#Réduisez l'image recadrée au grossissement spécifié
face_img = image[y:y+h,x:x+w]
face_img = cv2.resize(face_img, (min(10,w//10),min(10,h//10)))
#Restaurer l'image réduite à sa taille d'origine
#Spécifiez comment redimensionner avec l'argument interpolation (cv.INTER_LINEAR a des coins en mosaïque discrets)
face_img = cv2.resize(face_img,(w,h),interpolation=cv2.INTER_AREA)
#Coller à l'image d'origine
image[y:y+h,x:x+w] = face_img
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
im = Image.fromarray(image)
im.save(desc)
Lors de l'envoi d'un message image avec LINE Bot, vous devez préparer les URL de l'image d'origine et de l'image d'aperçu.
main_image_path = MAIN_IMAGE_PATH.format(user_id)
preview_image_path = PREVIEW_IMAGE_PATH.format(user_id)
La méthode de base pour envoyer des images est la suivante.
#Envoyer l'image
image_message = ImageSendMessage(original_content_url=f"https://<Le nom de mon application>.herokuapp.com/{main_image_path}",
preview_image_url=f"https://<Le nom de mon application>.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 == 'La revue':
line_bot_api.reply_message(
event.reply_token, messages=[TextSendMessage(text="<Examiner l'URL du site>")]
)
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://<Le nom de mon application>.herokuapp.com/{main_image_path}",
preview_image_url=f"https://<Le nom de mon application>.herokuapp.com/{preview_image_path}",
)
app.logger.info(f"https://<Le nom de mon application>.herokuapp.com/{main_image_path}")
line_bot_api.reply_message(
event.reply_token, messages=[image_message,TextSendMessage(text="Je suis désolé si tu n'aimes pas ça")]
)
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)
#Enregistrer l'image
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())
#Envoyer l'image
image_message = ImageSendMessage(
original_content_url=f"https://<Le nom de mon application>.herokuapp.com/{main_image_path}",
preview_image_url=f"https://<Le nom de mon application>![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="Pouvez-vous entrer le numéro du visage que vous souhaitez mosaïquer, séparé par des espaces demi-largeur?\n Exemple) Je souhaite appliquer une mosaïque sur les 1ère et 3ème faces\n ☞ Entrez "1 3"\n\n'-1'Vous pouvez également spécifier le numéro du visage que vous souhaitez conserver en le préfixant.\n Exemple) Je souhaite appliquer une mosaïque à des faces autres que N ° 0 et N ° 2.\n☞「-Entrez 1 0 2 "")])
#Enregistrer le fichier texte des coordonnées de face
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='Je n'ai pas pu trouver un visage reconnaissable')
)
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:
"""sauvegarder"""
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:
"""Rechercher et lister les visages
:params src:
Chemin de l'image à lire
:params desc:
Chemin de destination
"""
#Spécification du chemin du fichier en cascade (classificateur des données d'apprentissage des fonctionnalités)
cascade_file = './cascade/haarcascade_frontalface_alt2.xml'
#Chargement d'image
image = cv2.imread(str(src))
#Convertir en échelle de gris
image_gs = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
#Lecture du fichier de quantité de fonction pour la reconnaissance faciale
cascade = cv2.CascadeClassifier(cascade_file)
#Reconnaissance de visage
face_list = cascade.detectMultiScale(image_gs,
scaleFactor=1.1,
minNeighbors=1,
minSize=(20,20)) #Ignorez les plages inférieures à 20 x 20 pixels. Pour éviter que l'arrière-plan ne soit confondu avec un visage
length = len(face_list)
#Disposer les feuilles pm x pm dans une tuile
pm = 1
while pm**2 < length:
pm += 1
#Lister les images dans les tuiles
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 #Verticale
j = k % pm #côté
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]
#Découpez une face à l'aide de l'accès au tableau
#Le type d'image est un tableau de Numpy(numpy.ndarray).. Facile à utiliser
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:
Chemin de l'image à lire
:params desc:
Chemin de destination
:numberslist:
Liste des numéros saisis par l'utilisateur
:face_list:
detect_and_Liste des coordonnées de visage reconnues par la gamme
"""
#Chargement d'image
image = cv2.imread(str(src))
#Lorsque l'utilisateur spécifie le numéro du visage qu'il souhaite conserver
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
#Réduisez l'image recadrée au grossissement spécifié
face_img = image[y:y+h,x:x+w]
face_img = cv2.resize(face_img, (min(10,w//10),min(10,h//10)))
#Restaurer l'image réduite à sa taille d'origine
#Spécifiez comment redimensionner avec l'argument interpolation (cv.INTER_LINEAR a des coins en mosaïque discrets)
face_img = cv2.resize(face_img,(w,h),interpolation=cv2.INTER_AREA)
#Coller à l'image d'origine
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
Lorsque j'ajoute opencv-python à requirements.txt et que je le déploie sur Heroku même si le programme a pu s'exécuter dans l'environnement local
ImportError: libSM.so.6: cannot open shared object file: No such file or directory
J'obtiens une erreur comme celle-ci. J'ai pu résoudre l'erreur en faisant deux choses:
Ajoutez https://github.com/heroku/heroku-buildpack-apt aux buildpacks de Heroku et ajoutez le fichier Aptfile à votre dossier de projet. ..
Aptfile
libsm6
libxrender1
libfontconfig1
libice6
Il n'y a aucun problème si vous suivez l'article ci-dessous. Utilisation d'OpenCV avec heroku [Python3] --Qiita
opencv-python-headless==4.2.0.32
J'ai trouvé une personne qui a le même problème sur le site suivant et a pu le résoudre. https://stackoverflow.com/questions/49469764/how-to-use-opencv-with-heroku/51004957
J'étais très ennuyé par l'incompatibilité entre Heroku et OpenCV, mais j'ai réussi à y arriver. Pourquoi ne créez-vous pas un bot LINE qui vous semble utile si vous avez des idées? Ayez une bonne vie de programmation.
☟ Il est vraiment facile de comprendre comment créer un bot LINE en utilisant Python. J'ai créé un LINE BOT avec Python + Heroku --Qiita
☟ La méthode d'enregistrement et d'envoi des images a été très utile. [Python] J'ai créé un robot LINE qui date les photos --Qiita
Recommended Posts