J'essaie de trouver le contour d'un objet rectangle avec un coin arrondi dans une image. J'ai essayé HoughLinesP
et findContours
, mais je n'ai pas obtenu le résultat souhaité.
Je veux trouver le rectangle comme ceci:
Code:
import cv2
import matplotlib.pyplot as plt
import util
image = cv2.imread("./img/findrect0.png", 1)
gray = util.grayImage(image)
edges = cv2.Canny(image, 50, 200)
lines = cv2.HoughLinesP(edges, 1, cv2.cv.CV_PI/180, 50, minLineLength=50, maxLineGap=10)[0]
linesImage = image.copy()
util.drawLines(linesImage, lines, thickness=10)
contoursImage = image.copy()
(contours, hierarchy) = cv2.findContours(gray.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
util.drawContours(contoursImage, contours, thickness=10)
util.showOpenCVImagesGrid([image, edges, linesImage, contoursImage], 2, 2, titles=["original image", "canny image", "lines image", "contours image"])
util:
import cv2
import math
import matplotlib.pyplot as plt
def showOpenCVImagesGrid(images, x, y, titles=None, axis="on"):
fig = plt.figure()
i = 1
for image in images:
copy = image.copy()
channel = len(copy.shape)
cmap = None
if channel == 2:
cmap = "gray"
Elif channel == 3:
copy = cv2.cvtColor(copy, cv2.COLOR_BGR2RGB)
Elif channel == 4:
copy = cv2.cvtColor(copy, cv2.COLOR_BGRA2RGBA)
fig.add_subplot(x, y, i)
if titles is not None:
plt.title(titles[i-1])
plt.axis(axis)
plt.imshow(copy, cmap=cmap)
i += 1
plt.show()
def grayImage(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
return gray
def drawLines(image, lines, thickness=1):
for line in lines:
# print("line="+str(line))
cv2.line(image, (line[0], line[1]), (line[2], line[3]),
(0, 0, 255), thickness)
def drawContours(image, contours, thickness=1):
i = 0
for contour in contours:
cv2.drawContours(image, [contours[i]], i, (0, 255, 0), thickness)
area = cv2.contourArea(contour)
i += 1
J'utilise Python 2.7.13
et OpenCV 2.4.13.3
.
J'ai pensé étendre ces lignes et obtenir des points d'intersection de lignes. Enfin, je vais obtenir quatre coordonnées de rectangle . Mais si l'image est plus complexe, je ne sais pas comment traiter.
Vous devez trouver le rectangle de délimitation des contours trouvés.
img = cv2.imread("image.png", -1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
binary = cv2.bitwise_not(gray)
(_,contours,_) = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for contour in contours:
(x,y,w,h) = cv2.boundingRect(contour)
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)
Vous pouvez trouver un rectangle englobant des points non nuls.
image = cv2.imread("./img/findrect0.png", 1)
gray = util.grayImage(image)
gray_inv = cv2.bitwise_not(gray)
points = cv2.findNonZero(gray)
rect = cv2.boundingRect(points)
J'ai pensé étendre ces lignes et obtenir des points d'intersection de lignes. Enfin, je vais obtenir quatre coordonnées de rectangle.
Plus ou moins, c'est une bonne approche si les contours ne vous conviennent pas. En effet, comme tu le dis,
Mais si l'image est plus complexe, je ne sais pas comment gérer.
il y a des complications à gérer. Le principal problème est que la détection de ligne typique ne vous donne pas des segments de ligne parfaits à chaque fois. Vous pouvez avoir plusieurs segments de ligne le long de la même ligne, empilés dans le sens de la longueur ou se chevauchant plusieurs fois. De plus, vous devrez segmenter les lignes automatiquement de façon à ne pas rechercher l'intersection de lignes parallèles.
Cependant, ces deux problèmes ne sont pas trop difficiles à traiter. J'ai répondu à une question il y a quelque temps sur ce site à propos de la recherche de points d'intersection à partir de HoughLinesP
qui utilise beaucoup des suggestions ci-dessous, mais qui n'est pas aussi robuste (la segmentation des lignes en deux groupes, par exemple, a été effectuée beaucoup plus naïvement ), mais cela devrait vous donner un bon point de départ.
Après avoir détecté les lignes, vous devez les segmenter en groupes ou en segments parallèles. Si votre rectangle a une orientation définie, vous pouvez simplement filtrer les lignes en fonction de cela, ce qui serait très simple. Mais si le rectangle peut être orienté dans n'importe quelle orientation, vous aurez besoin d'un autre moyen pour les segmenter. Vous pouvez utiliser k - signifie grouper avec k = 2 pour trouver les deux angles principaux, et placer les lignes correspondant à un angle dans un bac et les lignes correspondant à l'autre angle dans un autre bac et trouver les intersections des lignes d'un bac avec les lignes de l'autre bac. Ce qui est bien avec cette approche, c’est que cela fonctionnerait pour n’importe quel parallélogramme. Vous pouvez également rejeter les lignes de chaque groupe si elles ne sont pas dans les limites (par exemple, 10 degrés) d'être perpendiculaires à l'angle moyen par rapport à l'autre groupe, ou quelque chose du genre, si vous souhaitez vous en tenir aux rectangles.
Une fois que vous avez réparti toutes les lignes en conséquence, vous pouvez calculer leurs points d'intersection. Il existe en fait une formule de Nice utilisant des déterminants pour calculer les points d'intersection entre deux lignes étant donné deux points sur la ligne, que vous avez déjà depuis les points de terminaison. Donc, c'est pratique! Chaque ligne de la case 0 aura un point d'intersection avec la ligne de la case 1, et c'est tout ce que vous devez faire.
Donc, à la fin, vous auriez 4 groupes de points d’intersection. Une option consiste simplement à les regrouper, toujours avec k -means avec k = 4, et vous obtiendrez les centroïdes de ces quatre groupes de points représentant les coins de votre rectangle. Bien sûr, comme vous avez utilisé beaucoup d'étapes d'approximation, vos points ne définiront pas exactement un rectangle. Vous devrez donc ajuster le rectangle le plus proche possible à ces points. Ou bien, au lieu de k -means, une autre méthode consisterait à rechercher un sous-ensemble de vos nombreux points d’intersection qui représente le plus fidèlement un rectangle, et then s’ajuste au rectangle le plus proche. Probablement un moyen d'utiliser la régression linéaire, les moindres carrés, RANSAC, etc. pour résoudre ce problème. Ou si vous voulez, vous pouvez simplement trouver le rectangle englobant des quatre points avec boundingRect()
.