问题描述
我是一名经验丰富的程序员,但 Python 和 OpenCV 经验有限。我正在裁剪电影帧,尝试使用电影的可见穿孔和边缘作为参考。附加的是单个帧(原始 1920 x 1080)和从该帧派生的掩码被传递给下面代码中的 findcountours。 FindContours 找到 3 个轮廓,但只有最大的(右框架边界)是正确的,其他两个(驱动器穿孔)没有被正确检测到。你们中的一位能不能告诉我我做错了什么,并指出我向善的方向?
我使用 Python 3.8 和 cv2 的关联版本。
这是使用下面的代码派生的 sample frame 和 mask。
谢谢
克里斯
for file in args.files:
idx += 1
if os.path.isfile(file):
# load the input image (whose path was supplied via command line
# argument) and display the image to our screen
image = cv2.imread(file)
if image is None:
print('Could not open or find the image: ',args["image"])
fileinput.close()
exit(0)
image_clone = image.copy()
image_height = image.shape[0]
image_width = image.shape[1]
"""
Threshold image
"""
hsv = cv2.cvtColor(image.copy(),cv2.COLOR_BGR2HSV)
#
# # define range of white color in HSV
# # change it according to your need !
lower_white = np.array([0,235],dtype=np.uint8)
upper_white = np.array([0,255],dtype=np.uint8)
#
# Threshold the HSV image to get only white colors
mask = cv2.inRange(hsv,lower_white,upper_white)
mask = cv2.copyMakeBorder(mask,10,cv2.BORDER_CONSTANT,value=[0,0])
# I have seen some spurIoUs pixels so I filter
se1 = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
mask = cv2.morphologyEx(mask,cv2.MORPH_OPEN,se1,iterations = 5)
# Make sure I get rid of noise
mask = cv2.GaussianBlur(mask,5),0)
cny = cv2.Canny(mask.copy(),255/3,255)
mask = cv2.bitwise_xor(mask,cny)
cv2.imwrite(os.path.splitext(file)[0] + '_mask_' + str(idx) + '.tif',mask)
dsize = (int(mask.shape[1] * (50 / 100)),int(mask.shape[0] * (50 / 100)))
# # # # # #
cv2.imshow("Mask Image",cv2.resize(mask.copy(),dsize))
cv2.waitKey(0)
cv2.destroyWindow("Mask Image")
contours,_ = cv2.findContours(mask,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours,key=cv2.contourArea,reverse=True)
#
new_image = np.zeros((mask.shape[0],mask.shape[1]),np.uint8) * 0
#
cv2.drawContours(new_image,[max(contours,key=cv2.contourArea)],-1,255,thickness=-1)
dsize = (int(new_image.shape[1] * (50 / 100)),int(new_image.shape[0] * (50 / 100)))
# # # # # # #
cv2.imshow("Mask Image",cv2.resize(new_image.copy(),dsize))
cv2.waitKey(0)
cv2.destroyWindow("Mask Image")
解决方法
问题在于函数 drawContours
的参数。第二个参数包含应该绘制的 contours
,但您似乎正在过滤较小的轮廓并仅存储/绘制最大的轮廓,在这种情况下,是框架右侧的轮廓。不要过滤您的 contours
。尝试像这样计算它们:
# Find the contours on the mask image:
contours,hierarchy = cv2.findContours(mask,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
# Draw the contours on the mask image:
cv2.drawContours(maskCopy,contours,-1,(0,255,0),3)
注意第三个参数是-1
,这意味着绘制所有找到的轮廓。这就是你得到的:
此外,您可能希望将这些轮廓转换为 bounding rectangles
,以便轻松裁剪感兴趣的区域。这是将轮廓转换为 4-vertex polygon
的方法:
contoursPoly = [None] * len(contours)
boundRect = []
# Contour to Bounding Rects:
for i,c in enumerate(contours):
# Approximate each contour to a bonding rectangle:
contoursPoly[i] = cv2.approxPolyDP(c,3,True)
# Append each bounding rect to the list:
boundRect.append(cv2.boundingRect(contours_poly[i]))
# Draw the Bounding Rects:
rectColor = (0,0)
for i in range(len(boundRect)):
cv2.rectangle(maskCopy,(int(boundRect[i][0]),int(boundRect[i][1])),(int(boundRect[i][0] + boundRect[i][2]),int(boundRect[i][1] + boundRect[i][3])),rectColor,5)
现在注意轮廓如何近似为矩形:
现在,您使用一些 morphology
来获得干净的二进制掩码。这实际上非常好,但形态学操作稍微扭曲了掩码的斑点,引入了位置偏移。查看覆盖在原始图像中的边界矩形:
看到位置偏移了吗?您可以尝试应用额外的 morphology
来恢复 blob 的原始位置。
第一种选择:
不要使用形态学,只考虑外轮廓。外轮廓是没有子(内)轮廓的父轮廓。这将过滤掉您可能有的任何可能的噪音:
# BGR to HSV conversion:
hsv = cv2.cvtColor(inputImage.copy(),cv2.COLOR_BGR2HSV)
# HSV Range definition:
lower_white = np.array([0,235],dtype=np.uint8)
upper_white = np.array([0,255],dtype=np.uint8)
# Get the HSV mask:
mask = cv2.inRange(hsv,lower_white,upper_white)
# Find the big contours/blobs on the filtered image:
contours,cv2.CHAIN_APPROX_SIMPLE)
contoursPoly = [None] * len(contours)
boundRect = []
# Alright,just look for the outer bounding boxes:
for i,c in enumerate(contours):
if hierarchy[0][i][3] == -1:
contoursPoly[i] = cv2.approxPolyDP(c,True)
boundRect.append(cv2.boundingRect(contoursPoly[i]))
# Draw the bounding boxes on the (copied) input image:
for i in range(len(boundRect)):
color = (0,0)
cv2.rectangle(inputCopy,color,5)
你明白了:
第二种选择:
使用 reduce 函数将二值掩码缩减为 1 x columns
矩阵,检测轮廓并提取内部坐标以裁剪框架:
# Reduce matrix to a 1 row x n columns matrix:
reducedMask = cv2.reduce(mask,cv2.REDUCE_MAX)
reduce 函数将创建一个 1 x columns
矩阵(实际上是一个 vector),其中包含所有列的 MAX
值。二进制掩码中的 MAX
值 (255
) 对应于感兴趣的斑点的位置,因此,将图像缩小到这个新掩码:
现在的想法是获取两个白色部分的坐标。这可以通过一种以上的方法来实现。现在让我们坚持寻找轮廓:
# Find the big contours/blobs on the filtered image:
contours,hierarchy = cv2.findContours(reducedMask,True)
boundRect.append(cv2.boundingRect(contoursPoly[i]))
到目前为止获得的轮廓描述了两个白色部分。我们需要这些线的内部坐标。让我们从最小到最大 sort
绑定矩形列表。列表是一个 tuple
,每个 tuple
的第一个元素是 top left
(或水平)坐标,所以我们有排序列表后的第一个白色部分:
# Sort rects from smallets to largest:
sortedRectangles = sorted(boundRect,key = lambda topLeft: topLeft[0])
# Set Top Left crop coordinate:
startX = sortedRectangles[0][0] + sortedRectangles[0][2]
# Set Lower Right crop coordinate:
endX = sortedRectangles[1][0]
现在,只需使用此信息裁剪图像:
# Crop the roi:
imageSize = inputCopy.shape
imageHeight = imageSize[0]
croppedImg = inputCopy[0:0+imageHeight,startX:endX]
你会得到最终的裁剪图像: