如何在OpenCV Python中分割轮廓?

huangapple go评论102阅读模式
英文:

How to split a contour in opencv python?

问题

我目前面临的问题与我正在开发的代码相关,该代码用于跟随一条黑线。目前,我的代码通过创建黑线周围的轮廓并在该线的中心绘制一个点来实现功能。我还在图像的中心绘制一个点,并计算这些点之间的距离。有了这些信息,我可以相应地转动机器人。

我想要实现的目标是将轮廓水平分割,因为当线路中有陡峭的拐角时,代码将计算平均值,而点将位于线外。

这不是世界上最大的问题,因为我可以将摄像头放置得更靠近线路,使线路的曲线变得不那么戏剧化,但除非这是我的最后选择,否则我不想这样做,因为这样效率较低且不酷。这是我希望图像看起来的样子:

如您所见,轮廓被分割成了较小的轮廓。

这是我的代码:

  1. import numpy as np
  2. import cv2
  3. import math
  4. # 在hsv格式中的掩膜的下限和上限值
  5. lg = np.array([75, 52, 60])
  6. ug = np.array([106, 255, 255])
  7. lb = np.array([0,32,0])
  8. ub = np.array([179, 255, 93])
  9. # 忽略这部分(用于线路跟踪的分隔)
  10. def percentage_calculator(boundaries, colour, image):
  11. for(lower, upper) in boundaries:
  12. lower = np.array(lower)
  13. upper = np.array(upper)
  14. # 找到在边界内的颜色并应用掩膜
  15. mask = cv2.inRange(image, lower, upper)
  16. output = cv2.bitwise_and(image, image, mask = mask)
  17. tot_pixel = image.size
  18. pixel = np.count_nonzero(output)
  19. percentage = round(pixel * 100 / tot_pixel, 2)
  20. print(colour + " 像素数: " + str(pixel))
  21. print("总像素数: " + str(tot_pixel))
  22. print(colour + " 像素的百分比: " + str(percentage) + "%")
  23. green_boundaries = [
  24. ([75, 52, 60], [106, 255, 255])
  25. ]
  26. cap = cv2.VideoCapture(1)
  27. # 屏幕中心点
  28. Xmid = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) / 2)
  29. Ymid = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) / 2)
  30. while True:
  31. _, img = cap.read()
  32. hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
  33. # 裁剪并获取hsv值并将其转换为查看绿色值是否存在
  34. mask = cv2.inRange(hsv, lg, ug)
  35. green_result = cv2.bitwise_and(hsv, hsv, mask=mask)
  36. # 裁剪并获取hsv值并将其转换为查看值是否存在
  37. mask1 = cv2.inRange(hsv, lb, ub)
  38. black_result = cv2.bitwise_and(hsv, hsv, mask=mask1)
  39. # 查找所有当前轮廓
  40. contours,_= cv2.findContours(mask1,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
  41. # 查找最大的轮廓
  42. max_contour = contours[0]
  43. for contour in contours:
  44. if cv2.contourArea(contour)>cv2.contourArea(max_contour):
  45. max_contour = contour
  46. # 简化轮廓以更好地逼近线条
  47. contour = cv2.approxPolyDP(max_contour, 0.05 * cv2.arcLength(contour, True), True)
  48. # 计算轮廓的质心(我认为是)
  49. M = cv2.moments(contour)
  50. cX = int(M["m10"] / M["m00"])
  51. cY = int(M["m01"] / M["m00"])
  52. # cX和cY是轮廓的中心
  53. cv2.circle(img, (cX, cY), 5, (0, 0, 255), -1)
  54. cv2.circle(img, (Xmid, Ymid), 5, (255, 255, 0), -1)
  55. # 在两个圆之间绘制一条线以进行可视化
  56. cv2.line(img, (cX, cY), (Xmid, Ymid), (255, 0, 0), 2)
  57. # 使用距离公式找到两个圆的坐标之间的距离
  58. distance = math.sqrt((Xmid - cX) ** 2 + (Ymid - cY) ** 2)
  59. cv2.drawContours(img, [contour], -1, (0, 255, 0), 2)
  60. cv2.imshow('black', black_result)
  61. cv2.imshow('img', img)
  62. k = cv2.waitKey(30) & 0xff
  63. if k==27:
  64. break

感谢回答!

  1. <details>
  2. <summary>英文:</summary>
  3. the problem I&#39;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.
  4. [![Example of current code][1]][1]
  5. 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
  6. [![enter image description here][2]][2]
  7. This isn&#39;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&#39;t wan to do this unless it&#39;s my last option as it&#39;s less efficient and not cool. This is an example of what i want the image to look like:
  8. [![enter image description here][3]][3]
  9. as you can see the contour is seperated into smaller contours.
  10. 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):

  1. for(lower, upper) in boundaries:
  2. lower = np.array(lower)
  3. upper = np.array(upper)
  4. # finds colors in boundaries a applies a mask
  5. mask = cv2.inRange(image, lower, upper)
  6. output = cv2.bitwise_and(image, image, mask = mask)
  7. tot_pixel = image.size
  8. pixel = np.count_nonzero(output)
  9. percentage = round(pixel * 100 / tot_pixel, 2)
  10. print(colour + &quot; pixels: &quot; + str(pixel))
  11. print(&quot;Total pixels: &quot; + str(tot_pixel))
  12. print(&quot;Percentage of &quot; + colour + &quot; pixels: &quot; + str(percentage) + &quot;%&quot;)

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()

  1. hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
  2. #cropping and getting the hsv value and converting it to see of the value is there for green
  3. mask = cv2.inRange(hsv, lg, ug)
  4. green_result = cv2.bitwise_and(hsv, hsv, mask=mask)
  5. #cropping and getting the hsv value and converting it to see of the value is there
  6. mask1 = cv2.inRange(hsv, lb, ub)
  7. black_result = cv2.bitwise_and(hsv, hsv, mask=mask1)
  8. #Finding all current contours
  9. contours,_= cv2.findContours(mask1,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
  10. #finding the biggest contour
  11. max_contour = contours[0]
  12. for contour in contours:
  13. if cv2.contourArea(contour)&gt;cv2.contourArea(max_contour):
  14. max_contour = contour
  15. #simplifying the contour so it approximates the lines better
  16. contour = cv2.approxPolyDP(max_contour, 0.05 * cv2.arcLength(contour, True), True)
  17. #calculating the centroid (I think) of the contour
  18. M = cv2.moments(contour)
  19. cX = int(M[&quot;m10&quot;] / M[&quot;m00&quot;])
  20. cY = int(M[&quot;m01&quot;] / M[&quot;m00&quot;])
  21. #cX and cY are the centre of the contour
  22. cv2.circle(img, (cX, cY), 5, (0, 0, 255), -1)
  23. cv2.circle(img, (Xmid, Ymid), 5, (255, 255, 0), -1)
  24. #drawing a line between the two circles for visualisation
  25. cv2.line(img, (cX, cY), (Xmid, Ymid), (255, 0, 0), 2)
  26. #using distance formula to find the distance between the two coords of the circles
  27. distance = math.sqrt((Xmid - cX) ** 2 + (Ymid - cY) ** 2)
  28. cv2.drawContours(img, [contour], -1, (0, 255, 0), 2)
  29. #cv2.imshow(&quot;green&quot;, green_result)
  30. cv2.imshow(&#39;black&#39;, black_result)
  31. #cv2.imshow(&quot;hsv&quot;, hsv)
  32. cv2.imshow(&#39;img&#39;, img)
  33. k = cv2.waitKey(30) &amp; 0xff
  34. if k==27:
  35. break
  1. Thanks for the answers!
  2. [1]: https://i.stack.imgur.com/cNSy3.png
  3. [2]: https://i.stack.imgur.com/9QZnN.png
  4. [3]: https://i.stack.imgur.com/4Ur3X.png
  5. </details>
  6. # 答案1
  7. **得分**: 1
  8. 以下是使用Python/OpenCV/Skimage进行骨架化的示例。
  9. 输入:
  10. [![在此输入图片描述][1]][1]
  11. ```python
  12. import cv2
  13. import numpy as np
  14. import skimage.morphology
  15. img = cv2.imread("WIN_20230617_22_11_44_Pro.jpg")
  16. ht, wd = img.shape[:2]
  17. # 转换为灰度图
  18. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  19. # 创建一个二值化阈值图像
  20. thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
  21. # 反转使线条变为白色
  22. thresh = 255 - thresh
  23. # 然后将thresh骨架化为0到1的范围,类型为float32,然后转换回uint8
  24. skeleton = skimage.morphology.skeletonize(thresh.astype(np.float32)/255)
  25. skeleton = (255*skeleton).clip(0,255).astype(np.uint8)
  26. # 保存结果
  27. cv2.imwrite("WIN_20230617_22_11_44_Pro_thresh.jpg", thresh)
  28. cv2.imwrite("WIN_20230617_22_11_44_Pro_skeleton.jpg", skeleton)
  29. # 显示结果
  30. cv2.imshow("threshold", thresh)
  31. cv2.imshow("skeleton", skeleton)
  32. cv2.waitKey(0)
  33. cv2.destroyAllWindows()

阈值图像:

如何在OpenCV Python中分割轮廓?

骨架图像:

如何在OpenCV Python中分割轮廓?

英文:

Here is an example of how to skeletonize using Python/OpenCV/Skimage.

Input:

如何在OpenCV Python中分割轮廓?

  1. import cv2
  2. import numpy as np
  3. import skimage.morphology
  4. img = cv2.imread(&quot;WIN_20230617_22_11_44_Pro.jpg&quot;)
  5. ht, wd = img.shape[:2]
  6. # convert to grayscale
  7. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  8. # create a binary thresholded image
  9. thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
  10. # invert so lines are white
  11. thresh = 255 - thresh
  12. # then skeletonize thresh in range 0 to 1 as float32, then convert back to uint8
  13. skeleton = skimage.morphology.skeletonize(thresh.astype(np.float32)/255)
  14. skeleton = (255*skeleton).clip(0,255).astype(np.uint8)
  15. # save result
  16. cv2.imwrite(&quot;WIN_20230617_22_11_44_Pro_thresh.jpg&quot;, thresh)
  17. cv2.imwrite(&quot;WIN_20230617_22_11_44_Pro_skeleteon.jpg&quot;, skeleton)
  18. # show results
  19. cv2.imshow(&quot;threshold&quot;, thresh)
  20. cv2.imshow(&quot;skeleton&quot;, skeleton)
  21. cv2.waitKey(0)
  22. cv2.destroyAllWindows()

Threshold Image:

如何在OpenCV Python中分割轮廓?

Skeleton Image:

如何在OpenCV Python中分割轮廓?

答案2

得分: 0

没有办法分割轮廓,但是我找到了一个最佳解决方案,可以得到线的中间部分。我将图像转换为灰度并模糊化图像:

  1. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  2. blurred = cv2.medianBlur(gray, 9)

我对图像进行了阈值处理并反转图像,以获得黑线作为唯一的非零像素:

  1. _, thresholded = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
  2. inverted_thresholded = cv2.bitwise_not(thresholded)

然后,我使用skimage的骨架化方法侵蚀了这些像素,并捕获了剩下的所有像素:

  1. skeleton, dist = medial_axis(inverted_thresholded, return_distance=True)
  2. dist_on_skel = dist * skeleton
  3. skeleton = (255 * dist_on_skel).astype(np.uint8)
  4. Line = cv2.findNonZero(skeleton)

然后,我通过仅获取彼此靠近的像素来消除任何随机噪音:

  1. filtered_pixel_coords = []
  2. prev_point = None
  3. for pixel in Line:
  4. x, y = pixel[0]
  5. if prev_point is not None:
  6. prev_x, prev_y = prev_point
  7. distance = math.sqrt((x - prev_x) ** 2 + (y - prev_y) ** 2)
  8. if distance <= outlier_threshold:
  9. #忽略边缘上的像素,因为有时会有很多,这是不好的
  10. if 10 < x < w - 10 and 10 < y < h - 10:
  11. filtered_pixel_coords.append([x, y])
  12. else:
  13. filtered_pixel_coords.append([x, y])
  14. prev_point = [x, y]
  15. pixel_Coords = filtered_pixel_coords

然后,我在每50个像素的平均值之间绘制一条线:

  1. for i, coord in enumerate(pixel_Coords):
  2. avg[0] += coord[0]
  3. avg[1] += coord[1]
  4. count += 1
  5. if count == 50 or i == len(pixel_Coords) - 1:
  6. avg[0] //= count
  7. avg[1] //= count
  8. averaged_coords.append(avg.copy())
  9. avg = [0, 0]
  10. count = 0
  11. for i in range(len(averaged_coords) - 1):
  12. pt1 = (averaged_coords[i][0], averaged_coords[i][1])
  13. pt2 = (averaged_coords[i + 1][0], averaged_coords[i + 1][1])
  14. 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:

  1. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  2. blurred = cv2.medianBlur(gray, 9)

I thresholded and inverted the image to get the black line as the only non-zero pixels:

  1. _, thresholded = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
  2. inverted_thresholded = cv2.bitwise_not(thresholded)

I then eroded those pixels with skimage's skeletonization and grabbed all the pixels left:

  1. skeleton, dist = medial_axis(inverted_thresholded, return_distance=True)
  2. dist_on_skel = dist * skeleton
  3. skeleton = (255 * dist_on_skel).astype(np.uint8)
  4. Line = cv2.findNonZero(skeleton)

Then I remove any random noise by only grabbing pixels that are close to each other:

  1. filtered_pixel_coords = []
  2. prev_point = None
  3. for pixel in Line:
  4. x, y = pixel[0]
  5. if prev_point is not None:
  6. prev_x, prev_y = prev_point
  7. distance = math.sqrt((x - prev_x) ** 2 + (y - prev_y) ** 2)
  8. if distance &lt;= outlier_threshold:
  9. #forgets about any pixels on the edge cause sometimes there are a lot of them and that is bad
  10. if 10 &lt; x &lt; w - 10 and 10 &lt; y &lt; h - 10:
  11. filtered_pixel_coords.append([x, y])
  12. else:
  13. filtered_pixel_coords.append([x, y])
  14. prev_point = [x, y]
  15. pixel_Coords = filtered_pixel_coords

I then draw a line between the average of every 50th pixel:

  1. for i, coord in enumerate(pixel_Coords):
  2. avg[0] += coord[0]
  3. avg[1] += coord[1]
  4. count += 1
  5. if count == 50 or i == len(pixel_Coords) - 1:
  6. avg[0] //= count
  7. avg[1] //= count
  8. averaged_coords.append(avg.copy())
  9. avg = [0, 0]
  10. count = 0
  11. for i in range(len(averaged_coords) - 1):
  12. pt1 = (averaged_coords[i][0], averaged_coords[i][1])
  13. pt2 = (averaged_coords[i + 1][0], averaged_coords[i + 1][1])
  14. cv2.line(img, pt1, pt2, (0, 0, 255), 2)

I hope this helps.

huangapple
  • 本文由 发表于 2023年6月18日 21:20:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76500743.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定