import cv2 import numpy as np from pyzbar.pyzbar import decode from PIL import Image, ImageDraw import requests from io import BytesIO import os import sys # 设置默认编码为UTF-8 if sys.platform.startswith('win'): import locale locale.setlocale(locale.LC_ALL, 'zh_CN.UTF-8') def detect_and_extract_circular_qr(image): """ 检测并提取圆形二维码区域 参数: image: numpy数组格式的图像 返回: 处理后的图像列表 """ processed_images = [] # 转换为灰度图 if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image.copy() # 使用霍夫圆检测 circles = cv2.HoughCircles( gray, cv2.HOUGH_GRADIENT, dp=1, minDist=50, param1=50, param2=30, minRadius=50, maxRadius=500 ) if circles is not None: circles = np.uint16(np.around(circles)) for circle in circles[0, :]: x, y, r = circle # 创建掩码 mask = np.zeros(gray.shape, np.uint8) cv2.circle(mask, (x, y), r, 255, -1) # 提取圆形区域 circular_region = cv2.bitwise_and(image, image, mask=mask) # 裁剪到包含圆形的最小矩形 x_min = max(0, x - r) x_max = min(image.shape[1], x + r) y_min = max(0, y - r) y_max = min(image.shape[0], y + r) cropped = circular_region[y_min:y_max, x_min:x_max] processed_images.append(cropped) # 同时添加将圆形区域转换为方形的版本 square_size = 2 * r square_region = np.zeros((square_size, square_size, 3), dtype=np.uint8) square_region[:] = 255 # 白色背景 # 将圆形内容复制到方形图像中心 if cropped.shape[0] > 0 and cropped.shape[1] > 0: y_offset = (square_size - cropped.shape[0]) // 2 x_offset = (square_size - cropped.shape[1]) // 2 if y_offset >= 0 and x_offset >= 0: square_region[y_offset:y_offset+cropped.shape[0], x_offset:x_offset+cropped.shape[1]] = cropped processed_images.append(square_region) return processed_images def parse_qr_code_advanced(file_path): """ 高级二维码解析函数,支持各种形状的二维码 参数: file_path: 二维码图片的本地路径 返回: 解析出的链接或文本 """ try: # 检查文件是否存在 if not os.path.exists(file_path): print(f"错误:文件不存在 - {file_path}") return None # 使用PIL读取图片 pil_image = Image.open(file_path) # 转换为RGB格式 if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') # 转换为numpy数组 image_array = np.array(pil_image) # 准备多个版本的图像进行尝试 images_to_try = [image_array] # 检测并提取圆形二维码 circular_extracts = detect_and_extract_circular_qr(image_array) images_to_try.extend(circular_extracts) # 尝试各种预处理方法 for idx, img in enumerate(images_to_try): print(f"尝试方法 {idx + 1}/{len(images_to_try)}...") # 直接尝试解码 decoded_objects = decode(img) if decoded_objects: print(f"✓ 方法 {idx + 1} 成功解析!") return decoded_objects[0].data.decode('utf-8') # 转换为灰度图并尝试多种预处理 if len(img.shape) == 3: gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) else: gray = img # 预处理方法列表 preprocessing_methods = [ # 简单二值化 lambda g: cv2.threshold(g, 127, 255, cv2.THRESH_BINARY)[1], # OTSU阈值 lambda g: cv2.threshold(g, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1], # 自适应阈值 lambda g: cv2.adaptiveThreshold(g, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2), # 反转颜色 lambda g: cv2.bitwise_not(g), # 增强对比度 lambda g: cv2.convertScaleAbs(g, alpha=2.0, beta=0), # 形态学操作 - 闭运算 lambda g: cv2.morphologyEx( cv2.threshold(g, 127, 255, cv2.THRESH_BINARY)[1], cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) ), # 高斯模糊后二值化 lambda g: cv2.threshold( cv2.GaussianBlur(g, (5, 5), 0), 127, 255, cv2.THRESH_BINARY )[1] ] for j, preprocess in enumerate(preprocessing_methods): try: processed = preprocess(gray) decoded_objects = decode(processed) if decoded_objects: print(f"✓ 方法 {idx + 1} 的预处理 {j + 1} 成功解析!") return decoded_objects[0].data.decode('utf-8') except: continue # 如果所有方法都失败,尝试更激进的方法 print("尝试更多高级处理方法...") # 尝试旋转图像 for angle in [90, 180, 270]: rotated = cv2.rotate(image_array, angle // 90 - 1) decoded_objects = decode(rotated) if decoded_objects: print(f"✓ 旋转 {angle}° 后成功解析!") return decoded_objects[0].data.decode('utf-8') # 尝试缩放图像 for scale in [0.5, 0.75, 1.5, 2.0]: new_size = (int(image_array.shape[1] * scale), int(image_array.shape[0] * scale)) resized = cv2.resize(image_array, new_size, interpolation=cv2.INTER_CUBIC) decoded_objects = decode(resized) if decoded_objects: print(f"✓ 缩放 {scale}x 后成功解析!") return decoded_objects[0].data.decode('utf-8') print("✗ 所有方法都无法解析此二维码") return None except Exception as e: print(f"解析二维码时出错: {e}") import traceback traceback.print_exc() return None def batch_process_directory(directory_path=None): """ 批量处理目录下的所有图片文件 参数: directory_path: 目录路径,默认为当前目录 """ if directory_path is None: directory_path = os.getcwd() image_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp'] print(f"扫描目录: {directory_path}") print("=" * 60) found_images = [] for file in os.listdir(directory_path): if any(file.lower().endswith(ext) for ext in image_extensions): found_images.append(file) if not found_images: print("未找到图片文件") return print(f"找到 {len(found_images)} 个图片文件\n") success_count = 0 for i, img_file in enumerate(found_images, 1): print(f"\n[{i}/{len(found_images)}] 正在处理: {img_file}") print("-" * 40) full_path = os.path.join(directory_path, img_file) result = parse_qr_code_advanced(full_path) if result: success_count += 1 print(f"\n✅ 成功解析!") print(f"内容: {result}") # 如果是微信支付链接,特别标注 if result.startswith('wxp://'): print("类型: 微信支付二维码") elif 'weixin://' in result or 'wx.tenpay.com' in result: print("类型: 微信相关链接") else: print(f"\n❌ 无法解析此文件") print("=" * 60) print(f"\n\n总结: 成功解析 {success_count}/{len(found_images)} 个文件") # 使用示例 if __name__ == "__main__": print("=== 增强版二维码解析工具 ===") print("支持圆形、方形等各种形状的二维码\n") # 批量处理当前目录 batch_process_directory() # 或者处理单个文件 # result = parse_qr_code_advanced("your_circular_qr_code.jpg") # if result: # print(f"解析结果: {result}")