You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将分割模型生成的二值掩码轮廓近似为四个角点

提取二值掩码轮廓的最小面积四点包围方案

核心思路

原方案的问题在于minAreaRect强制生成矩形,无法适配非矩形的目标轮廓;approxPolyDP的近似结果受epsilon参数影响极大,难以稳定得到四个点且保证最小面积。我们可以通过以下步骤解决:

  • 预处理优化:用开运算替代单纯腐蚀,在去除噪声的同时减少轮廓过度收缩
  • 凸包简化:提取轮廓的凸包,过滤凹点和局部噪声,保留核心外围边界
  • 最小面积四边形拟合:基于凸包点集,找到能覆盖大部分轮廓点的最小面积四边形

实现代码

替换原代码中轮廓近似及之后的逻辑,完整修改后的代码如下:

#!/usr/bin/env python3

import os
from os import path as osp
import cv2
import numpy as np

path = "seg_masks"
im_list = os.listdir(path)

def lcc(binary_image:np.ndarray)->np.ndarray:
    # 获取最大连通分量
    num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary_image, connectivity=8)
    largest_component_label = np.argmax(stats[1:, cv2.CC_STAT_AREA]) + 1  # 跳过背景标签0
    largest_component_mask = (labels == largest_component_label).astype(np.uint8)
    return largest_component_mask

def point_in_quadrilateral(point, quad):
    # 判断点是否在四边形内部(含边界)
    def cross(o, a, b):
        return (a[0]-o[0])*(b[1]-o[1]) - (a[1]-o[1])*(b[0]-o[0])
    a, b, c, d = quad
    d1 = cross(a, b, point)
    d2 = cross(b, c, point)
    d3 = cross(c, d, point)
    d4 = cross(d, a, point)
    has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0) or (d4 < 0)
    has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0) or (d4 > 0)
    return not (has_neg and has_pos)

def find_min_area_quadrilateral(contour, coverage_threshold=0.95):
    # 计算轮廓凸包
    hull = cv2.convexHull(contour)
    hull_points = np.squeeze(hull, axis=1)
    n = len(hull_points)
    if n < 4:
        return None  # 凸包点不足4个,无法生成四边形

    min_area = float('inf')
    best_quad = None

    # 遍历凸包中所有四点组合,筛选覆盖达标且面积最小的四边形
    for i in range(n):
        for j in range(i+1, n):
            for k in range(j+1, n):
                for l in range(k+1, n):
                    quad = [hull_points[i], hull_points[j], hull_points[k], hull_points[l]]
                    area = cv2.contourArea(np.array([quad], dtype=np.int32))
                    if area >= min_area:
                        continue
                    # 检查轮廓点覆盖比例
                    covered = 0
                    for p in contour:
                        if point_in_quadrilateral(p[0], quad):
                            covered +=1
                    coverage = covered / len(contour)
                    if coverage >= coverage_threshold and area < min_area:
                        min_area = area
                        best_quad = quad

    # 若遍历无结果,用凸包极值点生成初始四边形
    if best_quad is None:
        x_sorted = sorted(hull_points, key=lambda p: p[0])
        y_sorted = sorted(hull_points, key=lambda p: p[1])
        leftmost = x_sorted[0]
        rightmost = x_sorted[-1]
        topmost = y_sorted[0]
        bottommost = y_sorted[-1]
        best_quad = [leftmost, topmost, rightmost, bottommost]

    return np.array(best_quad, dtype=np.int32)

for img_name in im_list:
    bgr_img_mask = cv2.imread(osp.join(path, img_name), 0)
    cv2.imwrite(osp.join(path, "white", img_name), bgr_img_mask)
    lcc_mask = lcc(bgr_img_mask)
    # 开运算替代单纯腐蚀,保留轮廓形状的同时去噪
    cl_ker = 5
    kernel = np.ones((cl_ker, cl_ker), np.uint8)
    opening = cv2.morphologyEx(lcc_mask, cv2.MORPH_OPEN, kernel, iterations=2)

    contours, _ = cv2.findContours(
        opening, mode=cv2.RETR_EXTERNAL,  # 仅提取最外层轮廓,减少干扰
        method=cv2.CHAIN_APPROX_SIMPLE   # 压缩轮廓点,降低计算量
    )
    if(len(contours)):
        max_cnt = max(contours, key=cv2.contourArea)
        quad = find_min_area_quadrilateral(max_cnt)
        if quad is not None:
            # 绘制目标四边形
            bgr_img_mask = cv2.drawContours(bgr_img_mask, [quad], 0, 200, 2)
            # 绘制凸包作为参考
            hull = cv2.convexHull(max_cnt)
            cv2.drawContours(bgr_img_mask, [hull], -1, 128, 1)

    win_name = "img"
    cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)
    cv2.imshow(win_name, bgr_img_mask)
    cv2.waitKey(0)
cv2.destroyAllWindows()

关键优化点说明

  • 预处理:MORPH_OPEN(开运算)先腐蚀去噪,再膨胀恢复轮廓,避免单纯腐蚀导致的轮廓过度收缩
  • 轮廓提取:RETR_EXTERNAL只保留最外层轮廓,CHAIN_APPROX_SIMPLE压缩冗余点,大幅降低后续计算量
  • 凸包简化:过滤轮廓的凹点和局部噪声,保留最外围边界,是最小包围计算的核心基础
  • 四边形筛选:通过遍历凸包四点组合,确保生成的四边形覆盖指定比例(默认95%)的原始轮廓点,同时面积最小
  • 效率兼容:若凸包点数量过大,可替换为旋转卡尺算法(O(n)时间复杂度)来计算最小面积外接四边形

内容的提问来源于stack exchange,提问作者bhomaidan90

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:23:17