识别微信小程序太阳码的图形特征,不依赖文本解码。
(cv2, image)
| 68 | |
| 69 | |
| 70 | def detect_sun_code_shape(cv2, image) -> tuple[bool, dict]: |
| 71 | """识别微信小程序太阳码的图形特征,不依赖文本解码。""" |
| 72 | |
| 73 | height, width = image.shape[:2] |
| 74 | max_side = max(width, height) |
| 75 | scale = 560 / max_side if max_side > 560 else 1 |
| 76 | if scale != 1: |
| 77 | image = cv2.resize(image, (round(width * scale), round(height * scale)), interpolation=cv2.INTER_AREA) |
| 78 | |
| 79 | gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
| 80 | blurred = cv2.GaussianBlur(gray, (3, 3), 0) |
| 81 | _, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) |
| 82 | |
| 83 | contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| 84 | image_area = binary.shape[0] * binary.shape[1] |
| 85 | center_x = binary.shape[1] / 2 |
| 86 | center_y = binary.shape[0] / 2 |
| 87 | small_components = 0 |
| 88 | radial_components = 0 |
| 89 | finder_rings = 0 |
| 90 | dark_area = int(cv2.countNonZero(binary)) |
| 91 | |
| 92 | for contour in contours: |
| 93 | area = cv2.contourArea(contour) |
| 94 | if area < image_area * 0.00005 or area > image_area * 0.16: |
| 95 | continue |
| 96 | |
| 97 | x, y, w, h = cv2.boundingRect(contour) |
| 98 | ratio = w / max(h, 1) |
| 99 | fill = area / max(w * h, 1) |
| 100 | component_center_x = x + w / 2 |
| 101 | component_center_y = y + h / 2 |
| 102 | distance_to_center = ((component_center_x - center_x) ** 2 + (component_center_y - center_y) ** 2) ** 0.5 |
| 103 | radius_ratio = distance_to_center / max(min(binary.shape[:2]) / 2, 1) |
| 104 | |
| 105 | if 0.45 <= ratio <= 2.25 and 0.08 <= fill <= 0.82: |
| 106 | small_components += 1 |
| 107 | |
| 108 | if radius_ratio >= 0.2 and 0.18 <= fill <= 0.72 and (ratio >= 1.65 or ratio <= 0.62): |
| 109 | radial_components += 1 |
| 110 | |
| 111 | perimeter = cv2.arcLength(contour, True) |
| 112 | if perimeter <= 0: |
| 113 | continue |
| 114 | circularity = 4 * 3.141592653589793 * area / (perimeter * perimeter) |
| 115 | if 0.72 <= ratio <= 1.28 and 0.24 <= fill <= 0.64 and circularity >= 0.42 and area >= image_area * 0.002: |
| 116 | finder_rings += 1 |
| 117 | |
| 118 | density = dark_area / image_area |
| 119 | aspect_ratio = width / height |
| 120 | aspect_score = clamp(1 - abs(aspect_ratio - 1) / 0.38, 0, 1) |
| 121 | density_score = 1 if 0.035 <= density <= 0.42 else 0 |
| 122 | component_score = clamp(small_components / 80, 0, 1) |
| 123 | radial_score = clamp(radial_components / 28, 0, 1) |
| 124 | finder_score = clamp(finder_rings / 3, 0, 1) |
| 125 | score = aspect_score * 24 + density_score * 12 + component_score * 22 + radial_score * 24 + finder_score * 18 |
| 126 | |
| 127 | details = { |