|
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
|
|
|
|
|
|
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_image = Image.open(file_path)
|
|
|
|
|
|
if pil_image.mode != 'RGB':
|
|
pil_image = pil_image.convert('RGB')
|
|
|
|
|
|
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],
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|