英文:
How to split a contour in opencv python?
问题
我目前面临的问题与我正在开发的代码相关,该代码用于跟随一条黑线。目前,我的代码通过创建黑线周围的轮廓并在该线的中心绘制一个点来实现功能。我还在图像的中心绘制一个点,并计算这些点之间的距离。有了这些信息,我可以相应地转动机器人。
我想要实现的目标是将轮廓水平分割,因为当线路中有陡峭的拐角时,代码将计算平均值,而点将位于线外。
这不是世界上最大的问题,因为我可以将摄像头放置得更靠近线路,使线路的曲线变得不那么戏剧化,但除非这是我的最后选择,否则我不想这样做,因为这样效率较低且不酷。这是我希望图像看起来的样子:
如您所见,轮廓被分割成了较小的轮廓。
这是我的代码:
import numpy as np
import cv2
import math
# 在hsv格式中的掩膜的下限和上限值
lg = np.array([75, 52, 60])
ug = np.array([106, 255, 255])
lb = np.array([0,32,0])
ub = np.array([179, 255, 93])
# 忽略这部分(用于线路跟踪的分隔)
def percentage_calculator(boundaries, colour, image):
for(lower, upper) in boundaries:
lower = np.array(lower)
upper = np.array(upper)
# 找到在边界内的颜色并应用掩膜
mask = cv2.inRange(image, lower, upper)
output = cv2.bitwise_and(image, image, mask = mask)
tot_pixel = image.size
pixel = np.count_nonzero(output)
percentage = round(pixel * 100 / tot_pixel, 2)
print(colour + " 像素数: " + str(pixel))
print("总像素数: " + str(tot_pixel))
print(colour + " 像素的百分比: " + str(percentage) + "%")
green_boundaries = [
([75, 52, 60], [106, 255, 255])
]
cap = cv2.VideoCapture(1)
# 屏幕中心点
Xmid = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) / 2)
Ymid = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) / 2)
while True:
_, img = cap.read()
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 裁剪并获取hsv值并将其转换为查看绿色值是否存在
mask = cv2.inRange(hsv, lg, ug)
green_result = cv2.bitwise_and(hsv, hsv, mask=mask)
# 裁剪并获取hsv值并将其转换为查看值是否存在
mask1 = cv2.inRange(hsv, lb, ub)
black_result = cv2.bitwise_and(hsv, hsv, mask=mask1)
# 查找所有当前轮廓
contours,_= cv2.findContours(mask1,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
# 查找最大的轮廓
max_contour = contours[0]
for contour in contours:
if cv2.contourArea(contour)>cv2.contourArea(max_contour):
max_contour = contour
# 简化轮廓以更好地逼近线条
contour = cv2.approxPolyDP(max_contour, 0.05 * cv2.arcLength(contour, True), True)
# 计算轮廓的质心(我认为是)
M = cv2.moments(contour)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# cX和cY是轮廓的中心
cv2.circle(img, (cX, cY), 5, (0, 0, 255), -1)
cv2.circle(img, (Xmid, Ymid), 5, (255, 255, 0), -1)
# 在两个圆之间绘制一条线以进行可视化
cv2.line(img, (cX, cY), (Xmid, Ymid), (255, 0, 0), 2)
# 使用距离公式找到两个圆的坐标之间的距离
distance = math.sqrt((Xmid - cX) ** 2 + (Ymid - cY) ** 2)
cv2.drawContours(img, [contour], -1, (0, 255, 0), 2)
cv2.imshow('black', black_result)
cv2.imshow('img', img)
k = cv2.waitKey(30) & 0xff
if k==27:
break
感谢回答!
<details>
<summary>英文:</summary>
the problem I'm facing currently is related to my code which is being developed to follow a black line. Currently my code function by creating a contour around the black line and drawing a dot in the centre of that line. I also draw a dot in the centre of the image and calculate the distance between those dots. With this information i can turn the robot accordingly.
[![Example of current code][1]][1]
What I want to achieve is to segment the contour horizontally as when there are steep corners in the line the code will calculate the average and the dot will be outside the line
[![enter image description here][2]][2]
This isn't the biggest issue in the world because I can just situate the camera to be closer to the line making the line curve less dramatic however, I don't wan to do this unless it's my last option as it's less efficient and not cool. This is an example of what i want the image to look like:
[![enter image description here][3]][3]
as you can see the contour is seperated into smaller contours.
This is my code:
import numpy as np
import cv2
import math
#lower and upper values in hsv format for mask
lg = np.array([75, 52, 60])
ug = np.array([106, 255, 255])
lb = np.array([0,32,0])
ub = np.array([179, 255, 93])
#ignore this (for seperate part of line following)
def percentage_calculator(boundaries, colour, image):
for(lower, upper) in boundaries:
lower = np.array(lower)
upper = np.array(upper)
# finds colors in boundaries a applies a mask
mask = cv2.inRange(image, lower, upper)
output = cv2.bitwise_and(image, image, mask = mask)
tot_pixel = image.size
pixel = np.count_nonzero(output)
percentage = round(pixel * 100 / tot_pixel, 2)
print(colour + " pixels: " + str(pixel))
print("Total pixels: " + str(tot_pixel))
print("Percentage of " + colour + " pixels: " + str(percentage) + "%")
green_boundaries = [
([75, 52, 60], [106, 255, 255])
]
cap = cv2.VideoCapture(1)
#centre point of screen
Xmid = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) / 2)
Ymid = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) / 2)
while True:
_, img = cap.read()
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#cropping and getting the hsv value and converting it to see of the value is there for green
mask = cv2.inRange(hsv, lg, ug)
green_result = cv2.bitwise_and(hsv, hsv, mask=mask)
#cropping and getting the hsv value and converting it to see of the value is there
mask1 = cv2.inRange(hsv, lb, ub)
black_result = cv2.bitwise_and(hsv, hsv, mask=mask1)
#Finding all current contours
contours,_= cv2.findContours(mask1,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
#finding the biggest contour
max_contour = contours[0]
for contour in contours:
if cv2.contourArea(contour)>cv2.contourArea(max_contour):
max_contour = contour
#simplifying the contour so it approximates the lines better
contour = cv2.approxPolyDP(max_contour, 0.05 * cv2.arcLength(contour, True), True)
#calculating the centroid (I think) of the contour
M = cv2.moments(contour)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
#cX and cY are the centre of the contour
cv2.circle(img, (cX, cY), 5, (0, 0, 255), -1)
cv2.circle(img, (Xmid, Ymid), 5, (255, 255, 0), -1)
#drawing a line between the two circles for visualisation
cv2.line(img, (cX, cY), (Xmid, Ymid), (255, 0, 0), 2)
#using distance formula to find the distance between the two coords of the circles
distance = math.sqrt((Xmid - cX) ** 2 + (Ymid - cY) ** 2)
cv2.drawContours(img, [contour], -1, (0, 255, 0), 2)
#cv2.imshow("green", green_result)
cv2.imshow('black', black_result)
#cv2.imshow("hsv", hsv)
cv2.imshow('img', img)
k = cv2.waitKey(30) & 0xff
if k==27:
break
Thanks for the answers!
[1]: https://i.stack.imgur.com/cNSy3.png
[2]: https://i.stack.imgur.com/9QZnN.png
[3]: https://i.stack.imgur.com/4Ur3X.png
</details>
# 答案1
**得分**: 1
以下是使用Python/OpenCV/Skimage进行骨架化的示例。
输入:
[![在此输入图片描述][1]][1]
```python
import cv2
import numpy as np
import skimage.morphology
img = cv2.imread("WIN_20230617_22_11_44_Pro.jpg")
ht, wd = img.shape[:2]
# 转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建一个二值化阈值图像
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# 反转使线条变为白色
thresh = 255 - thresh
# 然后将thresh骨架化为0到1的范围,类型为float32,然后转换回uint8
skeleton = skimage.morphology.skeletonize(thresh.astype(np.float32)/255)
skeleton = (255*skeleton).clip(0,255).astype(np.uint8)
# 保存结果
cv2.imwrite("WIN_20230617_22_11_44_Pro_thresh.jpg", thresh)
cv2.imwrite("WIN_20230617_22_11_44_Pro_skeleton.jpg", skeleton)
# 显示结果
cv2.imshow("threshold", thresh)
cv2.imshow("skeleton", skeleton)
cv2.waitKey(0)
cv2.destroyAllWindows()
阈值图像:
骨架图像:
英文:
Here is an example of how to skeletonize using Python/OpenCV/Skimage.
Input:
import cv2
import numpy as np
import skimage.morphology
img = cv2.imread("WIN_20230617_22_11_44_Pro.jpg")
ht, wd = img.shape[:2]
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# create a binary thresholded image
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# invert so lines are white
thresh = 255 - thresh
# then skeletonize thresh in range 0 to 1 as float32, then convert back to uint8
skeleton = skimage.morphology.skeletonize(thresh.astype(np.float32)/255)
skeleton = (255*skeleton).clip(0,255).astype(np.uint8)
# save result
cv2.imwrite("WIN_20230617_22_11_44_Pro_thresh.jpg", thresh)
cv2.imwrite("WIN_20230617_22_11_44_Pro_skeleteon.jpg", skeleton)
# show results
cv2.imshow("threshold", thresh)
cv2.imshow("skeleton", skeleton)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Skeleton Image:
答案2
得分: 0
没有办法分割轮廓,但是我找到了一个最佳解决方案,可以得到线的中间部分。我将图像转换为灰度并模糊化图像:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.medianBlur(gray, 9)
我对图像进行了阈值处理并反转图像,以获得黑线作为唯一的非零像素:
_, thresholded = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
inverted_thresholded = cv2.bitwise_not(thresholded)
然后,我使用skimage的骨架化方法侵蚀了这些像素,并捕获了剩下的所有像素:
skeleton, dist = medial_axis(inverted_thresholded, return_distance=True)
dist_on_skel = dist * skeleton
skeleton = (255 * dist_on_skel).astype(np.uint8)
Line = cv2.findNonZero(skeleton)
然后,我通过仅获取彼此靠近的像素来消除任何随机噪音:
filtered_pixel_coords = []
prev_point = None
for pixel in Line:
x, y = pixel[0]
if prev_point is not None:
prev_x, prev_y = prev_point
distance = math.sqrt((x - prev_x) ** 2 + (y - prev_y) ** 2)
if distance <= outlier_threshold:
#忽略边缘上的像素,因为有时会有很多,这是不好的
if 10 < x < w - 10 and 10 < y < h - 10:
filtered_pixel_coords.append([x, y])
else:
filtered_pixel_coords.append([x, y])
prev_point = [x, y]
pixel_Coords = filtered_pixel_coords
然后,我在每50个像素的平均值之间绘制一条线:
for i, coord in enumerate(pixel_Coords):
avg[0] += coord[0]
avg[1] += coord[1]
count += 1
if count == 50 or i == len(pixel_Coords) - 1:
avg[0] //= count
avg[1] //= count
averaged_coords.append(avg.copy())
avg = [0, 0]
count = 0
for i in range(len(averaged_coords) - 1):
pt1 = (averaged_coords[i][0], averaged_coords[i][1])
pt2 = (averaged_coords[i + 1][0], averaged_coords[i + 1][1])
cv2.line(img, pt1, pt2, (0, 0, 255), 2)
希望这对你有所帮助。
英文:
There isn't a way to split a contour, however, here is the best solution I found to get the middle of the line. I converted the image to grayscale and blurred the image:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.medianBlur(gray, 9)
I thresholded and inverted the image to get the black line as the only non-zero pixels:
_, thresholded = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
inverted_thresholded = cv2.bitwise_not(thresholded)
I then eroded those pixels with skimage's skeletonization and grabbed all the pixels left:
skeleton, dist = medial_axis(inverted_thresholded, return_distance=True)
dist_on_skel = dist * skeleton
skeleton = (255 * dist_on_skel).astype(np.uint8)
Line = cv2.findNonZero(skeleton)
Then I remove any random noise by only grabbing pixels that are close to each other:
filtered_pixel_coords = []
prev_point = None
for pixel in Line:
x, y = pixel[0]
if prev_point is not None:
prev_x, prev_y = prev_point
distance = math.sqrt((x - prev_x) ** 2 + (y - prev_y) ** 2)
if distance <= outlier_threshold:
#forgets about any pixels on the edge cause sometimes there are a lot of them and that is bad
if 10 < x < w - 10 and 10 < y < h - 10:
filtered_pixel_coords.append([x, y])
else:
filtered_pixel_coords.append([x, y])
prev_point = [x, y]
pixel_Coords = filtered_pixel_coords
I then draw a line between the average of every 50th pixel:
for i, coord in enumerate(pixel_Coords):
avg[0] += coord[0]
avg[1] += coord[1]
count += 1
if count == 50 or i == len(pixel_Coords) - 1:
avg[0] //= count
avg[1] //= count
averaged_coords.append(avg.copy())
avg = [0, 0]
count = 0
for i in range(len(averaged_coords) - 1):
pt1 = (averaged_coords[i][0], averaged_coords[i][1])
pt2 = (averaged_coords[i + 1][0], averaged_coords[i + 1][1])
cv2.line(img, pt1, pt2, (0, 0, 255), 2)
I hope this helps.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论