125 lines
4.7 KiB
Python
125 lines
4.7 KiB
Python
import cv2
|
||
from nonebot import logger
|
||
import numpy as np
|
||
|
||
from shapely.geometry import Polygon
|
||
from shapely.ops import unary_union
|
||
|
||
def fix_with_shapely(contours: list) -> np.ndarray:
|
||
"""
|
||
使用Shapely库处理复杂自相交
|
||
"""
|
||
fixed_polygons = []
|
||
for contour in contours:
|
||
# 转换输入为正确的格式
|
||
contour_array = contour.reshape(-1, 2)
|
||
# 转换为Shapely多边形
|
||
polygon = Polygon(contour_array)
|
||
if not polygon.is_valid:
|
||
polygon = polygon.buffer(0)
|
||
fixed_polygons.append(polygon)
|
||
# 接下来把所有轮廓合并为一个
|
||
if len(fixed_polygons) >= 1:
|
||
merged_polygon = unary_union(fixed_polygons)
|
||
if merged_polygon.geom_type == 'Polygon':
|
||
merged_points = np.array(merged_polygon.exterior.coords, dtype=np.int32)
|
||
elif merged_polygon.geom_type == 'MultiPolygon':
|
||
largest = max(merged_polygon.geoms, key=lambda p: p.area)
|
||
merged_points = np.array(largest.exterior.coords, dtype=np.int32)
|
||
return [merged_points.reshape(-1, 1, 2)]
|
||
else:
|
||
logger.warning("No valid contours found after fixing with Shapely.")
|
||
return [np.array([], dtype=np.int32).reshape(0, 1, 2)]
|
||
|
||
def expand_contours(contours, stroke_width):
|
||
"""
|
||
将轮廓向外扩展指定宽度
|
||
|
||
参数:
|
||
contours: OpenCV轮廓列表
|
||
stroke_width: 扩展宽度(像素)
|
||
|
||
返回:
|
||
扩展后的轮廓列表
|
||
"""
|
||
expanded_contours = []
|
||
|
||
for cnt in contours:
|
||
# 将轮廓转换为点列表
|
||
points = cnt.reshape(-1, 2).astype(np.float32)
|
||
n = len(points)
|
||
|
||
if n < 3:
|
||
continue # 至少需要3个点才能形成多边形
|
||
|
||
expanded_points = []
|
||
|
||
for i in range(n):
|
||
# 获取当前点、前一个点和后一个点
|
||
p_curr = points[i]
|
||
p_prev = points[(i - 1) % n]
|
||
p_next = points[(i + 1) % n]
|
||
|
||
# 计算两条边的向量
|
||
v1 = p_curr - p_prev # 前一条边(从prev到curr)
|
||
v2 = p_next - p_curr # 后一条边(从curr到next)
|
||
|
||
# 归一化
|
||
norm1 = np.linalg.norm(v1)
|
||
norm2 = np.linalg.norm(v2)
|
||
|
||
if norm1 == 0 or norm2 == 0:
|
||
# 如果有零向量,直接沿着法线方向扩展
|
||
edge_dir = np.array([0, 0])
|
||
if norm1 > 0:
|
||
edge_dir = v1 / norm1
|
||
elif norm2 > 0:
|
||
edge_dir = v2 / norm2
|
||
normal = np.array([-edge_dir[1], edge_dir[0]])
|
||
expanded_point = p_curr + normal * stroke_width
|
||
else:
|
||
# 归一化向量
|
||
v1_norm = v1 / norm1
|
||
v2_norm = v2 / norm2
|
||
|
||
# 计算两条边的单位法向量(指向多边形外部)
|
||
n1 = np.array([-v1_norm[1], v1_norm[0]])
|
||
n2 = np.array([-v2_norm[1], v2_norm[0]])
|
||
|
||
# 计算角平分线方向(两个法向量的和)
|
||
bisector = n1 + n2
|
||
|
||
# 计算平分线的长度
|
||
bisector_norm = np.linalg.norm(bisector)
|
||
|
||
if bisector_norm == 0:
|
||
# 如果两条边平行(同向或反向),取任一法线方向
|
||
expanded_point = p_curr + n1 * stroke_width
|
||
else:
|
||
# 归一化平分线
|
||
bisector_normalized = bisector / bisector_norm
|
||
|
||
# 计算偏移距离(考虑夹角)
|
||
# 使用余弦定理计算正确的偏移距离
|
||
cos_angle = np.dot(v1_norm, v2_norm)
|
||
angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
|
||
|
||
if abs(np.pi - angle) < 1e-6: # 近似平角
|
||
# 接近直线的情况
|
||
offset_distance = stroke_width
|
||
else:
|
||
# 计算正确的偏移距离
|
||
offset_distance = stroke_width / np.sin(angle / 2)
|
||
|
||
# 计算扩展点
|
||
expanded_point = p_curr + bisector_normalized * offset_distance
|
||
|
||
expanded_points.append(expanded_point)
|
||
|
||
# 将扩展后的点转换为整数坐标
|
||
expanded_cnt = np.array(expanded_points, dtype=np.float32).reshape(-1, 1, 2)
|
||
expanded_contours.append(expanded_cnt.astype(np.int32))
|
||
|
||
expanded_contours = fix_with_shapely(expanded_contours)
|
||
|
||
return expanded_contours |