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

Jay*_*til 0 python opencv image image-segmentation

我想根据问题之间的微弱灰线使用 Python 将图像拆分为多个部分。(如下图所示)。有没有办法这样做? 要分割的图像

sta*_*ine 7

可以创建一个掩模水平线,然后使用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)
Run Code Online (Sandbox Code Playgroud)

这为您提供了线条掩码:

这有点嘈杂,你的图像被压缩了。让我们应用areaFilter最小面积为 50 的 a 来过滤掉这种噪音:

# Set a filter area on the mask:
minArea = 50
mask = areaFilter(minArea, mask)
Run Code Online (Sandbox Code Playgroud)

这是过滤后的掩码:

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

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

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

# 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)
Run Code Online (Sandbox Code Playgroud)

我还在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
Run Code Online (Sandbox Code Playgroud)

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

这是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
Run Code Online (Sandbox Code Playgroud)