[Python] Génère automatiquement un exemple d'image de type liste d'animation.

*** Cliquez ici pour la liste des animations (mise à jour le 25/09) ***
*** Animation hiver 2021 (à partir du 25/09) ***

Animation d'automne 2020 (mise à jour le 25/09) Qiita ne peut pas utiliser l'attribut ʻusemap` directement, c'est donc via CodePen, mais vous pouvez ouvrir le site officiel en cliquant sur. ↓

See the Pen yLOQNKZ by Cartelet Cydius (@cartelet-cydius) on CodePen.

9/25 post-scriptum

M. Uzura Info lui-même Il y avait un tweet sur Twitter lui demandant de s'abstenir de publier la liste d'animation au format de M. Uzura Info, donc à l'exception d'un échantillon du début de cet article, la liste d'animation sera publiée par la suite Est l'original? Je voudrais en faire le design de (bien que je l'ai reçu). Si vous voulez une image de style Uzura Info, veuillez exécuter le programme publié.

9/27 post-scriptum

Vous pouvez le générer sur Colab à partir d'ici. [Exemple de script de génération automatique de liste d'animations](https://colab.research.google.com/github/Cartelet0423/animeListGen/blob/master/%E4%BE%8B%E3%81%AE%E3%82%A2 % E3% 83% 8B% E3% 83% A1% E3% 83% AA% E3% 82% B9% E3% 83% 88% E8% 87% AA% E5% 8B% 95% E7% 94% 9F% E6 % 88% 90% E3% 82% B9% E3% 82% AF% E3% 83% AA% E3% 83% 97% E3% 83% 88.ipynb)

introduction

Connaissez-vous Uzurainfo? Si vous aimez l'anime, vous avez peut-être été pris en charge une fois sans connaître le nom. D'environ 2011 jusqu'à maintenant, il a créé une liste d'anime comme $ ↓ $ chaque cool.

M. Uzura Info semble terminer la production de la liste d'animation dans ce trimestre (animation d'été 2020). → Avis d'arrêt de la production de listes Merci pour ce que vous avez fait.

Cependant, il existe une longue liste qui peut être vue d'un coup d'œil, et de nombreuses personnes, y compris moi-même, estiment que c'est nécessaire.

** J'ai écrit un programme qui crée des images comme celle-ci sans autorisation. **

Ce qui a été fait

Puisque tout ce que j'ai à faire est d'exécuter le programme, je voudrais le mettre à jour à chaque période à moins que je ne l'oublie ou que la source animateTimes termine la mise à jour de la liste. (Les mises à jour sont en haut de la page).

2020秋アニメ一覧.png

la mise en oeuvre

Image explicative en haut à gauche 1588994537339st.png Tout le reste est fabriqué dans le cadre du programme. (Cette image peut également être créée dans le programme, mais il est difficile d'ajuster la position des caractères, donc je l'ai faite avec une exposition de photos.)

couler

・ Générer du matériel d'image de base

· Obtenez des informations de ** animateTimes ** ・ Formater les informations dans le type de dictionnaire ・ Séparez les phrases trop longues sur une ligne et insérez des sauts de ligne afin qu'ils ne se coupent pas à des positions étranges. -Transformez l'image de base pour l'adapter à la taille des caractères-> insérez des caractères-> transformez-la dans la forme souhaitée-> placez-la à la position souhaitée d'une autre image de base ・ Obtenir une image à partir d'une adresse ・ Reconnaissance faciale avec OpenCV (lbpcascade_animeface.xml est requis) Référence: Détection de visage Anime avec OpenCV -Découpez l'image pour que la moyenne des positions de visage reconnues soit (proche) du centre. ・ Placez dans la position souhaitée de l'image de base ・ Tout en faisant le travail ci-dessus pour toutes les animations, organisez-les dans une grille de 6 $ \ fois N $ -Reproduire l'ombre dans l'espace supplémentaire · Exportation

code

Il peut être difficile à lire car il ne s'agit que d'une fonction écrite dans Jupyter 23/09 Insertion de caractères améliorée

from requests import get
import re
from bs4 import BeautifulSoup
from math import ceil
from janome.tokenizer import Tokenizer
from PIL import Image, ImageFont, ImageDraw, ImageFilter
import matplotlib.pyplot as plt
from io import BytesIO
import unicodedata
import numpy as np
import cv2

classifier = cv2.CascadeClassifier('lbpcascade_animeface.xml')
t = Tokenizer()

template = Image.new('RGB', (158, 332), (71, 71, 71))

part = Image.fromarray(np.r_[[[np.linspace(130.5, 84.5, 256)] * 256] *
                             3].T.astype(np.uint8))

aimsize = {
    "Titre": (157, 39),
    "Entrepreneur de production": (70, 39),
    "Personnel": (86, 99),
    "jeter": (70, 112),
    "Calendrier de diffusion": (86, 52),
    "Original": (157, 37),
}
aimpoint = {
    "Titre": (0, 103),
    "Entrepreneur de production": (0, 142),
    "Personnel": (71, 195),
    "jeter": (0, 182),
    "Calendrier de diffusion": (71, 142),
    "Original": (0, 295),
}


def get_data(url):
    global Title
    html = get(url).text
    soup = BeautifulSoup(html, 'html.parser')

    for i in soup.select("br"):
        i.replace_with("\n")

    Title = re.sub("(\d+)(.+)", "\\1\n\\2",
                   soup.title.text.replace("|", "|").split("|")[0])
#"
    li = []
    headingh2 = soup.find_all('h2', class_='c-heading-h2')
    if headingh2[0].get("id") != "1":
        headingh2.pop(0)
    for i, j in zip(headingh2, soup.find_all('table')):
        a = [k.text for k in j.select("th")]
        a.append(a[0])
        a[0] = i.text
        aa = []
        for e in i.next_elements:
            if e.name == "img":
                aa.append(e["src"])
                break
        for k in a:
            if k:
                if k[0] == "\n":
                    k = k[1:]
                aa.append(k)
        li.append(aa)

    data = {}
    for i in li:
        d = {"img": "", "Original": "", "jeter": "", "Entrepreneur de production": "", "Calendrier de diffusion": ""}
        data[i[1]] = d
        d["img"] = i[0]
        d["Calendrier de diffusion"] = i[-1]
        d["jeter"] = "\n".join(re.findall(".+:(.+)", i[2].replace(":", ":")))
        staff = []
        for j in i[3].splitlines():
            j = j.replace(":", ":")
            if len(j.split(':')) < 2: continue
            if "Original" in j:
                d["Original"] = " ".join(j.split(':')[1:])
            elif "Production" in j:
                d["Entrepreneur de production"] = j.split(':')[1]
            else:
                staff.append("\n".join(j.split(':')))
        d["Personnel"] = "\n".join(staff)
        for j in soup.find(text=f"『{i[1]}] Dernier article / liste des vidéos associées").previous_elements:
            if j.name == "a" and "site" in j.text:
                data[i[1]]['href'] = j["href"]
                break

    return data


def len_(text):
    count = 0
    for c in text:
        if unicodedata.east_asian_width(c) in 'FWA':
            count += 2
        else:
            count += 1
    return count


def nn(text, w):
    tt = ""
    l = 0
    for j in t.tokenize(text, wakati=True):
        if l + len(j) > w:
            tt += "\n"
            l = 0
        elif j == "\n":
            l = 0
        tt += j
        l += len(j)

    return tt.replace("\n\n", "\n")


def mojiire(text, font_path, tmp, aimsize, aimpoint, case, hopt):
    text = text.replace("\n ", "\n")
    if case == 1:
        tm = Image.new('RGB', (256, 256), (66, 58, 59))
        if text:
            text = nn(text, 14)
    elif case == 2:
        tm = part.copy()
        if text:
            text = nn("\n".join(text.splitlines()[-3:]), 10) + "\n "
    elif case == 3:
        tm = part.copy()
        if text:
            text = nn(text, 20)
            while len_(text) < 20:
                text += " "
    else:
        tm = part.copy()
        if text:
            text = nn("\n".join(text.splitlines()[:8]), 10)
    if text:
        while len(text.splitlines()) < hopt:
            text += "\n "
        font = ImageFont.truetype(font_path, 100)
        draw = ImageDraw.Draw(tm)
        x, y = draw.textsize(text, font=font, spacing=1)
        tm = tm.resize((x + 30, y + 30))
        draw = ImageDraw.Draw(tm)
        draw.text((15, 15), text, font=font, spacing=1)
        tm = tm.resize(aimsize)
        if case == 2:
            draw = ImageDraw.Draw(tm)
            draw.line((0, 39, aimsize[0], 39), fill=(179, 179, 179), width=1)
    else:
        tm = tm.resize(aimsize)
    tmp.paste(tm, aimpoint)


def main(url, font_title, font_main):
    data = get_data(url)
    titles = list(data.keys())
    inList = True
    for x in range(ceil((len(titles) + 1) / 6)):
        for y in range(6):
            i = x * 6 + y - 1
            tmp = template.copy()
            if i == -1:
                tmp = Image.open("Chemin de l'image d'explication en haut à gauche").convert("RGB")
                font = ImageFont.truetype(font_main, 20)
                draw = ImageDraw.Draw(tmp)
                draw.text((2, 2), Title, (96, 167, 200), font=font, spacing=1)
                tmp = np.array(tmp)
            elif i < len(titles):
                for kw in aimsize.keys():
                    if kw == "Titre":
                        case = 1
                        hopt = 1
                    elif kw == "Calendrier de diffusion":
                        case = 2
                        hopt = 3
                    elif kw == "Original":
                        case = 3
                        hopt = 2
                    else:
                        case = 0
                        if kw == "Entrepreneur de production":
                            hopt = 2
                        else:
                            hopt = 6
                    mojiire(titles[i] if kw == "Titre" else data[titles[i]][kw],
                            font_title if kw == "Titre" else font_main, tmp,
                            aimsize[kw], aimpoint[kw], case, hopt)
                try:
                    img = Image.open(
                        BytesIO(get(
                            data[titles[i]]["img"]).content)).convert("RGB")
                    gray_image = cv2.cvtColor(np.array(img),
                                              cv2.COLOR_BGR2GRAY)
                    faces = classifier.detectMultiScale(gray_image)
                    h, w = img.height, img.width
                    if len(faces):
                        x_, y_ = (
                            np.r_[[faces[:, 3]**2 /
                                   (faces[:, 3]**2).sum()]].T *
                            (faces[:, :2] + faces[:, 2:] * .5)).sum(axis=0,
                                                                    dtype=int)
                    else:
                        x_, y_ = 0.5 * w, 0.45 * h
                    if w > 1.5 * h:
                        cropped_image = img.crop(
                            (max(0, int(x_ - .75 * h)) -
                             max(0,
                                 int(x_ + .75 * h) - w), 0,
                             min(w, int(x_ + .75 * h)) +
                             max(0, -int(x_ - .75 * h)), h))
                    else:
                        cropped_image = img.crop(
                            (0, max(0, int(y_ - (1 / 3) * w)) -
                             max(0,
                                 int(y_ + (1 / 3) * w) - h), w,
                             min(h, int(y_ + (1 / 3) * w)) +
                             max(0, -int(y_ - (1 / 3) * w))))
                    tmp = np.array(tmp)
                    tmp[:103, :-1] = np.array(cropped_image.resize((157, 103)))
                except Exception as e:
                    print(e)
            elif inList:
                foundation = np.array(template)
                foundation[20:, :10] = 0
                foundation[:2] = 0
                tmp = np.array(
                    Image.fromarray(foundation).filter(
                        ImageFilter.GaussianBlur(10.0)))
                inList = False
            else:
                foundation = np.array(template)
                foundation[:2] = 0
                tmp = np.array(
                    Image.fromarray(foundation).filter(
                        ImageFilter.GaussianBlur(10.0)))
            try:
                line = np.r_["1", line, tmp]
            except:
                line = tmp.copy()
        try:
            image = np.r_["0", image, line]
        except:
            image = line.copy()
        del line

    plt.imsave(f"{''.join(Title.splitlines())}.png ", image)


if __name__ == "__main__":
    url = "https://www.animatetimes.com/tag/details.php?id=5947" #URL de animateTimes
    font_title = "C:\\Windows\\Fonts\\YuGothB.ttc" #Chemin de police pour la partie titre
    font_main = "C:\\Windows\\Fonts\\YuGothM.ttc" #Chemins d'autres polices
    main(url, font_title, font_main)

Tâche

・ Faiblesse face aux changements dans la structure du site source d'information ・ S'il n'est pas reconnu comme un visage ou si la composition a des faces dispersées, la découpe peut être misérable.

Résumé

Les détails sont loin d'être faits à la main, mais je pense que les informations nécessaires en tant que liste minimale ont été supprimées.

Recommended Posts

[Python] Génère automatiquement un exemple d'image de type liste d'animation.
Découpez une image avec python
Générer automatiquement un collage à partir de la liste d'images
Générer automatiquement un commentaire de chaîne de document Python avec Emacs
Un programme qui détermine automatiquement s'il s'agit d'une animation ou d'une photo lorsque vous entrez l'image d'une personne [python]
Essayez de générer une image avec aliénation
Comment recadrer une image avec Python + OpenCV
Essayez de générer automatiquement des documents Python avec Sphinx
Générez une instruction d'insertion à partir de CSV avec Python.
Créer une image avec des caractères avec python (japonais)
traitement d'image python
Faire de chaque page PowerPoint un fichier image en Python
Créez un fichier image à l'aide de PIL (Python Imaging Library).
[Python] [Windows] Enregistrer une capture d'écran sous forme d'image
Comment créer un téléchargeur d'image avec Bottle (Python)