Линейный итератор OpenCV 3.0

Я хочу использовать LineIterator в OpenCV 3.0. используя Python, он все еще доступен с OpenCV 3.0, созданным для Python? Кажется, что все ответы в Интернете указывают на cv.InitLineIterator, который является частью модуля cv. Я пытался импортировать этот модуль, но похоже, что он не включен в текущую сборку. Его переименовали или строго просто удалили?


person mohikhsan    schedule 01.09.2015    source источник


Ответы (5)


arrow_upward
106
arrow_downward

Я решил свою проблему. Итератор строки, похоже, недоступен в библиотеке cv2. Поэтому я сделал свой собственный итератор строк. Циклы не используются, так что это должно быть довольно быстро. Вот код, если кому нужно:

def createLineIterator(P1, P2, img):
    """
    Produces and array that consists of the coordinates and intensities of each pixel in a line between two points

    Parameters:
        -P1: a numpy array that consists of the coordinate of the first point (x,y)
        -P2: a numpy array that consists of the coordinate of the second point (x,y)
        -img: the image being processed

    Returns:
        -it: a numpy array that consists of the coordinates and intensities of each pixel in the radii (shape: [numPixels, 3], row = [x,y,intensity])
    """
   #define local variables for readability
   imageH = img.shape[0]
   imageW = img.shape[1]
   P1X = P1[0]
   P1Y = P1[1]
   P2X = P2[0]
   P2Y = P2[1]

   #difference and absolute difference between points
   #used to calculate slope and relative location between points
   dX = P2X - P1X
   dY = P2Y - P1Y
   dXa = np.abs(dX)
   dYa = np.abs(dY)

   #predefine numpy array for output based on distance between points
   itbuffer = np.empty(shape=(np.maximum(dYa,dXa),3),dtype=np.float32)
   itbuffer.fill(np.nan)

   #Obtain coordinates along the line using a form of Bresenham's algorithm
   negY = P1Y > P2Y
   negX = P1X > P2X
   if P1X == P2X: #vertical line segment
       itbuffer[:,0] = P1X
       if negY:
           itbuffer[:,1] = np.arange(P1Y - 1,P1Y - dYa - 1,-1)
       else:
           itbuffer[:,1] = np.arange(P1Y+1,P1Y+dYa+1)
   elif P1Y == P2Y: #horizontal line segment
       itbuffer[:,1] = P1Y
       if negX:
           itbuffer[:,0] = np.arange(P1X-1,P1X-dXa-1,-1)
       else:
           itbuffer[:,0] = np.arange(P1X+1,P1X+dXa+1)
   else: #diagonal line segment
       steepSlope = dYa > dXa
       if steepSlope:
           slope = dX.astype(np.float32)/dY.astype(np.float32)
           if negY:
               itbuffer[:,1] = np.arange(P1Y-1,P1Y-dYa-1,-1)
           else:
               itbuffer[:,1] = np.arange(P1Y+1,P1Y+dYa+1)
           itbuffer[:,0] = (slope*(itbuffer[:,1]-P1Y)).astype(np.int) + P1X
       else:
           slope = dY.astype(np.float32)/dX.astype(np.float32)
           if negX:
               itbuffer[:,0] = np.arange(P1X-1,P1X-dXa-1,-1)
           else:
               itbuffer[:,0] = np.arange(P1X+1,P1X+dXa+1)
           itbuffer[:,1] = (slope*(itbuffer[:,0]-P1X)).astype(np.int) + P1Y

   #Remove points outside of image
   colX = itbuffer[:,0]
   colY = itbuffer[:,1]
   itbuffer = itbuffer[(colX >= 0) & (colY >=0) & (colX<imageW) & (colY<imageH)]

   #Get intensities from img ndarray
   itbuffer[:,2] = img[itbuffer[:,1].astype(np.uint),itbuffer[:,0].astype(np.uint)]

   return itbuffer
person mohikhsan    schedule 30.09.2015
comment
Спасибо, что поделились @mohikhsan. Просто хотел отметить, что линия немного отличается от той, что указана cv2.drawLine(): ваша линия не включает первую точку P1, тогда как cv2.drawLine() включает ее. - person krookedking; 20.02.2018
comment
Ну, с моим тестированием я доказал, что этот код недействителен для любой строки, а также, как показал последний комментарий, первая точка не включена. Я работаю над реализацией Python, используя исходный код C++, потому что не думаю, что смогу сделать что-то лучше. - person trenixjetix; 02.08.2019

arrow_upward
10
arrow_downward

Изменить: функциональная строка из scikit-image может дать тот же эффект, и она быстрее, чем все, что мы могли бы закодировать.

from skimage.draw import line
# being start and end two points (x1,y1), (x2,y2)
discrete_line = list(zip(*line(*start, *end)))

Кроме того, результат timeit намного быстрее. Итак, используйте это.

Старый «устаревший» ответ:

Как говорится в предыдущем ответе, это не реализовано, поэтому вы должны сделать это самостоятельно. Я не делал это с нуля, я просто переписал некоторые части функции более причудливым и современным способом, который должен правильно обрабатывать все случаи, в отличие от ответа с наибольшим количеством голосов, который не работал правильно для меня. Я взял пример из здесь и сделал некоторая очистка и некоторая укладка. Не стесняйтесь комментировать это. Также я добавил тест линии отсечения, как в исходном коде, который можно найти в drawing.cpp в исходном коде для OpenCv 4.x Спасибо всем за ссылки и усердную работу.

    def bresenham_march(img, p1, p2):
        x1 = p1[0]
        y1 = p1[1]
        x2 = p2[0]
        y2 = p2[1]
        #tests if any coordinate is outside the image
        if (
            x1 >= img.shape[0]
            or x2 >= img.shape[0]
            or y1 >= img.shape[1]
            or y2 >= img.shape[1]
        ): #tests if line is in image, necessary because some part of the line must be inside, it respects the case that the two points are outside
            if not cv2.clipLine((0, 0, *img.shape), p1, p2):
                print("not in region")
                return

        steep = math.fabs(y2 - y1) > math.fabs(x2 - x1)
        if steep:
            x1, y1 = y1, x1
            x2, y2 = y2, x2

        # takes left to right
        also_steep = x1 > x2
        if also_steep:
            x1, x2 = x2, x1
            y1, y2 = y2, y1

        dx = x2 - x1
        dy = math.fabs(y2 - y1)
        error = 0.0
        delta_error = 0.0
        # Default if dx is zero
        if dx != 0:
            delta_error = math.fabs(dy / dx)

        y_step = 1 if y1 < y2 else -1

        y = y1
        ret = []
        for x in range(x1, x2):
            p = (y, x) if steep else (x, y)
            if p[0] < img.shape[0] and p[1] < img.shape[1]:
                ret.append((p, img[p]))
            error += delta_error
            if error >= 0.5:
                y += y_step
                error -= 1
        if also_steep:  # because we took the left to right instead
            ret.reverse()
        return ret
person trenixjetix    schedule 04.08.2019
comment
Я могу подтвердить, что решение с использованием линии Sci-kit работает хорошо. - person David; 18.08.2020
comment
Я бы не стал доверять коду @trenixjetix для его собственного решения. Исходный код OpenCV говорит следующее: (pt1.x ›= rect.width), который он перенес на это: (x1 ›= img.shape[0]), что неверно. - person David; 18.08.2020
comment
@David, для этого кода нужно использовать пустые массивы uint8 :) Не изображения PIL. - person trenixjetix; 19.08.2020

arrow_upward
4
arrow_downward

Не причудливый способ сделать это, а эффективный и очень очень простой однострочник:

points_on_line = np.linspace(pt_a, pt_b, 100) # 100 samples on the line

Если вы хотите примерно получить каждый пиксель по пути

points_on_line = np.linspace(pt_a, pt_b, np.linalg.norm(pt_a - pt_b))

(например, количество выборок как количество пикселей между точкой A и точкой B)

Например:

pt_a = np.array([10, 11])
pt_b = np.array([45, 67])
im = np.zeros((80, 80, 3), np.uint8)
for p in np.linspace(pt_a, pt_b, np.linalg.norm(pt_a-pt_b)):
    cv2.circle(im, tuple(np.int32(p)), 1, (255,0,0), -1)
plt.imshow(im)

указывает на линии

person Roy Shilkrot    schedule 30.12.2019

arrow_upward
3
arrow_downward

Я сравнил 4 метода, представленные на этой странице:

Использование python 2.7.6 и scikit-image 0.9.3 с небольшими изменениями кода.
Ввод изображения осуществляется через OpenCV.
Сегмент линии (1, 76) до (867, 190)

Метод 1: Sci-kit Image Line
Время вычисления: 0,568 мс
Количество найденных пикселей: 867
Правильный начальный пиксель: да
Правильный конечный пиксель : да

Метод 2: код из кода @trenixjetix
Кажется, есть ошибка, из-за которой ширина и высота изображения перевернуты.
Время вычисления: 0,476 мс
Количество пикселей найдено: 866
Правильный начальный пиксель: да
Правильный конечный пиксель: нет, отклонено на 1

Способ 3. Код с сайта ROS.org
https://answers.ros.org/question/10160/opencv-python-lineiterator-returning-position-information/ Время вычисления: 0,433 мс (должно быть таким же, как в методе 2). )
Количество найденных пикселей: 866
Правильный начальный пиксель: да
Правильный конечный пиксель: нет, отклонено на 1

Метод 4: код от @mohikhsan
Время вычисления: 0,156 мс
Количество найденных пикселей: 866
Правильный начальный пиксель: нет, отклонено на 1
Правильный конечный пиксель: да

Вывод:
Самый точный метод: Sci-kit Image Line
Самый быстрый метод: Код от @mohikhsan

Было бы неплохо иметь реализацию Python, которая соответствует реализации OpenCV C++?
https://github.com/opencv/opencv/blob/master/modules/imgproc/src/drawing.cpp
или использует генератор Python:
https://wiki.python.org/moin/Generators

person David    schedule 18.08.2020
comment
Спасибо за ваш пост, он очень информативен и должен быть закреплен или что-то в этом роде. Просто хочу отметить, что вы не должны использовать python2 и старые версии scikit. Это повлияет на результаты теста скорости и сделает ваш пост недействительным. Кроме того, ответ от @mohikhsan работает не во всех направлениях и довольно глючит. Также точность линий хуже. - person trenixjetix; 09.09.2020
comment
Результаты правильные и достоверные. Многие люди до сих пор используют python 2.x, и это четко указано в начале поста. - person David; 10.09.2020
comment
Хорошо, они действительны, но я не могу принять решение на основе старого программного обеспечения, для этого вам следует использовать версии программного обеспечения 2020 года. - person trenixjetix; 14.09.2020

arrow_upward
3
arrow_downward

Это не совсем ответ, но я не могу добавить комментарий, поэтому пишу его здесь. Решение от trenixjetix действительно отлично подходит для двух наиболее эффективных способов сделать это. Я просто хочу дать небольшое пояснение к методу scikit-image, который он упомянул.

# being start and end two points (x1,y1), (x2,y2)
discrete_line = list(zip(*line(*start, *end)))

В метрике scikit-image следуют начальная и конечная точки строки (строка, столбец), в то время как opencv использует координату (x, y), которая меняется на противоположную с точки зрения параметров функции. Обратите на это внимание.

Сложите ответ Дэвида, я получил время выполнения scikit быстрее, чем функция trenixjetix, используя python 3.8. Результат может быть разным, но почти каждый раз scikit работает быстрее.

время trenixjetix (мс) 0,22279999999996747

время scikit-изображения (мс) 0,13810000000002987

person Feliks    schedule 15.05.2021