代码1:
import cv2
import numpy as np
import matplotlib.pyplot as plt
import json
import os
from matplotlib import font_manager
import warnings
# ========================
# 设置中文字体
# ========================
def setup_chinese_font():
chinese_fonts = [
'SimHei', 'Microsoft YaHei', 'FangSong', 'KaiTi',
'Arial Unicode MS', 'PingFang SC', 'WenQuanYi Micro Hei'
]
available_fonts = set(f.name for f in font_manager.fontManager.ttflist)
matched_font = None
for font in chinese_fonts:
if font in available_fonts:
matched_font = font
break
if matched_font:
plt.rcParams['font.sans-serif'] = [matched_font]
print(f"✅ 使用中文字体: {matched_font}")
else:
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Arial']
print("⚠️ 未检测到中文字体,使用英文替代")
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.dpi'] = 150
setup_chinese_font()
# ========================
# 安全转换 NumPy 类型为 Python 原生类型(用于 JSON)
# ========================
def convert_types(obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, (np.float32, np.float64)):
return float(obj)
elif isinstance(obj, (np.int32, np.int64)):
return int(obj)
elif isinstance(obj, dict):
return {k: convert_types(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [convert_types(i) for i in obj]
else:
return obj
# ========================
# 图像分析函数(单次对比)
# ========================
def analyze_pair(ref_img_path, test_img_path, output_match_image=None):
"""
分析测试图像相对于参考图像的差异
返回包含畸变率、像素误差等指标的结果字典
"""
ref_img = cv2.imread(ref_img_path)
test_img = cv2.imread(test_img_path)
if ref_img is None or test_img is None:
raise ValueError(f"无法加载图像: {ref_img_path} 或 {test_img_path}")
gray_ref = cv2.cvtColor(ref_img, cv2.COLOR_BGR2GRAY)
gray_test = cv2.cvtColor(test_img, cv2.COLOR_BGR2GRAY)
h, w = gray_ref.shape
diagonal = np.sqrt(w ** 2 + h ** 2)
def calculate_distortion(gray1, gray2):
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)
if len(kp1) < 8 or len(kp2) < 8:
return None, None, [], [], [], None # 匹配失败
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
good_matches = [m for m, n in matches if m.distance < 0.75 * n.distance]
if len(good_matches) < 8:
return None, None, [], [], [], None
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
reprojected = cv2.perspectiveTransform(src_pts, H)
errors = np.linalg.norm(dst_pts - reprojected, axis=2)
mean_error_px = np.mean(errors)
distortion_rate_percent = (mean_error_px / diagonal) * 100
return distortion_rate_percent, mean_error_px, good_matches, kp1, kp2, mask
def calculate_contrast(gray):
return np.std(gray)
def calculate_sharpness(gray):
return cv2.Laplacian(gray, cv2.CV_64F).var()
# 执行计算
dr_percent, mean_px_err, matches, kp_ref, kp_test, mask = calculate_distortion(gray_ref, gray_test)
if dr_percent is None:
print(f"⚠️ 特征匹配不足,跳过: {os.path.basename(test_img_path)}")
return None
contrast_ref = calculate_contrast(gray_ref)
contrast_test = calculate_contrast(gray_test)
sharpness_ref = calculate_sharpness(gray_ref)
sharpness_test = calculate_sharpness(gray_test)
# 百分制评分
MAX_DR = 5.0
MAX_CONTRAST_DIFF = 50.0
MAX_SHARPNESS_DIFF = 1000.0
def score(v, max_v, inv=True):
s = min(float(v) / max_v, 1.0)
return round((1 - s) * 100, 2) if inv else round(s * 100, 2)
scores = {
"distortion": score(dr_percent, MAX_DR),
"contrast": score(abs(contrast_ref - contrast_test), MAX_CONTRAST_DIFF),
"sharpness": score(abs(sharpness_ref - sharpness_test), MAX_SHARPNESS_DIFF),
}
weights = {"distortion": 0.4, "contrast": 0.3, "sharpness": 0.3}
overall = round(sum(scores[k] * weights[k] for k in scores), 2)
# 可视化并保存匹配图
if output_match_image:
title = f"与基准图对比\n畸变率: {dr_percent:.3f}% | 平均误差: {mean_px_err:.3f}px"
draw_params = dict(matchColor=(0, 255, 0), matchesMask=mask.ravel().tolist(), flags=2)
matched_img = cv2.drawMatches(ref_img, kp_ref, test_img, kp_test, matches, None, **draw_params)
matched_img_rgb = cv2.cvtColor(matched_img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(16, 9))
plt.imshow(matched_img_rgb)
plt.title(title, fontsize=16, weight='bold')
plt.axis('off')
plt.savefig(output_match_image, bbox_inches='tight', pad_inches=0.1)
plt.close()
print(f"✅ 匹配图已保存: {output_match_image}")
# 构建结果(注意:数值仍是 np 类型,后续会转换)
result = {
"reference_image": os.path.basename(ref_img_path),
"test_image": os.path.basename(test_img_path),
"distortion_rate_percent": dr_percent,
"mean_reprojection_error_pixels": mean_px_err,
"image_diagonal_pixels": diagonal,
"metrics": {
"reference": {
"contrast": contrast_ref,
"sharpness": sharpness_ref
},
"test": {
"contrast": contrast_test,
"sharpness": sharpness_test
}
},
"scores": {
"distortion": scores["distortion"],
"contrast": scores["contrast"],
"sharpness": scores["sharpness"],
"overall": overall
}
}
return result
# ========================
# 批量处理所有图像(第一张为基准)
# ========================
def batch_compare_images(image_paths, output_dir="comparison_results"):
"""
批量对比:以第一张图为基准,与其他图一一比较
"""
if len(image_paths) < 2:
raise ValueError("至少需要两张图像进行对比")
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
ref_img_path = image_paths[0]
results = []
print(f"\n🎯 开始批量分析,基准图像: {os.path.basename(ref_img_path)}")
print(f"共需对比 {len(image_paths) - 1} 张图像...\n")
for i, test_img_path in enumerate(image_paths[1:], start=1):
test_basename = os.path.basename(test_img_path)
print(f"🔍 正在对比第 {i}/{len(image_paths)-1}: {test_basename}")
# 修复文件扩展名重复问题(原代码可能生成 .jpg.png)
name_part = os.path.splitext(test_basename)[0]
match_img = os.path.join(output_dir, f"match_{i:02d}_{name_part}.png")
json_file = os.path.join(output_dir, f"result_{i:02d}_{name_part}.json")
result = analyze_pair(ref_img_path, test_img_path, output_match_image=match_img)
if result:
# ✅ 关键:先转换数据类型再保存
safe_result = convert_types(result)
results.append(safe_result)
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(safe_result, f, ensure_ascii=False, indent=4)
print(f" 保存结果: {json_file}")
else:
print(f" ❌ 对比失败,跳过")
# 保存汇总报告
summary = {
"summary": {
"reference_image": os.path.basename(ref_img_path),
"total_compared": len(results),
"failed_count": (len(image_paths) - 1) - len(results)
},
"details": results
}
summary_file = os.path.join(output_dir, "summary_report.json")
safe_summary = convert_types(summary)
with open(summary_file, 'w', encoding='utf-8') as f:
json.dump(safe_summary, f, ensure_ascii=False, indent=4)
print(f"\n📈 汇总报告已保存: {summary_file}")
return summary
# ========================
# 打印汇总结果
# ========================
def print_summary_report(summary):
print("\n" + "=" * 60)
print("📊 批量对比汇总报告")
print("=" * 60)
print(f"基准图像: {summary['summary']['reference_image']}")
print(f"成功对比: {summary['summary']['total_compared']} 张")
print(f"失败数量: {summary['summary']['failed_count']} 张\n")
for idx, r in enumerate(summary['details']):
print(f"--- [{idx+1}] {r['test_image']} ---")
print(f" 几何畸变: {r['scores']['distortion']}% → 畸变率: {r['distortion_rate_percent']:.3f}%")
print(f" 对比度分: {r['scores']['contrast']}% (Ref: {r['metrics']['reference']['contrast']:.3f}, "
f"Test: {r['metrics']['test']['contrast']:.3f})")
print(f" 锐度得分: {r['scores']['sharpness']}%")
print(f" 综合评分: {r['scores']['overall']}%\n")
# ========================
# 主程序入口
# ========================
if __name__ == "__main__":
# 📌 修改为你自己的图像路径列表(第一张为基准图)
image_files = [
"ref.jpg", # 基准图
"test1.jpg",
"test2.jpg",
"test3.jpg",
]
# 检查文件是否存在
missing = [f for f in image_files if not os.path.exists(f)]
if missing:
print("❌ 缺失文件:", missing)
else:
try:
summary_result = batch_compare_images(image_files, output_dir="comparison_results")
print_summary_report(summary_result)
except Exception as e:
print("❌ 错误:", str(e))
代码2:
import cv2
import numpy as np
import matplotlib.pyplot as plt
import json
import os
import glob
import pandas as pd # 用于导出 Excel
from matplotlib import font_manager
# ========================
# 设置常规中文字体(优先选择 SimHei、微软雅黑)
# ========================
def setup_chinese_font():
# 常见中文字体列表
chinese_fonts = [
'SimHei', # 黑体(最常用)
'Microsoft YaHei', # 微软雅黑
'FangSong', # 仿宋
'KaiTi', # 楷体
'Arial Unicode MS',
'PingFang SC',
'WenQuanYi Micro Hei'
]
available_fonts = set(f.name for f in font_manager.fontManager.ttflist)
matched_font = None
for font in chinese_fonts:
if font in available_fonts:
matched_font = font
break
if matched_font:
plt.rcParams['font.sans-serif'] = [matched_font]
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
print(f"✅ 成功加载中文字体: {matched_font}")
else:
plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
print("⚠️ 未找到中文字体,使用英文替代")
setup_chinese_font()
# ========================
# 安全转换 NumPy 类型为原生 Python 类型
# ========================
def convert_types(obj):
if isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, (np.float32, np.float64)):
return float(obj)
elif isinstance(obj, (np.int32, np.int64)):
return int(obj)
elif isinstance(obj, dict):
return {k: convert_types(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [convert_types(i) for i in obj]
else:
return obj
# ========================
# 发现所有有效图像文件
# ========================
def discover_images(directory=".", recursive=False):
patterns = ['*.jpg', '*.jpeg', '*.png', '*.bmp', '*.tiff', '*.tif']
found_files = []
abs_dir = os.path.abspath(directory)
print(f"\n🔍 扫描目录: {abs_dir}")
for pattern in patterns:
search_path = os.path.join(directory, pattern)
matches = glob.glob(search_path, recursive=recursive)
found_files.extend(matches)
unique_files = sorted(set(os.path.normpath(f) for f in found_files))
valid_images = []
print(f"📄 候选文件数: {len(unique_files)}")
for f in unique_files:
if not os.path.isfile(f):
continue
try:
img = cv2.imread(f)
if img is not None and img.size > 0:
valid_images.append(f)
print(f" ✅ 加载成功: {os.path.basename(f)} | 尺寸: {img.shape[1]}x{img.shape[0]}")
else:
print(f" ❌ 跳过损坏图像: {f}")
except Exception as e:
print(f" ❌ 读取异常: {f} -> {str(e)}")
print(f"\n🎉 共发现有效图像: {len(valid_images)} 张")
for i, f in enumerate(valid_images):
print(f" [{i+1:02d}] {os.path.basename(f)}")
return valid_images
# ========================
# 单次图像对比分析函数
# ========================
def analyze_pair(ref_img_path, test_img_path, output_match_image=None):
ref_img = cv2.imread(ref_img_path)
test_img = cv2.imread(test_img_path)
if ref_img is None or test_img is None:
print(f"❌ 无法加载图像 -> Ref: {ref_img_path}, Test: {test_img_path}")
return None
gray_ref = cv2.cvtColor(ref_img, cv2.COLOR_BGR2GRAY)
gray_test = cv2.cvtColor(test_img, cv2.COLOR_BGR2GRAY)
h, w = gray_ref.shape
diagonal = float(np.sqrt(w ** 2 + h ** 2))
# --- 1. 计算几何畸变率 ---
def calculate_distortion():
sift = cv2.SIFT_create(nfeatures=200)
kp1, des1 = sift.detectAndCompute(gray_ref, None)
kp2, des2 = sift.detectAndCompute(gray_test, None)
if len(kp1) < 8 or len(kp2) < 8:
return None, None, [], [], [], None
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
good_matches = [m for m, n in matches if m.distance < 0.75 * n.distance]
if len(good_matches) < 8:
return None, None, [], [], [], None
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
reprojected = cv2.perspectiveTransform(src_pts, H)
errors = np.linalg.norm(dst_pts - reprojected, axis=2)
mean_error_px = np.mean(errors)
distortion_rate_percent = (mean_error_px / diagonal) * 100
return distortion_rate_percent, mean_error_px, good_matches, kp1, kp2, mask
dr_percent, mean_px_err, matches, kp_ref, kp_test, mask = calculate_distortion()
if dr_percent is None:
print(f" ⚠️ 特征匹配不足,跳过: {os.path.basename(test_img_path)}")
return None
# --- 2. 对比度与锐度 ---
def calculate_contrast(gray):
return np.std(gray)
def calculate_sharpness(gray):
return cv2.Laplacian(gray, cv2.CV_64F).var()
contrast_ref = calculate_contrast(gray_ref)
contrast_test = calculate_contrast(gray_test)
sharpness_ref = calculate_sharpness(gray_ref)
sharpness_test = calculate_sharpness(gray_test)
# --- 3. 百分制评分 ---
MAX_DR = 5.0
MAX_CONTRAST_DIFF = 50.0
MAX_SHARPNESS_DIFF = 1000.0
def score(value, max_val, inverse=True):
normalized = min(float(abs(value)) / max_val, 1.0)
return round((1 - normalized) * 100, 2) if inverse else round(normalized * 100, 2)
scores = {
"distortion": score(dr_percent, MAX_DR),
"contrast": score(contrast_ref - contrast_test, MAX_CONTRAST_DIFF),
"sharpness": score(sharpness_ref - sharpness_test, MAX_SHARPNESS_DIFF),
}
weights = {"distortion": 0.4, "contrast": 0.3, "sharpness": 0.3}
overall_score = round(sum(scores[k] * weights[k] for k in scores), 2)
# --- 4. 可视化特征点匹配 ---
if output_match_image:
title = f"特征点匹配可视化\n畸变率: {dr_percent:.3f}% | 重投影误差: {mean_px_err:.3f}px"
draw_params = dict(matchColor=(0, 255, 0), singlePointColor=None,
matchesMask=mask.ravel().tolist(), flags=2)
matched_img = cv2.drawMatches(ref_img, kp_ref, test_img, kp_test, matches, None, **draw_params)
matched_rgb = cv2.cvtColor(matched_img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(16, 9))
plt.imshow(matched_rgb)
plt.title(title, fontsize=16, fontweight='bold')
plt.axis('off')
plt.tight_layout()
plt.savefig(output_match_image, dpi=120, bbox_inches='tight', pad_inches=0.1)
plt.close()
print(f" 🖼️ 匹配图已保存: {output_match_image}")
# --- 5. 构建结果 ---
result = {
"参考图像": os.path.basename(ref_img_path),
"测试图像": os.path.basename(test_img_path),
"成像畸变率_百分比": round(dr_percent, 4),
"平均重投影误差_像素": round(mean_px_err, 4),
"对角线长度_像素": round(diagonal, 2),
"参考图对比度_std": round(contrast_ref, 4),
"测试图对比度_std": round(contrast_test, 4),
"对比度差值": round(abs(contrast_ref - contrast_test), 4),
"参考图锐度_LaplacianVar": round(sharpness_ref, 4),
"测试图锐度_LaplacianVar": round(sharpness_test, 4),
"锐度差值": round(abs(sharpness_ref - sharpness_test), 4),
"畸变评分": scores["distortion"],
"对比度评分": scores["contrast"],
"锐度评分": scores["sharpness"],
"综合得分": overall_score
}
return result
# ========================
# 批量处理主函数 + Excel 导出
# ========================
def batch_compare_images(image_paths, output_dir="comparison_results"):
if len(image_paths) < 2:
raise ValueError("至少需要两张图像进行对比")
if len(image_paths) > 100:
print(f"⚠️ 图像数量超过100,仅处理前100张")
image_paths = image_paths[:100]
os.makedirs(output_dir, exist_ok=True)
ref_img_path = image_paths[0]
results = []
print(f"\n🎯 开始分析,基准图像: {os.path.basename(ref_img_path)}")
print(f"共需对比 {len(image_paths) - 1} 张图像...\n")
for i, test_img_path in enumerate(image_paths[1:], start=1):
test_name = os.path.basename(test_img_path)
name_no_ext = os.path.splitext(test_name)[0]
match_img = os.path.join(output_dir, f"match_{i:02d}_{name_no_ext}.png")
json_file = os.path.join(output_dir, f"result_{i:02d}_{name_no_ext}.json")
print(f"🔍 [{i:02d}/{len(image_paths)-1:02d}] 正在对比: {test_name}")
result = analyze_pair(ref_img_path, test_img_path, output_match_image=match_img)
if result:
safe_result = convert_types(result)
results.append(safe_result)
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(safe_result, f, ensure_ascii=False, indent=4)
print(f" ✅ JSON 结果已保存: {json_file}")
else:
print(f" ❌ 分析失败,跳过该图像")
# === 🔽 新增:导出 Excel 汇总表 ===
excel_file = os.path.join(output_dir, "analysis_summary.xlsx")
try:
df = pd.DataFrame(results)
df.to_excel(excel_file, index=False, sheet_name="图像对比汇总")
print(f"📊 Excel 汇总表已生成: {excel_file}")
except Exception as e:
print(f"❌ Excel 导出失败: {e}")
# 保存 summary_report.json
summary = {
"summary": {
"reference_image": os.path.basename(ref_img_path),
"total_compared": len(results),
"failed_count": (len(image_paths) - 1) - len(results),
"output_directory": os.path.abspath(output_dir)
},
"details": results
}
summary_file = os.path.join(output_dir, "summary_report.json")
with open(summary_file, 'w', encoding='utf-8') as f:
json.dump(convert_types(summary), f, ensure_ascii=False, indent=4)
print(f"📈 JSON 汇总报告已生成: {summary_file}")
return summary
# ========================
# 打印简洁摘要
# ========================
def print_summary_report(summary):
s = summary['summary']
print("\n" + "=" * 80)
print("📊 图像质量与一致性分析报告")
print("=" * 80)
print(f"基准图像 : {s['reference_image']}")
print(f"对比图像总数 : {s['total_compared']} 张")
print(f"失败数量 : {s['failed_count']} 张")
print(f"输出目录 : {s['output_directory']}")
print("-" * 80)
for idx, r in enumerate(summary['details']):
print(f"[{idx+1:02d}] {r['测试图像']}")
print(f" 畸变率: {r['成像畸变率_百分比']:>6.3f}% "
f"| 误差: {r['平均重投影误差_像素']:>6.3f}px")
print(f" 对比度: Ref={r['参考图对比度_std']:>6.2f} → "
f"Test={r['测试图对比度_std']:>6.2f} | 评分: {r['对比度评分']}/100")
print(f" 锐度: Ref={r['参考图锐度_LaplacianVar']:>6.2f} → "
f"Test={r['测试图锐度_LaplacianVar']:>6.2f} | 评分: {r['锐度评分']}/100")
print(f" 综合评分: {r['综合得分']}/100")
print("=" * 80)
# ========================
# 主程序入口
# ========================
if __name__ == "__main__":
# 方法一:手动指定图像列表
image_files = [
# "ref.jpg",
# "test1.jpg"
]
# 方法二:自动扫描当前目录下所有图像
if not image_files:
IMAGE_DIR = "." # 可改为 "./images"
image_files = discover_images(directory=IMAGE_DIR, recursive=False)
if len(image_files) < 2:
print("❌ 至少需要两个有效的图像文件!")
else:
try:
summary = batch_compare_images(image_files, output_dir="comparison_results")
print_summary_report(summary)
except Exception as e:
print(f"💥 程序异常: {e}")
为何两个代码,输出的畸变率数值不同,请检查产生的原因
最新发布