UIBezierPath正方形检测失效求助及形状检测工具推荐
解决UIBezierPath正方形检测问题
一、手动实现正确的正方形检测逻辑
你的自定义方法一直返回true,核心问题是没处理手绘路径的冗余点和角度/长度的误差容忍——手绘路径不可能绝对规整,必须做近似判断。以下是可落地的步骤:
1. 先简化路径,提取关键拐点
手绘生成的UIBezierPath包含大量连续点,直接计算角度会出错,先用道格拉斯-普克算法简化路径,只保留形状的关键拐点:
extension UIBezierPath { func simplified(epsilon: CGFloat = 5.0) -> UIBezierPath { guard let points = cgPath.elements as? [CGPoint] else { return self } guard points.count > 2 else { return self } // 道格拉斯-普克算法简化实现 func douglasPeucker(_ points: [CGPoint], epsilon: CGFloat) -> [CGPoint] { var maxDistance: CGFloat = 0 var index = 0 let end = points.count - 1 for i in 1..<end { let distance = perpendicularDistance(points[i], lineStart: points[0], lineEnd: points[end]) if distance > maxDistance { maxDistance = distance index = i } } if maxDistance > epsilon { let left = douglasPeucker(Array(points[0...index]), epsilon: epsilon) let right = douglasPeucker(Array(points[index...end]), epsilon: epsilon) return left + right.dropFirst() } else { return [points[0], points[end]] } } func perpendicularDistance(_ point: CGPoint, lineStart: CGPoint, lineEnd: CGPoint) -> CGFloat { let lineVec = CGPoint(x: lineEnd.x - lineStart.x, y: lineEnd.y - lineStart.y) let pointVec = CGPoint(x: point.x - lineStart.x, y: point.y - lineStart.y) let lineLengthSquared = lineVec.x * lineVec.x + lineVec.y * lineVec.y if lineLengthSquared == 0 { return hypot(pointVec.x, pointVec.y) } let t = (pointVec.x * lineVec.x + pointVec.y * lineVec.y) / lineLengthSquared let clampedT = max(0, min(1, t)) let projection = CGPoint(x: lineStart.x + clampedT * lineVec.x, y: lineStart.y + clampedT * lineVec.y) return hypot(point.x - projection.x, point.y - projection.y) } let simplifiedPoints = douglasPeucker(points, epsilon: epsilon) let simplifiedPath = UIBezierPath() guard let firstPoint = simplifiedPoints.first else { return self } simplifiedPath.move(to: firstPoint) for point in simplifiedPoints.dropFirst() { simplifiedPath.addLine(to: point) } simplifiedPath.close() return simplifiedPath } }
2. 实现正方形检测逻辑
简化路径后,按以下条件判断:
- 简化后的拐点数量为4(允许首尾点接近的情况)
- 四条边的长度近似相等(误差范围可调整)
- 相邻边的夹角接近90度(允许角度误差)
- 路径接近闭合(首尾点距离小于阈值)
extension UIBezierPath { func isApproximateSquare(toleranceAngle: CGFloat = 10.0, toleranceLength: CGFloat = 0.15, closeTolerance: CGFloat = 10.0) -> Bool { let simplifiedPath = self.simplified() guard let points = simplifiedPath.cgPath.elements as? [CGPoint], points.count >= 4 else { return false } // 处理首尾点接近的情况,合并为4个有效点 let closedPoints: [CGPoint] let firstLastDistance = hypot(points.first!.x - points.last!.x, points.first!.y - points.last!.y) if firstLastDistance < closeTolerance { closedPoints = Array(points[0..<4]) } else { guard points.count == 4 else { return false } closedPoints = points } // 计算四条边的向量与长度 var edges: [(vector: CGPoint, length: CGFloat)] = [] for i in 0..<4 { let start = closedPoints[i] let end = closedPoints[(i+1)%4] let vec = CGPoint(x: end.x - start.x, y: end.y - start.y) let length = hypot(vec.x, vec.y) edges.append((vec, length)) } // 验证边长近似相等 let averageLength = edges.map { $0.length }.reduce(0, +) / 4 for edge in edges { guard abs(edge.length - averageLength) <= averageLength * toleranceLength else { return false } } // 验证相邻边夹角接近90度 let rightAngle = CGFloat.pi / 2 let angleTolerance = toleranceAngle * CGFloat.pi / 180 // 转弧度 for i in 0..<4 { let currentVec = edges[i].vector let nextVec = edges[(i+1)%4].vector let dotProduct = currentVec.x * nextVec.x + currentVec.y * nextVec.y let currentLength = edges[i].length let nextLength = edges[(i+1)%4].length guard currentLength > 0, nextLength > 0 else { return false } let cosTheta = dotProduct / (currentLength * nextLength) let theta = acos(max(-1, min(1, cosTheta))) // 避免数值溢出 // 夹角允许接近90度或270度(向量方向可能相反) let diff1 = abs(theta - rightAngle) let diff2 = abs(theta - (CGFloat.pi * 3 / 2)) guard diff1 <= angleTolerance || diff2 <= angleTolerance else { return false } } return true } }
二、推荐支持UIBezierPath的形状检测库
如果不想手动实现,可以用这些工具:
- BezierPathKit:专门处理UIBezierPath的几何计算,包含形状检测、路径简化、交点计算等功能,直接支持判断路径是否为正方形。
- Apple Vision框架:虽然主打图像识别,但可以把UIBezierPath渲染成UIImage,再用
VNDetectRectanglesRequest检测矩形,通过宽高比判断是否为正方形,适合同时处理手绘和图像的场景。
内容的提问来源于stack exchange,提问作者wprpovhtguetbfdotm
相关产品推荐
相关产品推荐

