Largest Perimeter Triangle

本文介绍了一种简单有效的算法,用于从给定长度数组中找出能构成最大周长三角形的三个元素。通过排序和逆向遍历,确保了算法的效率与正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0x00

难度:Easy

Given an array A of positive lengths, return the largest perimeter of a triangle with non-zero area, formed from 3 of these lengths.

If it is impossible to form any triangle of non-zero area, return 0.

 

Example 1:

Input: [2,1,2]
Output: 5
Example 2:

Input: [1,2,1]
Output: 0
Example 3:

Input: [3,2,3,4]
Output: 10
Example 4:

Input: [3,6,2,3]
Output: 8
 

Note:

3 <= A.length <= 10000
1 <= A[i] <= 10^6

0x01

思路其实很简单,三角形成立的条件在于两条边之和大于第三条边。
我们假设有三条边:a,b,c,满足 a >= b >= c。
那么如果存在 b+c > a,则 以下等式肯定成立:
a+b > c
a+c > b
因此只需要两条较小的边长之和大于最大的边,就必定是一个三角形。
根据题目的要求需要找到边长和最长的三角形,只需要对数组排序,并且从大到小检查条件即可。

0x02

func largestPerimeter(A []int) int {
    if len(A) < 3 {
        return 0
    }
    sort.Ints(A)
    for i := len(A) - 3;i >= 0;i-- {
        if A[i] + A[i + 1] > A[i + 2] {
            return A[i] + A[i + 1] + A[i + 2]
        }
    }
    return 0
}
import cv2 import numpy as np import time from collections import defaultdict import math def detect_a4_paper_and_shapes(frame, base_area): """检测A4纸并识别其上的形状,返回处理后的帧、形状数据和变换后区域面积""" frame = cv2.resize(frame, (800, 600)) output_frame = frame.copy() shape_data = [] # 存储形状信息:(形状名称, 面积mm², 占比%) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (7, 7), 0) edged = cv2.Canny(blurred, 30, 120) contours, _ = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) a4_contour = None warped_area = 0 # 默认值为0,表示未检测到A4纸 # 筛选A4纸轮廓 for contour in contours: if cv2.contourArea(contour) < 5000: continue peri = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, 0.02 * peri, True) if len(approx) == 4 and cv2.isContourConvex(approx): a4_contour = approx break if a4_contour is not None: cv2.drawContours(output_frame, [a4_contour], -1, (0, 0, 255), 2) cv2.putText(output_frame, "A4 Paper", (a4_contour[0][0][0], a4_contour[0][0][1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) # 透视变换准备 pts = a4_contour.reshape(4, 2) rect = np.zeros((4, 2), dtype="float32") s = pts.sum(axis=1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] diff = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] (tl, tr, br, bl) = rect widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) maxWidth = max(int(widthA), int(widthB)) heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) maxHeight = max(int(heightA), int(heightB)) # 计算变换后的区域面积 warped_area = maxWidth * maxHeight dst = np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype="float32") try: M = cv2.getPerspectiveTransform(rect, dst) warped = cv2.warpPerspective(frame, M, (maxWidth, maxHeight)) # 形状检测 gray_warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY) thresh = cv2.adaptiveThreshold(gray_warped, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) contours_warped, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 像素到毫米的转换比例(基于基准面积) px_per_mm = maxWidth / math.sqrt(base_area * (maxWidth / maxHeight)) # 适配基准面积的比例计算 # 识别形状并计算面积 for cnt in contours_warped: area_px = cv2.contourArea(cnt) if area_px < 100: continue # 计算实际面积和占比(占空比=图形面积/基准面积) area_mm2 = area_px / (px_per_mm ** 2) ratio = (area_mm2 / base_area) * 100 # 占空比(百分比) peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.03 * peri, True) shape = "Unknown" if len(approx) == 3: shape = "三角形" elif len(approx) == 4: x, y, w, h = cv2.boundingRect(approx) aspect_ratio = float(w) / h if 0.95 <= aspect_ratio <= 1.05: shape = "正方形" else: shape = "长方形" else: perimeter = cv2.arcLength(cnt, True) circularity = 4 * np.pi * area_px / (perimeter ** 2) if perimeter > 0 else 0 if 0.7 < circularity < 1.3: shape = "圆形" elif 5 <= len(approx) <= 8: shape = "多边形" # 只记录目标形状 if shape in ["三角形", "圆形", "正方形"]: shape_data.append((shape, area_mm2, ratio)) # 在图像上标记 M = cv2.moments(cnt) if M["m00"] != 0: cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) cv2.drawContours(warped, [cnt], -1, (0, 255, 0), 2) cv2.putText(warped, f"{shape} {ratio:.1f}%", (cX - 50, cY), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2) # 放置矫正后的图像 h, w = warped.shape[:2] if h < output_frame.shape[0] and w < output_frame.shape[1]: output_frame[0:h, 0:w] = warped else: scale = min(output_frame.shape[0] / h, output_frame.shape[1] / w) warped = cv2.resize(warped, (int(w * scale), int(h * scale))) h, w = warped.shape[:2] output_frame[0:h, 0:w] = warped except Exception as e: print(f"处理出错: {e}") return output_frame, shape_data, warped_area def calculate_dimensions(shape, avg_area): """根据图形类型和平均面积计算尺寸(边长或直径,单位:毫米)""" if shape == "正方形": # 正方形面积 = 边长² → 边长 = √面积 side_length = math.sqrt(avg_area) return f"边长: {side_length:.2f} mm" elif shape == "圆形": # 圆形面积 = π×(直径/2)² → 直径 = 2×√(面积/π) diameter = 2 * math.sqrt(avg_area / math.pi) return f"直径: {diameter:.2f} mm" elif shape == "三角形": # 假设为等边三角形:面积 = (√3/4)×边长² → 边长 = √(4×面积/√3) side_length = math.sqrt((4 * avg_area) / math.sqrt(3)) return f"等边三角形边长: {side_length:.2f} mm" return "无法计算尺寸" def set_camera_resolution(cap, width=640, height=480): """尝试设置摄像头分辨率""" # 设置目标分辨率 cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height) # 获取实际设置的分辨率 actual_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) actual_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) return actual_width, actual_height def main(): # 设定基准面积(整张纸的面积,单位:平方毫米) base_area = 43690 # 更新为43690平方毫米 cap = cv2.VideoCapture(0) if not cap.isOpened(): print("无法打开摄像头") return # 设置摄像头分辨率为1080p (1920x1080) actual_width, actual_height = set_camera_resolution(cap, 640, 480) print(f"尝试设置摄像头分辨率: 1280x720") print(f"实际分辨率: {actual_width}x{actual_height}") print(f"\n程序启动,以基准面积 {base_area} mm² 计算占空比,将进行4秒检测...") print("按 'q' 键可提前退出") # 存储4秒内的检测数据 detection_history = defaultdict(list) # 格式: {形状: [(面积, 占比), ...]} warped_areas = [] # 存储变换后的区域面积 start_time = time.time() detection_duration = 3 # 检测持续时间(秒) try: while True: # 检查是否达到检测时间 elapsed_time = time.time() - start_time if elapsed_time >= detection_duration: break ret, frame = cap.read() if not ret: print("无法获取视频帧") break frame = cv2.flip(frame, 1) # 镜像翻转 # 显示剩余时间 remaining_time = max(0, int(detection_duration - elapsed_time)) cv2.putText(frame, f"检测剩余: {remaining_time}秒", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # 显示基准面积 cv2.putText(frame, f"基准面积: {base_area} mm²", (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2) # 显示当前分辨率 cv2.putText(frame, f"分辨率: {actual_width}x{actual_height}", (10, 110), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) # 处理帧并检测形状(传入基准面积) processed_frame, shape_data, warped_area = detect_a4_paper_and_shapes(frame, base_area) # 如果检测到了A4纸,记录区域面积 if warped_area > 0: warped_areas.append(warped_area) # 记录检测数据 for shape, area, ratio in shape_data: detection_history[shape].append((area, ratio)) cv2.imshow("A4纸形状检测", processed_frame) if cv2.waitKey(1) & 0xFF == ord('q'): print("用户提前退出") return # 计算并打印结果(含尺寸计算) print("\n===== 4秒检测结果 =====") print(f"基准面积(整张纸): {base_area} mm²") print(f"摄像头分辨率: {actual_width}x{actual_height}") # 打印平均变换区域面积 if warped_areas: avg_warped_area = sum(warped_areas) / len(warped_areas) print(f"平均变换区域面积: {avg_warped_area:.2f} 像素") print(f"距离: {17910*avg_warped_area**(-0.501)-3.495:.2f} cm") else: print("未检测到A4纸") if not detection_history: print("未检测到任何图形") else: for shape, data in detection_history.items(): # 计算平均面积和平均占空比 avg_area = sum(area for area, _ in data) / len(data) avg_ratio = sum(ratio for _, ratio in data) / len(data) # 计算尺寸 dimensions = calculate_dimensions(shape, avg_area) print(f"\n{shape}:") print(f" 平均面积: {avg_area:.2f} mm²") print(f" 平均占空比: {avg_ratio:.2f}%(占整张纸面积)") print(f" {dimensions}") print(f" 检测到的次数: {len(data)}") finally: cap.release() cv2.destroyAllWindows() print("\n程序已退出") 用以上代码代替下面代码的可实现功能部分import cv2 import numpy as np import platform import Config from Config import * # noqa: F403 class A4GeometryDetector: def __init__(self): self.object_3d_points = np.array( [ [-A4_WIDTH_MM / 2, -A4_HEIGHT_MM / 2, 0], [A4_WIDTH_MM / 2, -A4_HEIGHT_MM / 2, 0], [A4_WIDTH_MM / 2, A4_HEIGHT_MM / 2, 0], [-A4_WIDTH_MM / 2, A4_HEIGHT_MM / 2, 0], ], dtype=np.float32, ) self.camera_matrix = np.array( Config.camera_matrix, dtype=np.float32, ) self.dist_coeffs = np.array( Config.dist_coeffs, dtype=np.float32, ) self.try_open_camera() self.frame_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) self.frame_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) print(self.frame_width, self.frame_height) self.camera_matrix[0, 2] = self.frame_width / 2 self.camera_matrix[1, 2] = self.frame_height / 2 self.pixel_per_mm = 1 self.edges = None self.finalWarped = None # 分辨率和id设置 def try_open_camera(self, camera_id=1): if platform.system().lower() == "windows": cap = cv2.VideoCapture(camera_id, cv2.CAP_DSHOW) else: cap = cv2.VideoCapture(camera_id) # 或者设置为你想要的分辨率,比如640x480 cap.set(3, 640) cap.set(4, 480) if cap.isOpened(): print(f"成功打开摄像头: {camera_id}") self.cap = cap return True else: cap.release() print("未找到可用的摄像头") return False def detect_corners(self, frame): def angle_sort(pt): vec = pt - center return np.arctan2(vec[1], vec[0]) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) ret, binary_img = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY) edges = cv2.Canny(binary_img, threshold1=50, threshold2=150) contours, _ = cv2.findContours( edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) # 存储符合条件的轮廓及其面积 valid_contours = [] for cnt in contours: epsilon = 0.01 * cv2.arcLength(cnt, closed=True) approx = cv2.approxPolyDP(cnt, epsilon, True) if len(approx) == 4 and cv2.isContourConvex(approx): area = cv2.contourArea(approx) if area < 1000: # 可选:面积过滤 continue corners = approx.reshape(4, 2) valid_contours.append((area, corners)) # 如果没有找到符合条件的轮廓 if not valid_contours: return None # 按面积从大到小排序 valid_contours.sort(key=lambda x: x[0], reverse=True) # 选择面积最大的轮廓 largest_area, corners = valid_contours[0] # 对角点进行排序 center = corners.mean(axis=0) sorted_corners = sorted(corners, key=angle_sort) start_idx = np.argmin([pt[1] for pt in sorted_corners]) ordered = np.roll(sorted_corners, -start_idx, axis=0) return np.array(ordered, dtype=np.float32) def order_points(self, pts): rect = np.zeros((4, 2), dtype=np.float32) s = pts.sum(axis=1) diff = np.diff(pts, axis=1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] return rect def calculate_distance_pnp(self, image_points): if image_points is None or len(image_points) != 4: return None, None, None ordered_points = self.order_points(image_points) try: success, rvec, tvec = cv2.solvePnP( self.object_3d_points, ordered_points, self.camera_matrix, self.dist_coeffs, ) if success: horizontal_baseline_distance = float( np.sqrt(tvec[0][0] ** 2 + tvec[2][0] ** 2) ) return horizontal_baseline_distance, rvec, tvec except: return None, None, None return None, None, None def warp_perspective_from_corners(self, frame, corners, output_size=(600, 400)): if corners is None or len(corners) != 4: return None, None, None rect = self.order_points(corners) dst = np.array( [ [0, 0], [output_size[0] - 1, 0], [output_size[0] - 1, output_size[1] - 1], [0, output_size[1] - 1], ], dtype=np.float32, ) M = cv2.getPerspectiveTransform(rect, dst) Minv = cv2.getPerspectiveTransform(dst, rect) warped = cv2.warpPerspective(frame, M, output_size) return warped, M, Minv def resize_warped(self): up_points = (int(A4_WIDTH_MM * 3), int(A4_HEIGHT_MM * 3)) resized_up = cv2.resize(self.warped, up_points, interpolation=cv2.INTER_LINEAR) self.pixel_per_mm = up_points[0] / A4_WIDTH_MM return resized_up def pixels_to_mm(self, pixels): return pixels / self.pixel_per_mm def mm_to_pixels(self, mm): return mm * self.pixel_per_mm def run(self): ret, frame = self.cap.read() if not ret: return self.frame = frame self.corners = self.detect_corners(frame) if self.corners is None: return self.distance, rvec, tvec = self.calculate_distance_pnp(self.corners) if self.distance is None: return self.warped, M, Minv = self.warp_perspective_from_corners(frame, self.corners) if self.warped is None: return self.finalWarped = self.resize_warped() gray = cv2.cvtColor(self.finalWarped, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (3, 3), 0) ret, binary_img = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY) self.edges = cv2.Canny(binary_img, 50, 150) # 遮盖多余的边框线 edge_bored_width = int(self.mm_to_pixels(BORDER_WIDTH_MM)) + 5 # Top border self.edges[0:edge_bored_width, :] = 0 # Bottom border self.edges[-edge_bored_width:, :] = 0 # Left border self.edges[:, 0:edge_bored_width] = 0 # Right border self.edges[:, -edge_bored_width:] = 0 # 绘制角点 for i, corner in enumerate(self.corners): cv2.circle(frame, tuple(corner.astype(int)), 5, (255, 0, 0), -1) cv2.putText( frame, str(i), tuple(corner.astype(int) + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1, ) # 显示距离 cv2.putText( frame, f"A4 Distance: {self.distance:.1f}mm ({self.distance/10:.1f}cm)", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2, ) class OverlayModelDetector: def __init__(self, geo: A4GeometryDetector): self.geo = geo def extend_edge_endpoints(self, p1, p2, extension_length=30): """ 延申边的两个端点 Args: p1, p2: 边的两个端点 extension_length: 延申长度(像素) Returns: tuple: (延申后的p1, 延申后的p2) """ # 计算边的方向向量 edge_vector = np.array(p2) - np.array(p1) edge_length = np.linalg.norm(edge_vector) if edge_length == 0: return p1, p2 # 单位方向向量 unit_vector = edge_vector / edge_length # 延申端点 extended_p1 = np.array(p1) - unit_vector * extension_length extended_p2 = np.array(p2) + unit_vector * extension_length return extended_p1.astype(int), extended_p2.astype(int) def is_point_in_contour(self, point, contour): """ 使用射线投射算法检测点是否在轮廓内 Args: point: 要检查的点 (x, y) contour: OpenCV轮廓 Returns: bool: True如果点在轮廓内 """ x, y = point[0], point[1] contour_points = contour.reshape(-1, 2) n = len(contour_points) inside = False p1x, p1y = contour_points[0] for i in range(1, n + 1): p2x, p2y = contour_points[i % n] if y > min(p1y, p2y): if y <= max(p1y, p2y): if x <= max(p1x, p2x): if p1y != p2y: xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x if p1x == p2x or x <= xinters: inside = not inside p1x, p1y = p2x, p2y return inside def get_edge_perpendicular_directions(self, p1, p2): """ 获取边的两个垂直方向的单位向量 Args: p1, p2: 边的端点 Returns: tuple: (方向1的单位向量, 方向2的单位向量) """ # 边的方向向量 edge_vector = np.array(p2) - np.array(p1) edge_length = np.linalg.norm(edge_vector) if edge_length == 0: return None, None # 垂直向量(顺时针旋转90度) perp_vector1 = np.array([edge_vector[1], -edge_vector[0]]) perp_vector1 = perp_vector1 / np.linalg.norm(perp_vector1) # 另一个垂直向量(逆时针旋转90度) perp_vector2 = -perp_vector1 return perp_vector1, perp_vector2 def create_square_from_edge(self, p1, p2, direction_vector): """ 根据边和方向创建正方形 Args: p1, p2: 边的端点 direction_vector: 垂直方向的单位向量 Returns: np.array: 正方形的四个顶点,如果无法创建则返回None """ edge_vector = np.array(p2) - np.array(p1) edge_length = np.linalg.norm(edge_vector) if edge_length == 0: return None # 计算正方形的另外两个顶点 p3 = np.array(p2) + direction_vector * edge_length p4 = np.array(p1) + direction_vector * edge_length # 返回正方形的四个顶点(按顺序) square = np.array([p1, p2, p3, p4], dtype=np.int32) return square.reshape(-1, 1, 2) # OpenCV轮廓格式 def is_special_edge(self, p1, p2, contour, extension_length=15): """ 检查是否为特殊边(延申后的端点都不在轮廓内) Args: p1, p2: 边的端点 contour: 轮廓 extension_length: 延申长度 Returns: bool: True如果是特殊边 """ extended_p1, extended_p2 = self.extend_edge_endpoints(p1, p2, extension_length) # 检查延申后的端点是否都不在轮廓内 p1_outside = not self.is_point_in_contour(extended_p1, contour) p2_outside = not self.is_point_in_contour(extended_p2, contour) return p1_outside and p2_outside def find_valid_square_for_edge(self, p1, p2, contour): """ 为特殊边找到有效的正方形假设 Args: p1, p2: 边的端点 contour: 轮廓 Returns: np.array or None: 有效的正方形顶点,如果找不到则返回None """ # 获取两个垂直方向 dir1, dir2 = self.get_edge_perpendicular_directions(p1, p2) if dir1 is None or dir2 is None: return None # 尝试两个方向 for direction in [dir1, dir2]: square = self.create_square_from_edge(p1, p2, direction) if square is None: continue # 计算正方形质心 square_points = square.reshape(-1, 2) centroid = np.mean(square_points, axis=0) # 检查质心是否在轮廓内 if self.is_point_in_contour(centroid, contour): return square return None def detect(self): """ 重构后的检测方法 """ # max_edge_length = self.geo.mm_to_pixels(130) min_edge_length = self.geo.mm_to_pixels(50) extension_delta = self.geo.mm_to_pixels(15) center_thresh = 30 # 获取轮廓 contours, _ = cv2.findContours( self.geo.edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) # 绘制所有轮廓用于调试 # cv2.drawContours( # self.geo.finalWarped, contours, -1, (0, 255, 0), thickness=cv2.FILLED # ) # cv2.imshow("con", self.geo.finalWarped) if not contours: return [] results = [] seen_squares = [] id_counter = 1 for cnt in contours: # 获取轮廓的近似多边形 epsilon = 0.01 * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, epsilon, True) if len(approx) < 4: continue points = [p[0] for p in approx] n = len(points) # 检查每条边 for i in range(n): p1 = points[i] p2 = points[(i + 1) % n] # 计算边长 edge_length = np.linalg.norm(np.array(p2) - np.array(p1)) # 过滤不符合长度要求的边 if edge_length < min_edge_length: continue # 检查是否为特殊边 if not self.is_special_edge(p1, p2, cnt, extension_delta): continue # 为特殊边找到有效的正方形 square = self.find_valid_square_for_edge(p1, p2, cnt) if square is None: continue # 检查是否已经检测到相似的正方形 square_points = square.reshape(-1, 2) square_center = np.mean(square_points, axis=0) is_duplicate = False for prev_center in seen_squares: if np.linalg.norm(prev_center - square_center) < center_thresh: is_duplicate = True break if is_duplicate: continue # 记录检测到的正方形 seen_squares.append(square_center) # 绘制特殊边和假设的正方形 cv2.line(self.geo.finalWarped, tuple(p1), tuple(p2), (255, 0, 0), 3) cv2.polylines( self.geo.finalWarped, [square], isClosed=True, color=(255, 0, 0), thickness=2, ) # 绘制正方形质心 cv2.circle( self.geo.finalWarped, tuple(square_center.astype(int)), 3, (255, 255, 0), -1, ) # 添加标签 edge_mid = ((p1[0] + p2[0]) // 2, (p1[1] + p2[1]) // 2) cv2.putText( self.geo.finalWarped, f"#{id_counter}: {self.geo.pixels_to_mm(edge_length):.1f}mm", edge_mid, cv2.FONT_HERSHEY_SIMPLEX, 1.8, (0, 0, 255), 1, cv2.LINE_AA, ) results.append((id_counter, edge_length, square_center)) id_counter += 1 return results class NormalModelDetector: def __init__(self, geo: A4GeometryDetector): self.geo = geo def detect(self): # 寻找轮廓 contours, _ = cv2.findContours( self.geo.edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE ) # 过滤并分析轮廓 for contour in sorted(contours, key=cv2.contourArea, reverse=True): # 分析几何形状 shape_type, size_pixels, shape_contour = self.analyze_shape(contour) if shape_type and size_pixels: # 将像素尺寸转换为实际尺寸 size_mm = self.geo.pixels_to_mm(size_pixels) # 绘制检测到的几何图形 cv2.drawContours( self.geo.finalWarped, [shape_contour], -1, (255, 0, 255), 3 ) shape_info = self.format_shape_info(shape_type, size_mm) cv2.putText( self.geo.finalWarped, shape_info, (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 255), 2, ) return shape_type, size_mm, shape_contour return None, None, None def analyze_shape(self, contour): """分析轮廓的几何形状""" # 计算轮廓特征 area = cv2.contourArea(contour) perimeter = cv2.arcLength(contour, True) if perimeter == 0: return None, None, None # 多边形逼近 epsilon = 0.02 * perimeter approx = cv2.approxPolyDP(contour, epsilon, True) vertices = len(approx) # 圆形检测 circularity = 4 * np.pi * area / (perimeter**2) if circularity > 0.85: # 圆形 # 计算最小外接圆 (x, y), radius = cv2.minEnclosingCircle(contour) diameter_pixels = radius * 2 return "circle", diameter_pixels, contour elif vertices == 3: # 三角形 # 检查是否为等边三角形 if self.is_equilateral_triangle(approx): # 计算边长(取三边的平均值) side_lengths = [] for i in range(3): p1 = approx[i][0] p2 = approx[(i + 1) % 3][0] side_length = np.linalg.norm(p1 - p2) side_lengths.append(side_length) avg_side_length = np.mean(side_lengths) return "equilateral_triangle", avg_side_length, contour elif vertices == 4: # 四边形 # 检查是否为正方形 if self.is_square(approx): # 计算边长 side_lengths = [] for i in range(4): p1 = approx[i][0] p2 = approx[(i + 1) % 4][0] side_length = np.linalg.norm(p1 - p2) side_lengths.append(side_length) avg_side_length = np.mean(side_lengths) return "square", avg_side_length, contour return None, None, None def is_equilateral_triangle(self, triangle_points): """检查是否为等边三角形""" if len(triangle_points) != 3: return False # 计算三条边的长度 sides = [] for i in range(3): p1 = triangle_points[i][0] p2 = triangle_points[(i + 1) % 3][0] side_length = np.linalg.norm(p1 - p2) sides.append(side_length) # 检查三边长度是否接近相等(允许10%的误差) max_side = max(sides) min_side = min(sides) if max_side == 0: return False ratio = min_side / max_side return ratio > 0.9 # 90%相似度认为是等边三角形 def is_square(self, quad_points): """检查是否为正方形""" if len(quad_points) != 4: return False # 计算四条边的长度 sides = [] for i in range(4): p1 = quad_points[i][0] p2 = quad_points[(i + 1) % 4][0] side_length = np.linalg.norm(p1 - p2) sides.append(side_length) # 检查四边长度是否接近相等 max_side = max(sides) min_side = min(sides) if max_side == 0: return False side_ratio = min_side / max_side # 检查对角线长度是否相等 diag1 = np.linalg.norm(quad_points[0][0] - quad_points[2][0]) diag2 = np.linalg.norm(quad_points[1][0] - quad_points[3][0]) if max(diag1, diag2) == 0: return False diag_ratio = min(diag1, diag2) / max(diag1, diag2) # 检查角度是否接近90度 angles_ok = self.check_right_angles(quad_points) return side_ratio > 0.9 and diag_ratio > 0.9 and angles_ok def check_right_angles(self, quad_points): """检查四边形的角度是否接近90度""" points = quad_points.reshape(4, 2) for i in range(4): p1 = points[i] p2 = points[(i + 1) % 4] p3 = points[(i + 2) % 4] # 计算向量 v1 = p1 - p2 v2 = p3 - p2 # 计算角度 dot_product = np.dot(v1, v2) norms = np.linalg.norm(v1) * np.linalg.norm(v2) if norms == 0: continue cos_angle = dot_product / norms cos_angle = np.clip(cos_angle, -1, 1) angle = np.arccos(cos_angle) angle_degrees = np.degrees(angle) # 检查是否接近90度(允许15度误差) if not (75 <= angle_degrees <= 105): return False return True def format_shape_info(self, shape_type, size_mm): """格式化几何图形信息""" if shape_type == "circle": return f"Circle: D={size_mm:.1f}mm" elif shape_type == "equilateral_triangle": return f"Eq.Triangle: Side={size_mm:.1f}mm" elif shape_type == "square": return f"Square: Side={size_mm:.1f}mm" return f"Shape: {size_mm:.1f}mm" def main(): geoDetector = A4GeometryDetector() overlayDetector = OverlayModelDetector(geoDetector) normalDetector = NormalModelDetector(geoDetector) while True: cv2.waitKey(1) geoDetector.run() results = overlayDetector.detect() # (id_counter, edge_length, square_center) # shape_type, size_mm, shape_contour = normalDetector.detect() cv2.imshow("Raw", geoDetector.frame) if geoDetector.edges is not None: cv2.imshow("Edges", geoDetector.edges) if geoDetector.finalWarped is not None: cv2.imshow("Warped", geoDetector.finalWarped) if __name__ == "__main__": main()
最新发布
08-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值