OpenCV 3.0 线迭代器
OpenCV 3.0 LineIterator
我想在使用 Python 的 OpenCV 3.0 中使用 LineIterator,它是否仍然适用于为 Python 构建的 OpenCV 3.0?似乎网上的答案都指向 cv.InitLineIterator
,它是 cv
模块的一部分。我试过导入这个模块,但它似乎不包含在当前版本中。是否已重命名或严格来说只是删除?
我已经解决了我自己的问题。行迭代器似乎在 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
编辑:
来自 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 结果也相当快。所以,使用这个。
旧"deprecated"答案:
正如前面的回答所说,它没有实现,所以你必须自己做。
我不是从头开始做的,我只是以一种更高级、更现代的方式重写了函数的某些部分,这种方式应该可以正确处理所有情况,这与投票最多的答案对我来说不起作用不同。我从 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
这不是一个奇特的方法,而是一种有效且非常非常简单的方法one-liner:
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种方法:
使用 python 2.7.6 和 scikit-image 0.9.3 进行一些小的代码更改。
图像输入是通过 OpenCV。
一条线段 (1, 76) 到 (867, 190)
方法一:Sci-kit图像线
计算时间:0.568 毫秒
找到的像素数:867
正确的起始像素:是
正确的结束像素:是
方法二:代码来自@trenixjetix代码
好像有图片宽高翻转的bug。
计算时间:0.476 毫秒
找到的像素数:866
正确的起始像素:是
正确的结束像素:不,偏移 1
方法三:代码来自ROS.org
https://answers.ros.org/question/10160/opencv-python-lineiterator-returning-position-information/
计算时间:0.433 ms(应该和方法2一样)
找到的像素数:866
正确的起始像素:是
正确的结束像素:不,偏移 1
方法四:代码来自@mohikhsan
计算时间:0.156 毫秒
找到的像素数:866
正确的起始像素:不,偏移 1
正确的结束像素:是
总结:
最准确的方法: Sci-kit 图像线
最快的方法: @mohikhsan 的代码
有一个与 OpenCV C++ 实现相匹配的 python 实现会更好吗?
https://github.com/opencv/opencv/blob/master/modules/imgproc/src/drawing.cpp
或使用 python 生成器:
https://wiki.python.org/moin/Generators
这不完全是一个答案,但我不能添加评论所以我写在这里。
trenixjetix 的解决方案非常棒,涵盖了最有效的 2 种方法。我只是想稍微说明一下他提到的 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)坐标,在函数参数方面是相反的。请注意。
加上 David 的答案,我得到 scikit 的执行时间比 trenixjetix 的函数快,使用 python 3.8。结果可能会有所不同,但几乎每次 scikit 都更快。
trenixjetix time(ms) 0.22279999999996747
scikit-image time(ms) 0.13810000000002987
我遇到了麻烦 运行 来自 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]]
我想在使用 Python 的 OpenCV 3.0 中使用 LineIterator,它是否仍然适用于为 Python 构建的 OpenCV 3.0?似乎网上的答案都指向 cv.InitLineIterator
,它是 cv
模块的一部分。我试过导入这个模块,但它似乎不包含在当前版本中。是否已重命名或严格来说只是删除?
我已经解决了我自己的问题。行迭代器似乎在 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
编辑: 来自 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 结果也相当快。所以,使用这个。
旧"deprecated"答案:
正如前面的回答所说,它没有实现,所以你必须自己做。 我不是从头开始做的,我只是以一种更高级、更现代的方式重写了函数的某些部分,这种方式应该可以正确处理所有情况,这与投票最多的答案对我来说不起作用不同。我从 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
这不是一个奇特的方法,而是一种有效且非常非常简单的方法one-liner:
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种方法:
使用 python 2.7.6 和 scikit-image 0.9.3 进行一些小的代码更改。
图像输入是通过 OpenCV。
一条线段 (1, 76) 到 (867, 190)
方法一:Sci-kit图像线
计算时间:0.568 毫秒
找到的像素数:867
正确的起始像素:是
正确的结束像素:是
方法二:代码来自@trenixjetix代码
好像有图片宽高翻转的bug。
计算时间:0.476 毫秒
找到的像素数:866
正确的起始像素:是
正确的结束像素:不,偏移 1
方法三:代码来自ROS.org
https://answers.ros.org/question/10160/opencv-python-lineiterator-returning-position-information/
计算时间:0.433 ms(应该和方法2一样)
找到的像素数:866
正确的起始像素:是
正确的结束像素:不,偏移 1
方法四:代码来自@mohikhsan
计算时间:0.156 毫秒
找到的像素数:866
正确的起始像素:不,偏移 1
正确的结束像素:是
总结:
最准确的方法: Sci-kit 图像线
最快的方法: @mohikhsan 的代码
有一个与 OpenCV C++ 实现相匹配的 python 实现会更好吗?
https://github.com/opencv/opencv/blob/master/modules/imgproc/src/drawing.cpp
或使用 python 生成器:
https://wiki.python.org/moin/Generators
这不完全是一个答案,但我不能添加评论所以我写在这里。 trenixjetix 的解决方案非常棒,涵盖了最有效的 2 种方法。我只是想稍微说明一下他提到的 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)坐标,在函数参数方面是相反的。请注意。
加上 David 的答案,我得到 scikit 的执行时间比 trenixjetix 的函数快,使用 python 3.8。结果可能会有所不同,但几乎每次 scikit 都更快。
trenixjetix time(ms) 0.22279999999996747
scikit-image time(ms) 0.13810000000002987
我遇到了麻烦 运行 来自 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]]