[PYTHON] J'ai trouvé un moyen de créer un modèle 3D à partir d'une photo Partie 04 Générer des polygones

Domo est Ksuke. En 04, quand j'ai proposé une méthode pour créer un modèle 3D à partir d'une photo, je vais générer des polygones. Cliquez ici pour la partie 3 https://qiita.com/Ksuke/items/8b7f2dc840126753b4e9

* Attention * </ b> Cet article ne donne que la fin de ce que j'ai trouvé et essayé, donc cela pourrait se terminer avec du matériel abrupt ou Bad End.

Essayer

Procédure </ b>

  1. Obtenez les points à la limite du groupe de points
  2. Obtenez les points à l'intérieur de la limite du groupe de points
  3. Génération de polygones

~~ Le code ici et là est celui résumé à la fin. ~~ Il n'y a qu'une seule source, mais comme d'habitude, elle est également postée à la fin.

1. Obtenez les points à la limite du groupe de points

Pour l'instant, l'objet est représenté par un groupe de points, mais les points sont également répartis à l'intérieur de l'objet. Puisque nous n'avons pas besoin des points intérieurs, nous extrairons uniquement les points à la frontière. Pour déterminer si chaque point est une limite, comptez le nombre de points dans 6 directions adjacentes (haut, bas, droite, gauche, avant et arrière) de chaque coordonnée. Les points où le nombre de points adjacents n'est pas égal à 0 sont extraits en tant que points limites.

Obtenez les points à la limite du groupe de points



#Une fonction qui renvoie un ensemble de points à la frontière
def genBorderPointSpace(imgProjectSpace):

    #6 directions dans l'espace(Haut, bas, gauche, droite, avant et arrière)Créez une carte du nombre de points autour de chaque point en effectuant un traitement équivalent au filtre d'addition de
    locationPointMap = np.stack([
        imgProjectSpace[2:,1:-1,1:-1],
        imgProjectSpace[:-2,1:-1,1:-1],
        imgProjectSpace[1:-1,2:,1:-1],
        imgProjectSpace[1:-1,:-2,1:-1],
        imgProjectSpace[1:-1,1:-1,2:],
        imgProjectSpace[1:-1,1:-1,:-2],
    ]).sum(axis=0,dtype=np.int8)
    
    #Restaurer la taille de l'espace
    locationPointMap = np.insert(locationPointMap, (0,-1), 0, axis=0)
    locationPointMap = np.insert(locationPointMap, (0,-1), 0, axis=1)
    locationPointMap = np.insert(locationPointMap, (0,-1), 0, axis=2)

    #A partir de la carte du nombre de points environnants pour chaque point, le nombre de points environnants n'est ni 0 ni 6.(Il y a des points autour et non entourés de points)Créez un espace avec des points
    borderPointSpace = np.where((0<locationPointMap)&(locationPointMap<6)&(imgProjectSpace==1),1,0).astype(np.int8)
    
    #Renvoie un espace qui laisse des points qui ne sont pas entourés de points
    return borderPointSpace

#Extrayez les points à la limite qui est en contact avec l'extérieur du groupe de points dans l'espace de chevauchement.
borderPointSpace = genBorderPointSpace(imgProjectSpace)

#Extraire les coordonnées d'un point de l'espace
borderCoords = binary2coords(borderPointSpace)

2. Obtenez les points à l'intérieur de la limite du groupe de points

Il suffit d'afficher le polygone uniquement au point de la partie frontière, mais pour la commodité de la génération automatique du polygone ultérieurement, le point à l'intérieur du point de la frontière est également acquis.

Obtenir des points à l'intérieur de la limite d'un groupe de points



#Une fonction qui renvoie un ensemble de points juste à l'intérieur de la limite
def genInsidePointSpace(imgProjectSpace,borderPointSpace):

    #Créer un espace qui laisse des points près de la limite(Ne quittez pas la partie frontière)
    nearBorderPointSpace = np.stack([
        borderPointSpace[2:,1:-1,1:-1],
        borderPointSpace[:-2,1:-1,1:-1],
        borderPointSpace[1:-1,2:,1:-1],
        borderPointSpace[1:-1,:-2,1:-1],
        borderPointSpace[1:-1,1:-1,2:],
        borderPointSpace[1:-1,1:-1,:-2],
        borderPointSpace[1:-1,1:-1,1:-1]*-6
    ]).sum(axis=0,dtype=np.int8)
    
    #Taille de retour
    nearBorderPointSpace = np.insert(nearBorderPointSpace, (0,-1), 0, axis=0)
    nearBorderPointSpace = np.insert(nearBorderPointSpace, (0,-1), 0, axis=1)
    nearBorderPointSpace = np.insert(nearBorderPointSpace, (0,-1), 0, axis=2)

    #Créez un espace près de la limite qui laisse les points à l'intérieur du groupe de points d'origine
    insidePointSpace = np.where(((0<nearBorderPointSpace)&(imgProjectSpace==1)),1,0)
    
    return insidePointSpace


#À partir du groupe de points dans l'espace superposé, retirez les points immédiatement à l'intérieur de la partie frontière
insidePointSpace = genInsidePointSpace(imgProjectSpace,borderPointSpace)

#Extraire les coordonnées d'un point de l'espace
insideCoords = binary2coords(insidePointSpace)

3. Génération de polygones

Après avoir obtenu deux types de points, il est temps de générer des polygones. Il est plus difficile d'expliquer ce que fait ce processus que d'autres processus, donc je vais l'expliquer avec une image en deux dimensions (cela a pris beaucoup de temps ici ...).

Le but de ce processus est de reconfirmer le point de la limite de l'objet pour déterminer la surface qui entoure l'objet. Sur une image bidimensionnelle, la ligne entourant la zone (Fig. 2) est déterminée à partir du point à la limite de l'objet (Fig. 1).

delaunay-explanation00.png
Figure 1.Points limites

delaunay-explanation01.png
Figure 2.La limite que vous voulez à la fin

Pour ce faire, j'ai utilisé une méthode appelée Delaunay. Il s'agit de diviser la zone représentée par les points par un triangle. La figure 3 ci-dessous montre le résultat de l'essai de Delaunay tel qu'il est avec les points rouges dans l'image. Comme vous pouvez le voir, la ligne est dessinée à la limite souhaitée, mais la ligne est également dessinée au niveau des autres parties.

delaunay-explanation02.png
figure 3.Résultat de la division triangulaire des points à la frontière

Après m'être inquiété du problème, j'ai décidé d'ajouter les points à l'intérieur de la partie frontière au point à l'intérieur de la partie frontière pour effectuer Delaunay. Un point à l'intérieur de la limite est un point qui est proche du point à la limite et à l'intérieur de la zone de l'objet. Il s'agit du point bleu ajouté dans la figure 4 ci-dessous.

delaunay-explanation03.png
Figure 4.Points limites et points à l'intérieur de la frontière

Ce qui se passe lorsque Delaunay est terminé, y compris les points à l'intérieur de cette partie frontière, est comme le montre la figure 5 ci-dessous.

delaunay-explanation04.png
Figure 5.Résultat de la division triangulaire des points à la frontière et des points à l'intérieur de la frontière

À première vue, les points rouges peuvent sembler un peu plus compliqués que lorsque Delaunay a été fait, mais en fait, cela vous permet de classer les triangles divisés en trois types. Comment classer ・ Un triangle composé uniquement des points à la frontière (peint en rouge clair) ・ Un triangle composé uniquement des points à l'intérieur de la limite (peint en bleu clair) -Un triangle composé à la fois des points à la frontière et des points à l'intérieur de la frontière (peint en violet clair) C'est comme ça. Si vous les peignez séparément, cela ressemblera à la figure 6.

delaunay-explanation05.png
Graphique 6.Résultat de la classification par division triangulaire

Si vous regardez les trois types de triangles, vous verrez que toutes les limites souhaitées à la fin sont contenues dans le triangle violet clair. En excluant les triangles violet clair, le résultat est comme indiqué sur la figure 7.

delaunay-explanation06.png
Graphique 7.Triangle avec bordure

Maintenant que le nombre de triangles a diminué jusqu'à présent, concentrons-nous sur les lignes. En se concentrant sur les lignes, elles peuvent également être classées en trois types. Comment classer ・ Ligne reliant les points à la limite (peinte en rouge clair) ・ Une ligne reliant les points à l'intérieur de la limite (peinte en bleu clair) -Une ligne reliant les points à la limite et les points à l'intérieur de la limite (peinte en violet clair) C'est comme ça. Si vous les peignez séparément, cela ressemblera à la figure 8.

delaunay-explanation07.png
Figure 8.Résultat de classification de ligne

Si vous arrivez à ce point, le reste est comme vous l'avez vu. Les lignes peintes en rouge clair sont exactement les mêmes que la ligne de démarcation finale souhaitée dessinée sur la figure 2, n'est-ce pas? C'est pourquoi il ressemble à la figure 9 à l'exception de la ligne rouge clair.

delaunay-explanation08.png
Graphique 9.Limite qui pourrait être obtenue

De cette manière, vous pouvez obtenir la ligne qui l'entoure (ou la face s'il s'agit d'un objet de groupe de points tridimensionnel) à partir du groupe de points. Voici la mise en œuvre de ce processus pour les groupes de points 3D.

Génération de polygones




#Une fonction qui détermine la face d'un polygone à partir d'un ensemble de coordonnées
def genPolygon(borderCoords,insideCoords):
    
    #Combinez deux types de sommets(Correspond au traitement de la figure 4)
    coords = np.concatenate([borderCoords,insideCoords])

    #Générer une carte montrant le type de chaque sommet(0 est la frontière et 1 est à l'intérieur)
    coordStat = np.zeros((len(coords)),dtype=np.uint8)
    coordStat[len(borderCoords):len(borderCoords)+len(insideCoords)] = 1
    
    #Génère un groupe pyramidal triangulaire composé de 4 points du groupe apex(Correspond au traitement de la Fig.5)
    triPyramids = Delaunay(coords).simplices
    
    #Vérifiez combien de sommets intérieurs sont inclus pour chaque pyramide triangulaire(Correspond au traitement de la Fig.6)
    triPyramidsStat = coordStat[triPyramids].sum(axis=1)
    
    #Parmi les pyramides triangulaires, celles qui incluent à la fois le sommet intérieur et le sommet de la frontière sont extraites et utilisées comme une pyramide triangulaire valide.(Correspond au traitement de la figure 7)
    #(Ici, la pyramide triangulaire dans l'espace de marge à l'extérieur de l'objet composé uniquement des sommets de bordure et la pyramide triangulaire dans l'espace de marge à l'intérieur de l'objet composé uniquement des sommets intérieurs sont supprimées.)
    effectiveTriPyramids = triPyramids[np.where((triPyramidsStat!=0)&(triPyramidsStat!=4))[0]]
    
    #Visage candidat pour objet(3 index des sommets)Liste à mettre(Retirez l'excédent plus tard)
    faces = []
        
    #Extraire une face de pyramide triangulaire valide en tant que face objet(Correspond à la partie focalisée sur la ligne dans l'explication de l'image de l'article)
    for coordIndexs in effectiveTriPyramids:
        faces.append([coordIndexs[0],coordIndexs[1],coordIndexs[2]])
        faces.append([coordIndexs[0],coordIndexs[1],coordIndexs[3]])
        faces.append([coordIndexs[0],coordIndexs[2],coordIndexs[3]])
        faces.append([coordIndexs[1],coordIndexs[2],coordIndexs[3]])
        
    #Trier l'index du sommet de la face candidate de l'objet et supprimer la duplication
    faces = np.array(faces)
    faces.sort(axis=1)
    faces = np.unique(faces,axis=0)
    
    #Vérifiez combien de sommets intérieurs sont inclus pour chaque face(Correspond au traitement de la Fig.8)
    faceStat = coordStat[faces].sum(axis=1)
    
    #Retirez une face qui ne contient aucun sommet intérieur(Correspond au traitement de la figure 9)
    faces = faces[np.where(faceStat==0)]
        
    #Renvoie les sommets et les faces
    return borderCoords,faces


#Polygone à partir de deux types de coordonnées(Pour être exact, la définition de la face qui relie les sommets qui sont des polygones)Générer un
coords,faces = genPolygon(borderCoords,insideCoords)

#Changement du format pour passer les visages au mélangeur
faces = [[face[0],face[1],face[2],face[0]] for face in faces]

Contrôle de fonctionnement

Enfin, vérifiez si le code fonctionne correctement.

1. Tous ensemble

J'ai épuisé mes efforts dans la génération de polygones, je vais donc résumer les mouvements. .. .. Exécutez le code ci-dessous dans blender

Pour dynamique 1



#Dessiner des sommets et des polygones
addObj(coords=coords,faces=faces,name = "porigon",offset=[-125,-50,-50])
addObj(coords=borderCoords,name = "borderCoords",offset=[-50,-85,-50])
addObj(coords=insideCoords,name = "insideCoords",offset=[-50,-15,-50])

Si un objet comme celui-ci est affiché, il réussit. Si vous regardez de près les deux objets du bas, celui de droite est un peu plus petit (car c'est un point à l'intérieur de la limite). キャプチャ.PNG

prochain?

Enfin les polygones sont affichés, mais le nombre de polygones est incroyablement grand ... Par conséquent, nous allons réduire le nombre de polygones en utilisant la fonction de mixage.

PostScript 2020/9/25 La partie 5 a été publiée. https://qiita.com/Ksuke/items/6595323e79892acf9a7a

Résumé du code

Si vous l'ajoutez après le code précédent, cela devrait fonctionner.

Edition de fonction

Résumé du code(Edition de fonction)


#Une fonction qui renvoie un ensemble de points à la frontière
def genBorderPointSpace(imgProjectSpace):

    #6 directions dans l'espace(Haut, bas, gauche, droite, avant et arrière)Créez une carte du nombre de points autour de chaque point en effectuant un traitement équivalent au filtre d'addition de
    locationPointMap = np.stack([
        imgProjectSpace[2:,1:-1,1:-1],
        imgProjectSpace[:-2,1:-1,1:-1],
        imgProjectSpace[1:-1,2:,1:-1],
        imgProjectSpace[1:-1,:-2,1:-1],
        imgProjectSpace[1:-1,1:-1,2:],
        imgProjectSpace[1:-1,1:-1,:-2],
    ]).sum(axis=0,dtype=np.int8)
    
    #Restaurer la taille de l'espace
    locationPointMap = np.insert(locationPointMap, (0,-1), 0, axis=0)
    locationPointMap = np.insert(locationPointMap, (0,-1), 0, axis=1)
    locationPointMap = np.insert(locationPointMap, (0,-1), 0, axis=2)

    #A partir de la carte du nombre de points environnants pour chaque point, le nombre de points environnants n'est ni 0 ni 6.(Il y a des points autour et non entourés de points)Créez un espace avec des points
    borderPointSpace = np.where((0<locationPointMap)&(locationPointMap<6)&(imgProjectSpace==1),1,0).astype(np.int8)
    
    #Renvoie un espace qui laisse des points qui ne sont pas entourés de points
    return borderPointSpace
    
#Une fonction qui renvoie un ensemble de points juste à l'intérieur de la limite
def genInsidePointSpace(imgProjectSpace,borderPointSpace):

    #Créer un espace qui laisse des points près de la limite(Ne quittez pas la partie frontière)
    nearBorderPointSpace = np.stack([
        borderPointSpace[2:,1:-1,1:-1],
        borderPointSpace[:-2,1:-1,1:-1],
        borderPointSpace[1:-1,2:,1:-1],
        borderPointSpace[1:-1,:-2,1:-1],
        borderPointSpace[1:-1,1:-1,2:],
        borderPointSpace[1:-1,1:-1,:-2],
        borderPointSpace[1:-1,1:-1,1:-1]*-6
    ]).sum(axis=0,dtype=np.int8)
    
    #Taille de retour
    nearBorderPointSpace = np.insert(nearBorderPointSpace, (0,-1), 0, axis=0)
    nearBorderPointSpace = np.insert(nearBorderPointSpace, (0,-1), 0, axis=1)
    nearBorderPointSpace = np.insert(nearBorderPointSpace, (0,-1), 0, axis=2)

    #Créez un espace près de la limite qui laisse les points à l'intérieur du groupe de points d'origine
    insidePointSpace = np.where(((0<nearBorderPointSpace)&(imgProjectSpace==1)),1,0)
    
    return insidePointSpace
    
    #Une fonction qui détermine la face d'un polygone à partir d'un ensemble de coordonnées
def genPolygon(borderCoords,insideCoords):
    
    #Combinez deux types de sommets(Correspond au traitement de la figure 4)
    coords = np.concatenate([borderCoords,insideCoords])

    #Générer une carte montrant le type de chaque sommet(0 est la frontière et 1 est à l'intérieur)
    coordStat = np.zeros((len(coords)),dtype=np.uint8)
    coordStat[len(borderCoords):len(borderCoords)+len(insideCoords)] = 1
    
    #Génère un groupe pyramidal triangulaire composé de 4 points du groupe apex(Correspond au traitement de la Fig.5)
    triPyramids = Delaunay(coords).simplices
    
    #Vérifiez combien de sommets intérieurs sont inclus pour chaque pyramide triangulaire(Correspond au traitement de la Fig.6)
    triPyramidsStat = coordStat[triPyramids].sum(axis=1)
    
    #Parmi les pyramides triangulaires, celles qui incluent à la fois le sommet intérieur et le sommet de la frontière sont extraites et utilisées comme une pyramide triangulaire valide.(Correspond au traitement de la figure 7)
    #(Ici, la pyramide triangulaire dans l'espace de marge à l'extérieur de l'objet composé uniquement des sommets de bordure et la pyramide triangulaire dans l'espace de marge à l'intérieur de l'objet composé uniquement des sommets intérieurs sont supprimées.)
    effectiveTriPyramids = triPyramids[np.where((triPyramidsStat!=0)&(triPyramidsStat!=4))[0]]
    
    #Visage candidat pour objet(3 index des sommets)Liste à mettre(Retirez l'excédent plus tard)
    faces = []
        
    #Extraire une face de pyramide triangulaire valide en tant que face objet(Correspond à la partie focalisée sur la ligne dans l'explication de l'image de l'article)
    for coordIndexs in effectiveTriPyramids:
        faces.append([coordIndexs[0],coordIndexs[1],coordIndexs[2]])
        faces.append([coordIndexs[0],coordIndexs[1],coordIndexs[3]])
        faces.append([coordIndexs[0],coordIndexs[2],coordIndexs[3]])
        faces.append([coordIndexs[1],coordIndexs[2],coordIndexs[3]])
        
    #Trier l'index du sommet de la face candidate de l'objet et supprimer la duplication
    faces = np.array(faces)
    faces.sort(axis=1)
    faces = np.unique(faces,axis=0)
    
    #Vérifiez combien de sommets intérieurs sont inclus pour chaque face(Correspond au traitement de la Fig.8)
    faceStat = coordStat[faces].sum(axis=1)
    
    #Retirez une face qui ne contient aucun sommet intérieur(Correspond au traitement de la figure 9)
    faces = faces[np.where(faceStat==0)]
        
    #Renvoie les sommets et les faces
    return borderCoords,faces

Code d'exécution

Résumé du code(Code d'exécution)



#À partir du groupe de points dans l'espace superposé, retirez les points à la limite qui est en contact avec l'extérieur.
borderPointSpace = genBorderPointSpace(imgProjectSpace)

#À partir du groupe de points dans l'espace superposé, retirez les points immédiatement à l'intérieur de la partie frontière
insidePointSpace = genInsidePointSpace(imgProjectSpace,borderPointSpace)

#Extraire les coordonnées d'un point de l'espace
borderCoords = binary2coords(borderPointSpace)
insideCoords = binary2coords(insidePointSpace)

#Polygone à partir de deux types de coordonnées(Pour être exact, la définition de la face qui relie les sommets qui sont des polygones)Générer un
coords,faces = genPolygon(borderCoords,insideCoords)

#Changement du format pour passer les visages au mélangeur
faces = [[face[0],face[1],face[2],face[0]] for face in faces]

print("step04:porigon generate success\n")

#Pour l'affichage de confirmation ci-dessous(Cela n'a rien à voir avec le flux principal, donc il disparaîtra probablement au prochain tour)

    
#Dessiner des sommets
addObj(coords=coords,faces=faces,name = "porigon",offset=[-125,-50,-50])
addObj(coords=borderCoords,name = "borderCoords",offset=[-50,-85,-50])
addObj(coords=insideCoords,name = "insideCoords",offset=[-50,-15,-50])

Recommended Posts