Почему cv2.fitEllipse считает контуры стороной эллипса?

У меня возникла странная проблема с cv2.fitEllipse. Когда я пытаюсь получить этот эллипс этой формы,  form

который может быть определен (извините за длинный список определений) как:

import numpy as np
test=np.zeros((400,1000))
test[241, 113]=1
test[241, 114]=1
test[242, 112]=1
test[242, 113]=1
test[242, 114]=1
test[242, 115]=1
test[242, 116]=1
test[242, 118]=1
test[242, 119]=1
test[242, 120]=1
test[242, 121]=1
test[243, 111]=1
test[243, 112]=1
test[243, 113]=1
test[243, 114]=1
test[243, 115]=1
test[243, 116]=1
test[243, 117]=1
test[243, 118]=1
test[243, 119]=1
test[243, 120]=1
test[243, 121]=1
test[244, 110]=1
test[244, 111]=1
test[244, 112]=1
test[244, 113]=1
test[244, 114]=1
test[244, 115]=1
test[244, 116]=1
test[244, 117]=1
test[244, 118]=1
test[244, 119]=1
test[244, 120]=1
test[245, 109]=1
test[245, 110]=1
test[245, 111]=1
test[245, 112]=1
test[245, 113]=1
test[245, 114]=1
test[245, 115]=1
test[245, 116]=1
test[245, 117]=1
test[245, 118]=1
test[245, 119]=1
test[245, 120]=1
test[246, 109]=1
test[246, 110]=1
test[246, 111]=1
test[246, 112]=1
test[246, 113]=1
test[246, 114]=1
test[246, 115]=1
test[246, 116]=1
test[246, 117]=1
test[246, 118]=1
test[246, 119]=1
test[247, 109]=1
test[247, 110]=1
test[247, 111]=1
test[247, 112]=1
test[247, 113]=1
test[247, 114]=1
test[247, 115]=1
test[247, 116]=1
test[247, 117]=1
test[247, 118]=1
test[247, 119]=1
test[248, 108]=1
test[248, 109]=1
test[248, 110]=1
test[248, 111]=1
test[248, 112]=1
test[248, 113]=1
test[248, 114]=1
test[248, 115]=1
test[248, 116]=1
test[248, 117]=1
test[248, 118]=1
test[248, 119]=1
test[249, 109]=1
test[249, 110]=1
test[249, 111]=1
test[249, 112]=1
test[249, 113]=1
test[249, 114]=1
test[249, 115]=1
test[249, 116]=1
test[249, 117]=1
test[249, 118]=1
test[249, 119]=1
test[250, 109]=1
test[250, 110]=1
test[250, 111]=1
test[250, 112]=1
test[250, 113]=1
test[250, 114]=1
test[250, 115]=1
test[250, 116]=1
test[250, 117]=1
test[250, 118]=1
test[250, 119]=1
test[250, 120]=1
test[251, 109]=1
test[251, 110]=1
test[251, 111]=1
test[251, 112]=1
test[251, 113]=1
test[251, 114]=1
test[251, 115]=1
test[251, 116]=1
test[251, 117]=1
test[251, 118]=1
test[251, 119]=1
test[251, 120]=1
test[252, 109]=1
test[252, 110]=1
test[252, 111]=1
test[252, 112]=1
test[252, 113]=1
test[252, 114]=1
test[252, 115]=1
test[252, 116]=1
test[252, 117]=1
test[252, 118]=1
test[252, 119]=1
test[252, 120]=1
test[252, 121]=1
test[253, 109]=1
test[253, 112]=1
test[253, 113]=1
test[253, 114]=1
test[253, 115]=1
test[253, 116]=1
test[253, 117]=1
test[253, 118]=1
test[253, 119]=1
test[253, 120]=1
test[253, 121]=1
test[254, 112]=1
test[254, 113]=1
test[254, 114]=1
test[254, 115]=1
test[254, 116]=1
test[254, 117]=1
test[254, 118]=1
test[254, 119]=1
test[254, 120]=1
test[254, 121]=1
test[255, 110]=1
test[255, 112]=1
test[255, 113]=1
test[255, 114]=1
test[255, 115]=1
test[255, 116]=1
test[255, 117]=1
test[255, 118]=1
test[255, 119]=1
test[255, 120]=1
test[255, 121]=1
test[255, 122]=1
test[256, 108]=1
test[256, 110]=1
test[256, 111]=1
test[256, 112]=1
test[256, 113]=1
test[256, 114]=1
test[256, 115]=1
test[256, 116]=1
test[256, 117]=1
test[256, 118]=1
test[256, 119]=1
test[256, 120]=1
test[256, 121]=1
test[256, 122]=1
test[256, 123]=1
test[257, 106]=1
test[257, 107]=1
test[257, 108]=1
test[257, 109]=1
test[257, 110]=1
test[257, 111]=1
test[257, 112]=1
test[257, 113]=1
test[257, 114]=1
test[257, 115]=1
test[257, 116]=1
test[257, 117]=1
test[257, 118]=1
test[257, 119]=1
test[257, 120]=1
test[257, 121]=1
test[257, 122]=1
test[257, 123]=1
test[257, 124]=1
test[258, 103]=1
test[258, 104]=1
test[258, 105]=1
test[258, 106]=1
test[258, 107]=1
test[258, 108]=1
test[258, 109]=1
test[258, 110]=1
test[258, 111]=1
test[258, 112]=1
test[258, 113]=1
test[258, 114]=1
test[258, 115]=1
test[258, 116]=1
test[258, 117]=1
test[258, 118]=1
test[258, 119]=1
test[258, 120]=1
test[258, 121]=1
test[258, 122]=1
test[258, 123]=1
test[258, 124]=1
test[259, 103]=1
test[259, 104]=1
test[259, 105]=1
test[259, 106]=1
test[259, 107]=1
test[259, 108]=1
test[259, 109]=1
test[259, 110]=1
test[259, 111]=1
test[259, 112]=1
test[259, 113]=1
test[259, 114]=1
test[259, 115]=1
test[259, 116]=1
test[259, 117]=1
test[259, 118]=1
test[259, 119]=1
test[259, 120]=1
test[259, 121]=1
test[259, 122]=1
test[259, 123]=1
test[259, 124]=1
test[260, 106]=1
test[260, 107]=1
test[260, 108]=1
test[260, 109]=1
test[260, 110]=1
test[260, 111]=1
test[260, 112]=1
test[260, 113]=1
test[260, 114]=1
test[260, 115]=1
test[260, 116]=1
test[260, 117]=1
test[260, 118]=1
test[260, 119]=1
test[260, 120]=1
test[260, 121]=1
test[260, 122]=1
test[260, 123]=1
test[260, 124]=1
test[261, 107]=1
test[261, 110]=1
test[261, 111]=1
test[261, 112]=1
test[261, 113]=1
test[261, 114]=1
test[261, 115]=1
test[261, 116]=1
test[261, 117]=1
test[261, 118]=1
test[261, 119]=1
test[261, 120]=1
test[261, 121]=1
test[261, 122]=1
test[261, 123]=1
test[261, 124]=1
test[261, 125]=1
test[262, 115]=1
test[262, 116]=1
test[262, 117]=1
test[262, 118]=1
test[262, 119]=1
test[262, 120]=1
test[262, 121]=1
test[262, 122]=1
test[262, 123]=1
test[262, 124]=1
test[262, 125]=1
test[262, 126]=1
test[262, 127]=1
test[262, 128]=1
test[263, 116]=1
test[263, 117]=1
test[263, 118]=1
test[263, 119]=1
test[263, 120]=1
test[263, 121]=1
test[263, 122]=1
test[263, 123]=1
test[263, 127]=1
test[263, 128]=1
test[263, 129]=1
test[263, 130]=1
test[263, 131]=1
test[263, 132]=1
test[263, 133]=1
test[263, 134]=1
test[263, 135]=1
test[264, 117]=1
test[264, 118]=1
test[264, 119]=1
test[264, 120]=1
test[264, 121]=1
test[264, 122]=1
test[264, 123]=1
test[264, 127]=1
test[264, 128]=1
test[264, 129]=1
test[264, 130]=1
test[264, 131]=1
test[264, 132]=1
test[264, 133]=1
test[264, 134]=1
test[264, 135]=1
test[264, 136]=1
test[264, 138]=1
test[264, 139]=1
test[265, 118]=1
test[265, 119]=1
test[265, 120]=1
test[265, 121]=1
test[265, 122]=1
test[265, 123]=1
test[265, 131]=1
test[265, 134]=1
test[265, 135]=1
test[265, 136]=1
test[265, 137]=1
test[265, 138]=1
test[265, 139]=1
test[265, 140]=1
test[265, 141]=1
test[265, 142]=1
test[265, 143]=1
test[266, 120]=1
test[266, 121]=1
test[266, 122]=1
test[266, 135]=1
test[266, 136]=1
test[266, 137]=1
test[266, 138]=1
test[266, 139]=1
test[266, 140]=1
test[266, 141]=1
test[266, 142]=1
test[266, 143]=1
test[266, 144]=1
test[266, 145]=1
test[267, 121]=1
test[267, 122]=1
test[267, 142]=1
test[267, 143]=1
test[267, 144]=1
test[267, 145]=1
test[267, 146]=1
test[267, 147]=1
test[267, 148]=1
test[267, 149]=1
test[267, 150]=1
test[267, 151]=1
test[268, 147]=1
test[268, 148]=1
test[268, 149]=1
test[268, 150]=1
test[268, 151]=1
test[268, 152]=1
test[268, 153]=1
test[268, 154]=1
test[268, 155]=1
test[268, 156]=1
test[268, 157]=1
test[269, 149]=1
test[269, 151]=1
test[269, 152]=1
test[269, 153]=1
test[269, 154]=1
test[269, 155]=1
test[269, 156]=1
test[269, 157]=1
test[270, 156]=1

У меня есть следующий эллипс:  плохой эллипс

Чтобы получить этот результат, вы можете просто запустить следующий код с python

import cv2
import numpy as np
import itertools
import matplotlib.pyplot as mpl
def getContours_test(clusters):
        copy_clusters=clusters.copy()
        int_cnts=[]
        for label in np.unique(clusters):
                mask = np.zeros(clusters.shape, dtype="uint8")
                mask[clusters == label] = 255
                cnts, hierarchy = \
                        cv2.findContours(mask.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
                int_cnts.append(cnts)
        list_contours=list(itertools.chain.from_iterable(int_cnts))
        return copy_clusters, list_contours


def getEllipse_test(clusters, list_contours):
        for index,cnt in enumerate(list_contours):
                map_ellipse=np.ones(clusters.shape).astype(np.uint8)
                cimg = np.zeros_like(clusters)
                cv2.drawContours(cimg, list_contours, index, color=255, thickness=-1)
                ellipse=cv2.fitEllipse(cnt)
                cv2.ellipse(map_ellipse,ellipse,(255,0,0))
        return map_ellipse

test_result, test_contours = getContours_test(test)
test_ellipse = getEllipse_test(test_result,test_contours)
mpl.contourf(test_result)
mpl.contour(test_ellipse)
mpl.show()

Я попытался проверить, смогу ли я воспроизвести то же поведение с аналогичной, но более простой формой (новая форма заполняется, а исходная - контурной): 2 формы, но на этот раз я получаю правильный результат с тем же код хороший эллипс

test2=np.zeros((400,1000))
test2[240:260,110:120]=1
test2[260:265,117:134]=1
test2[265:270,134:155]=1
test_result2, test_contours2 = getContours_test(test2)
test_ellipse2 = getEllipse_test(test_result2,test_contours2)
mpl.contourf(test_result2)
mpl.contour(test_ellipse2)
mpl.show()

Есть идеи, почему это происходит? cv2.fitEllipse должно иметь достаточно точек контура для оценки правильного эллипса исходной формы.

РЕДАКТИРОВАТЬ: следуя замечанию @ user2518618, я попытался включить cv2.convexHull () в метод:

def remove_convex(clusters,list_contours):
        for index,cnt in enumerate(list_contours):
                map_ellipse=np.ones(clusters.shape).astype(np.uint8)
                cimg = np.zeros_like(clusters)
                hull = cv2.convexHull(cnt)
                cv2.drawContours(cimg, hull, -1, color=255, thickness=-1)
        return cimg

в результате выбираются точки, подходящие для оценки эллипса:

test_hull = remove_convex(test_result,test_contours)

mpl.contour(test_hull)
mpl.show()

очков, оставшихся после cv2.convexHull ()

Но если я добавлю ellipse=cv2.fitEllipse(hull) после hull = cv2.convexHull(cnt) в remove_convex, я получу следующую ошибку: error: (-201) Количество точек должно быть> = 5 в функции cvFitEllipse2

Хотя я понимаю ошибку, поскольку в документации opencv говорится, что выпуклая оболочка возвращает четыре угла прямоугольника, я действительно не понимаю, почему я просто не могу использовать точки, указанные выше, чтобы соответствовать моему новому эллипсу.


person dcoppin    schedule 07.03.2018    source источник
comment
Я не мог найти лучшего способа описать 2D-массив - 2D-массив небольших целых чисел, используя библиотеку компьютерного зрения ... как насчет изображения в градациях серого? (результирующий PNG примерно в 7 раз меньше кода, который вы использовали для его создания)   -  person Dan Mašek    schedule 08.03.2018


Ответы (1)


fitEllipse () работал бы намного лучше, если бы ваша форма выглядела как эллипс.

Ваша фигура очень шумная, и трудно найти эллипс, который подходил бы к ней должным образом. Фактически, оба эллипса, найденные алгоритмом, проходят рядом с точками вашей формы, и оба могут быть хорошими приближениями.

Вам следует предварительно обработать форму, чтобы она больше походила на эллипс. Я бы попробовал использовать cv2.convexHull () перед fitEllipse ()

person user2518618    schedule 08.03.2018
comment
Это действительно хорошая идея. Однако cv2.convexHull () возвращает только 4 балла (см. Документ opencv), в то время как мне нужно как минимум 5 баллов, чтобы можно было уместить эллипс. hull = cv2.convexHull(cnt) и ellipse=cv2.fitEllipse(hull) дает: error: (-201) Number of points should be >= 5 in function cvFitEllipse2 - person dcoppin; 08.03.2018