web-dev-qa-db-fra.com

Détection / suivi de rectangle à l'aide d'OpenCV

Ce dont j'ai besoin

Je travaille actuellement sur un jeu un peu en réalité augmentée. Le contrôleur que le jeu utilise (je parle ici du périphérique d'entrée physique) est un morceau de papier rectangulaire et monochrome. Je dois détecter la position, la rotation et la taille de ce rectangle dans le flux de capture de la caméra. La détection doit être invariante sur l'échelle et invariante sur la rotation le long des axes X et Y.

L'invariance d'échelle est nécessaire dans le cas où l'utilisateur éloigne le papier ou le rapproche de la caméra. Je n'ai pas besoin de connaître la distance du rectangle, donc l'invariance d'échelle se traduit par l'invariance de taille.

L'invariance de rotation est nécessaire dans le cas où l'utilisateur incline le rectangle le long de son axe X et/ou Y local. Une telle rotation change la forme du papier du rectangle au trapèze. Dans ce cas, le cadre de sélection orienté objet peut être utilisé pour mesurer la taille du papier.

Ce que j'ai fait

Au début, il y a une étape d'étalonnage. Une fenêtre affiche le flux de la caméra et l'utilisateur doit cliquer sur le rectangle. Au clic, la couleur du pixel pointé par la souris est prise comme couleur de référence. Les cadres sont convertis en espace colorimétrique HSV pour améliorer la distinction des couleurs. J'ai 6 curseurs qui ajustent les seuils supérieur et inférieur pour chaque canal. Ces seuils sont utilisés pour binariser l'image (en utilisant la fonction inRange d'opencv).
Après cela, j'érode et dilate l'image binaire pour supprimer le bruit et unir les morceaux de nerby (en utilisant les fonctions erode et dilate d'opencv).
L'étape suivante consiste à trouver les contours (en utilisant la fonction findContours d'opencv) dans l'image binaire. Ces contours sont utilisés pour détecter les plus petits rectangles orientés (en utilisant la fonction minAreaRect d'opencv). Comme résultat final, j'utilise le rectangle avec la plus grande surface.

Une courte conclusion de la procédure:

  1. Prenez un cadre
  2. Convertissez ce cadre en HSV
  3. Binarisez-le (en utilisant la couleur que l'utilisateur a sélectionnée et les seuils des curseurs)
  4. Appliquer morph ops (éroder et dilater)
  5. Trouver les contours
  6. Obtenez la plus petite boîte boudante orientée de chaque contour
  7. Prenez le plus grand de ces cadres englobants comme résultat

Comme vous l'avez peut-être remarqué, je ne profite pas des connaissances sur la forme réelle du papier, simplement parce que je ne sais pas comment utiliser correctement ces informations.

J'ai également pensé à utiliser les algorithmes de suivi d'opencv. Mais trois raisons m'ont empêché de les utiliser:

  1. Invariance d'échelle: pour autant que je sache certains des algorithmes, certains ne prennent pas en charge différentes échelles de l'objet.
  2. Prédiction de mouvement: certains algorithmes utilisent la prédiction de mouvement pour de meilleures performances, mais l'objet que je surveille se déplace de manière complètement aléatoire et donc imprévisible.
  3. Simplicité: je cherche juste un rectangle monochrome dans une image, rien de spécial comme le suivi d'une voiture ou d'une personne.

Voici une - relativement - bonne prise (image binaire après érode et dilate) ok

et voici une mauvaise bad

La question

Comment améliorer la détection en général et surtout pour être plus résistant aux changements d'éclairage?

Mise à jour

Ici sont quelques images brutes à tester.

Ne pouvez-vous pas simplement utiliser un matériau plus épais?
Oui, je le peux et je le fais déjà (malheureusement, je ne peux pas accéder à ces pièces pour le moment). Cependant, le problème persiste. Même si j'utilise du matériel comme du carton. Il n'est pas plié aussi facilement que le papier, mais on peut toujours le plier.

Comment obtenez-vous la taille, la rotation et la position du rectangle?
La fonction minAreaRect d'opencv renvoie un objet RotatedRect. Cet objet contient toutes les données dont j'ai besoin.

Remarque
Parce que le rectangle est monochrome, il n'y a aucune possibilité de distinguer le haut et le bas ou la gauche et la droite. Cela signifie que la rotation est toujours dans la plage [0, 180] ce qui convient parfaitement à mes besoins. Le rapport des deux côtés du rect est toujours w:h > 2:1. Si le rectangle était un carré, la plage de rotation changerait en [0, 90], mais cela peut être considéré comme hors de propos ici.

Comme suggéré dans les commentaires, je vais essayer l'égalisation de l'histogramme pour réduire les problèmes de luminosité et jeter un œil à ORB, SURF et SIFT.

Je ferai le point sur les progrès.

14
Timo

Je sais que cela fait un moment que je n'ai pas posé la question. J'ai récemment continué sur le sujet et résolu mon problème (mais pas via la détection de rectangle).

Changements

  • Utiliser du bois pour renforcer mes contrôleurs (les "rectangles") comme ci-dessous.
  • Placé 2 ArUco marqueurs sur chaque contrôleur.

Controller

Comment ça fonctionne

  • Convertissez le cadre en niveaux de gris,
  • sous-échantillonner (pour augmenter les performances lors de la détection),
  • égaliser l'histogramme en utilisant cv::equalizeHist,
  • rechercher des marqueurs à l'aide de cv::aruco::detectMarkers,
  • corréler les marqueurs (si plusieurs contrôleurs),
  • analyser les marqueurs (position et rotation),
  • calculer le résultat et appliquer une correction d'erreur.

Il s'est avéré que la détection du marqueur est très robuste aux changements d'éclairage et aux angles de vision différents, ce qui me permet de sauter toutes les étapes de calibrage.

J'ai placé 2 marqueurs sur chaque contrôleur pour augmenter encore plus la robustesse de détection. Les deux marqueurs doivent être détectés une seule fois (pour mesurer leur corrélation). Après cela, il suffit de trouver un seul marqueur par contrôleur car l'autre peut être extrapolé à partir de la corrélation précédemment calculée.

Voici un résultat de détection dans un environnement lumineux:

Detection in a bright environment

dans un environnement plus sombre:

Detection in a dark environment

et lorsque vous cachez l'un des marqueurs (le point bleu indique la position du marqueur extrapolé):

Detection of missing markers

Les échecs

La détection de forme initiale que j'ai implémentée n'a pas bien fonctionné. Il était très fragile aux changements d'éclairage. De plus, il a nécessité une première étape d'étalonnage.

Après l'approche de détection de forme, j'ai essayé SIFT et ORB en combinaison avec la force brute et l'assembleur tricoté pour extraire et localiser les caractéristiques dans les cadres. Il s'est avéré que les objets monochromes ne fournissent pas beaucoup de points clés (quelle surprise). Les performances de SIFT étaient de toute façon terribles (environ 10 ips @ 540p). J'ai tracé des lignes et d'autres formes sur le contrôleur, ce qui a permis de disposer de plus de points clés. Cependant, cela n'a pas apporté d'énormes améliorations.

3
Timo

Le canal H dans l'espace HSV est la teinte, et il n'est pas sensible au changement de lumière. Gamme rouge dans environ [150,180].

Sur la base des informations mentionnées, je fais les travaux suivants.

  1. Changez dans l'espace HSV, divisez le canal H, le seuil et normalisez-le.
  2. Appliquer les opérations de morphing (ouvert)
  3. Trouvez les contours, filtrez par certaines propriétés (largeur, hauteur, surface, rapport, etc.).

PS. Je ne peux pas récupérer l'image que vous téléchargez sur la boîte de dépôt à cause du RÉSEAU. Donc, j'utilise juste crop le côté droit de votre deuxième image comme entrée.

enter image description here

imgname = "src.png"
img = cv2.imread(imgname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

## Split the H channel in HSV, and get the red range
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
h[h<150]=0
h[h>180]=0

## normalize, do the open-morp-op
normed = cv2.normalize(h, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8UC1)
kernel = cv2.getStructuringElement(shape=cv2.MORPH_ELLIPSE, ksize=(3,3))
opened = cv2.morphologyEx(normed, cv2.MORPH_OPEN, kernel)
res = np.hstack((h, normed, opened))
cv2.imwrite("tmp1.png", res)

Maintenant, nous obtenons le résultat comme ceci (h, normé, ouvert):

enter image description here

Trouvez ensuite les contours et filtrez-les.

contours = cv2.findContours(opened, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))[-2]

bboxes = []
rboxes = []
cnts = []
dst = img.copy()
for cnt in contours:
    ## Get the stright bounding rect
    bbox = cv2.boundingRect(cnt)
    x,y,w,h = bbox
    if w<30 or h < 30 or w*h < 2000 or w > 500:
        continue

    ## Draw rect
    cv2.rectangle(dst, (x,y), (x+w,y+h), (255,0,0), 1, 16)

    ## Get the rotated rect
    rbox = cv2.minAreaRect(cnt)
    (cx,cy), (w,h), rot_angle = rbox
    print("rot_angle:", rot_angle)  

    ## backup 
    bboxes.append(bbox)
    rboxes.append(rbox)
    cnts.append(cnt)

Le résultat est comme ceci:

rot_angle: -2.4540319442749023
rot_angle: -1.8476102352142334

enter image description here

Du fait de la balise rectangulaire bleue dans l'image source, la carte est divisée en deux côtés. Mais une image propre n'aura aucun problème.

2
Kinght 金