如何通过识别 Python 中的单条或多条水平线来分割图像?

How to split an image by identifying single or multiple horizontal lines in Python?

我想根据问题之间的淡灰线使用 Python 将图像分成多个部分。(如下图所示)。有办法吗?

您可以创建水平线的掩码,然后使用cv2.reduce将图像缩小 使用 MAX 值。通过检测轮廓,您可以计算缩小蒙版中线条的起始 垂直 坐标,最后使用此信息 crop 图像。像这样:

# Set image path
imagePath = "D://opencvImages//"
imageName = "zlSGu.jpg"

# Read image:
inputImage = cv2.imread(imagePath + imageName)
# Store a copy for results:
inputCopy = inputImage.copy()

# Convert BGR to grayscale:
grayInput = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)

# Set a lower and upper range for the threshold:
lowerThresh = 230
upperThresh = 235

# Get the lines mask:
mask = cv2.inRange(grayInput, lowerThresh, upperThresh)

这为您提供了线条掩码:

有点吵,你的图片被压缩了。让我们应用一个最小面积为 50 的 areaFilter 来滤除此噪声:

# Set a filter area on the mask:
minArea = 50
mask = areaFilter(minArea, mask)

这是过滤后的掩码:

现在,使用 MAX (255) 强度值将图像缩小为一列:

# Reduce matrix to a n row x 1 columns matrix:
reducedImage = cv2.reduce(mask, 1, cv2.REDUCE_MAX)

这是缩小后的图像,这里有点难看,但只显示灰色线条(缩小为一列)。现在,让我们检测这些线的起点和终点——它们实际上只是一个垂直坐标。我们可以从直线的边界框计算出这个坐标:

# Find the big contours/blobs on the filtered image:
contours, hierarchy = cv2.findContours(mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

# Store the lines here:
separatingLines = []

# We need some dimensions of the original image:
imageHeight = inputCopy.shape[0]
imageWidth = inputCopy.shape[1]

# Look for the outer bounding boxes:
for _, c in enumerate(contours):

    # Approximate the contour to a polygon:
    contoursPoly = cv2.approxPolyDP(c, 3, True)

    # Convert the polygon to a bounding rectangle:
    boundRect = cv2.boundingRect(contoursPoly)

    # Get the bounding rect's data:
    [x, y, w, h] = boundRect

    # Start point and end point:
    lineCenter = y + (0.5 * h)
    startPoint = (0,int(lineCenter))
    endPoint = (int(imageWidth), int(lineCenter))

    # Store the end point in list:
    separatingLines.append( endPoint )

    # Draw the line using the start and end points:
    color = (0, 255, 0)
    cv2.line(inputCopy, startPoint, endPoint, color, 2)

    # Show the image:
    cv2.imshow("inputCopy", inputCopy)
    cv2.waitKey(0)

我另外将行的数据存储在 separatingLines 列表中。此外,仅出于显示目的,我在原始输入上绘制了线条。这是已识别行的图像:

现在,这些行未排序。让我们 sort 他们基于他们的垂直坐标。行正确排序后,我们可以 crop 每个部分,因为我们循环遍历行列表。像这样:

# Sort the list based on ascending Y values:
separatingLines = sorted(separatingLines, key=lambda x: x[1])

# The past processed vertical coordinate:
pastY = 0

# Crop the sections:
for i in range(len(separatingLines)):

    # Get the current line width and starting y:
    (sectionWidth, sectionHeight) = separatingLines[i]

    # Set the ROI:
    x = 0
    y = pastY
    cropWidth = sectionWidth
    cropHeight = sectionHeight - y

    # Crop the ROI:
    currentCrop = inputImage[y:y + cropHeight, x:x + cropWidth]
    cv2.imshow("Current Crop", currentCrop)
    cv2.waitKey(0)

    # Set the next starting vertical coordinate:
    pastY = sectionHeight

这些是图像的裁剪部分。请注意,这些是单独的图片:

这是areaFilter函数的定义和实现:

def areaFilter(minArea, inputImage):
    # Perform an area filter on the binary blobs:
    componentsNumber, labeledImage, componentStats, componentCentroids = \
    cv2.connectedComponentsWithStats(inputImage, connectivity=4)

    # Get the indices/labels of the remaining components based on the area stat
    # (skip the background component at index 0)
    remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]

    # Filter the labeled pixels based on the remaining labels,
    # assign pixel intensity to 255 (uint8) for the remaining pixels
    filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')

    return filteredImage