OpenCV 3.0 线迭代器

Posted

技术标签:

【中文标题】OpenCV 3.0 线迭代器【英文标题】:OpenCV 3.0 LineIterator 【发布时间】:2015-11-26 11:42:01 【问题描述】:

我想使用 Python 在 OpenCV 3.0 中使用 LineIterator,它是否仍然适用于为 Python 构建的 OpenCV 3.0?似乎互联网上的答案都指向cv.InitLineIterator,它是cv 模块的一部分。我试过导入这个模块,但它似乎不包含在当前版本中。是否已重命名或严格删除?

【问题讨论】:

【参考方案1】:

我已经解决了我自己的问题。行迭代器似乎在 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

【讨论】:

感谢分享@mohikhsan。只是想注意,该行与cv2.drawLine() 给出的行略有不同:您的行不包含第一个点P1,而cv2.drawLine() 包含它。 好吧,通过我的测试,我已经证明此代码对任何行都无效,而且正如最后一条评论所证明的那样,第一点不包括在内。我正在努力制作一个采用 c++ 源代码的 python 实现,因为我认为我不能做得更好。 这段代码节省了我很多时间。非常感谢!【参考方案2】:

编辑: 来自 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 结果也相当快。所以,用这个吧。

旧的“已弃用”答案:

正如前面的回答所说,它没有实现,所以你必须自己做。 我不是从头开始做的,我只是以一种更时髦、更现代的方式重写了函数的某些部分,应该正确处理所有情况,这与投票最多的答案对我来说不起作用。我从here 中获取了示例,并进行了一些清理和一些样式设置。 随意评论它。我还添加了类似于源代码中的剪辑线测试,可以在 OpenCv 4.x 的源代码中的drawing.cpp 中找到 感谢大家的参考和辛勤工作。

    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

【讨论】:

我可以确认使用 Sci-kit 画线的解决方案效果很好。 我不相信@trenixjetix 代码可以用于他自己的解决方案。原始的 OpenCV 代码是这样说的: (pt1.x >= rect.width) 他移植到这个: (x1 >= img.shape[0]) 这是错误的。 @David 您需要为此代码使用 numpy uint8 数组 :) 不是 PIL 图像。【参考方案3】:

这不是一种花哨的方法,而是一种有效且非常非常简单的单线:

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)

【讨论】:

【参考方案4】:

我比较了本页提供的 4 种方法:

使用 python 2.7.6 和 scikit-image 0.9.3 并进行一些小的代码更改。 图像输入是通过 OpenCV。 一条线段 (1, 76) 到 (867, 190)

方法一: 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 ms(应与方法2相同) 找到的像素数:866 正确的起始像素:是 正确的结束像素:否,减 1

方法 4:来自@mohikhsan 的代码 计算时间:0.156 毫秒 找到的像素数:866 正确的起始像素:否,减 1 正确的结束像素:是

总结:最准确的方法: Sci-kit Image Line最快的方法:来自@mohikhsan的代码

有一个与 OpenCV C++ 实现相匹配的 python 实现会很好吗?https://github.com/opencv/opencv/blob/master/modules/imgproc/src/drawing.cpp 或使用 python 生成器:https://wiki.python.org/moin/Generators

【讨论】:

感谢您的帖子,内容非常丰富,应该置顶或其他什么。只是要指出,你不应该使用 python2 和旧版本的 scikit。这将影响基准测试的速度结果并使您的帖子无效。此外,@mohikhsan 的答案并非在每个角度都有效,而且非常有问题。线条的准确性也更差。 结果正确有效。许多人仍在使用 python 2.x,这在帖子顶部明确提到。 好的,它们是有效的,但我无法根据旧软件做出决定,您应该使用 2020 版本的软件。【参考方案5】:

这并不完全是一个答案,但我无法添加评论,所以我在这里写下。 trenixjetix 的解决方案非常适合涵盖最有效的两种方法。我只是想对他提到的 scikit-image 方法做一点澄清。

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

在scikit-image metric中,线的起点和终点是(row, col),而opencv使用的是(x,y)坐标,在函数参数方面是相反的。请注意这一点。

加起来大卫的答案,我得到scikit的执行时间比trenixjetix的函数快,使用python 3.8。结果可能会有所不同,但几乎每次 scikit 都更快。

trenixjetix 时间(毫秒)0.22279999999996747

scikit-image 时间(ms) 0.13810000000002987

【讨论】:

【参考方案6】:

我在运行 trenixjetix 的 skimage 示例时遇到了麻烦,所以我创建了一个小型包装函数,接受来自 numpy 数组切片、元组或列表的点:

from skimage.draw import line as skidline
def get_linepnts(p0, p1):
    p0, p1 = np.array(p0).flatten(), np.array(p1).flatten()
    return np.array(list(zip(*skidline(p0[0],p0[1], p1[0],p1[1]))))

生成的数组可用于通过以下方式从 numpy 数组中检索值:

l0 = get_linepnts(p0, p1)
#if p0/p1 are in (x,y) format, then this needs to be swapped for retrieval:
vals = yournpmat[l0[:,1], l0[:,0]]

【讨论】:

以上是关于OpenCV 3.0 线迭代器的主要内容,如果未能解决你的问题,请参考以下文章

Python学习笔记8(迭代器生成器装饰器)

枚举器、迭代器、IEnumerable - 有点困惑

迭代器,可迭代对象,迭代器对象和生成器

生成器和迭代器的原理及使用

OpenCV实战——像素操作

python基础:迭代器生成器面向过程编程