python:np.diff() 计算差值和升跌幅百分比

本文介绍了一种使用Python计算基金净值差值及升跌幅百分比的方法,适用于哈奇计划的数据分析需求。通过读取CSV格式的基金数据,利用Pandas库进行数据处理,计算每日净值变化及变化率,并将结果保存为TXT文件。

计算基金净值的差值和升跌幅百分比,用于哈奇计划。

基金净值文件数据格式: 

date,jz,ljjz
2016-01-04,1.1141,1.1141
2016-01-05,1.1161,1.1161
2016-01-06,1.1350,1.1350

csv2txt.py

# coding=utf-8
import os, sys
import datetime
import numpy as np
import pandas as pd

if len(sys.argv) ==2:
    fcode = sys.argv[1]
else:
    print('usage: csv2txt.py fundcode ')
    sys.exit(1)

f1 = fcode +'.csv'
if not os.path.exists(f1):
    print("Error: %s not found." % f1)
    sys.exit(1)

fn,ext = os.path.splitext(f1)
if len(fn) !=6:
    print('Error: len(%s) !=6' % fn)
    sys.exit(1)
if ext !='.csv':
    print('Error: %s is not .csv' % f1)
    sys.exit(1)

df = pd.read_csv(f1, index_col='date')
if len(df) <5:
    print(" len(df) <5 ")
    sys.exit(2)

# 基金净值
net_values = np.array(df['jz'].values)
# Take diff of net values and computing rate of change
diff = np.diff(net_values)
diff_percentage = 100.0 * np.diff(net_values) / net_values[:-1]
#
我正在编辑【python】代码, ,请帮我检查并改正错误点。我的原始代码如下: 【import numpy as np from scipy.signal import savgol_filter, find_peaks from scipy.optimize import least_squares # 定义可能会用到的物理常数 PHYS_CONST = { "e": 1.60217662e-19, # 电子电荷 (C) - 基本电荷单位 "eps0": 8.854187817e-12, # 真空介电常数 (F/m) - 真空中的电容量度 "m0": 9.10938356e-31, # 电子静止质量 (kg) - 电子的质量 "c": 2.99792458e8, # 光速 (m/s) - 真空中的光速 } # 碳化硅外延层厚度计算模型 # 基于红外干涉法物理光学模型计算4H-SiC外延层的厚度 class SiCThicknessAnalyzer: # 初始化函数,设定默认参数 # n_carrier: 载流子浓度,单位cm⁻³,默认5×10¹⁸ # R2: SiC-SiC界面反射率,默认0.20338(根据文献资料) def __init__(self, n_carrier=5e18, R2=0.20338): self.n_carrier = n_carrier # 载流子浓度 (cm^-3)影响Drude模型修正 self.m_eff = 0.4 * PHYS_CONST["m0"] # 4H-SiC电子有效质量 (kg) self.R2 = R2 # 常数近似法中的R2值(SiC-SiC界面反射率)- 衬底反射率 # Sellmeier方程计算4H-SiC折射率 # lambda_um: 波长,单位微米(μm) # 返回: 对应波长的折射率 def sellmeier(self, lambda_um): # Sellmeier系数(均为特定条件下的常数) # 这些系数通过实验数据拟合得到,适用于4H-SiC材料 A = 1.0 B1 = 0.20075 B2 = 5.54861 B3 = 35.650066 C1 = 12.07224 C2 = 35.65066 C3 = 1268.24708 # Sellmeier方程计算折射率平方 lambda_sq = lambda_um ** 2 n_sq = A + B1 * lambda_sq / (lambda_sq + C1) + \ B2 * lambda_sq / (lambda_sq - C2) + \ B3 * lambda_sq / (lambda_sq - C3) # 物理限制:4H-SiC折射率范围(2.3~2.8) # 确保计算结果在物理合理范围内,避免数值误差导致的异常值 n_sq = np.clip(n_sq, 2.3 ** 2, 2.8 ** 2) return np.sqrt(n_sq) # 返回折射率 # Drude模型修正折射率 # 考虑载流子浓度对折射率的影响(自由载流子吸收效应) # lambda_um: 波长数组,单位微米 # n_int: 本征折射率数组(Sellmeier方程计算结果) # n_carrier: 载流子浓度数组 # 返回: 修正后的折射率及相关参数 def drude(self, lambda_um, n_int, n_carrier): # 将输入转换为数组,确保处理标量数组的一致性 lambda_um = np.asarray(lambda_um) n_int = np.asarray(n_int) n_carrier = np.asarray(n_carrier) # 初始化输出数组 n_real = n_int.copy() # 修正后的折射率实部 delta_n = np.zeros_like(n_int) # 折射率变化量 relative_change = np.zeros_like(n_int) # 相对变化百分比 omega_p = np.zeros_like(n_int) # 等离子体频率 gamma = np.zeros_like(n_int) # 散射率 # 找出需要Drude修正的点 # 只有载流子浓度足够高时才需要修正(n_carrier >= 1e15) mask = n_carrier >= 1e15 if np.any(mask): # 单位转换 cm⁻³ → m⁻³ # 1 cm⁻³ = 10⁶ m⁻³(因为1 m³ = 10⁶ cm³) n_carrier_m = n_carrier[mask] * 1e6 # 迁移率与载流子浓度关系 # 迁移率随载流子浓度增加而减小 mobility = np.zeros_like(n_carrier_m) for i, nc in enumerate(n_carrier_m): mobility[i] = self._get_mobility(nc / 1e6) # 转换回cm⁻³单位输入 # 等离子体频率计算 omega_p_mask = np.sqrt(n_carrier_m * PHYS_CONST["e"] ** 2 / (self.m_eff * PHYS_CONST["eps0"])) # 散射率计算 mobility_si = mobility * 1e-4 # 单位转换,cm²/V·s → m²/V·s gamma_mask = PHYS_CONST["e"] / (self.m_eff * mobility_si) # 光波角频率计算 lambda_m = lambda_um[mask] * 1e-6 # μm → m omega = 2 * np.pi * PHYS_CONST["c"] / lambda_m # Drude模型修正介电函数 epsilon_complex = n_int[mask] ** 2 - (omega_p_mask ** 2) / (omega ** 2 + 1j * omega * gamma_mask) # 计算复折射率 n_complex = np.sqrt(epsilon_complex) n_real_mask = np.real(n_complex) # 取实部(折射率) # 物理合理性检查:如果修正后的折射率大于本征折射率,则进行限制 # 选择将修正后的折射率限制在本征折射率的90%以上 # Drude修正通常使折射率减小,但数值计算可能产生异常 n_real_mask = np.maximum(n_int[mask] * 0.9, n_real_mask) # 将修正后的值赋给输出数组 n_real[mask] = n_real_mask delta_n[mask] = n_real_mask - n_int[mask] relative_change[mask] = (delta_n[mask] / n_int[mask]) * 100 omega_p[mask] = omega_p_mask gamma[mask] = gamma_mask return n_real, delta_n, relative_change, omega_p, gamma # 根据载流子浓度获取迁移率 # 迁移率随载流子浓度增加而减小(散射增强) # n_carrier: 载流子浓度,单位cm⁻³ # 返回: 迁移率,单位cm²/V·s def _get_mobility(self, n_carrier): # 分段函数近似,基于实验数据 if n_carrier < 1e16: return 950 # 低浓度时迁移率高 elif n_carrier < 5e16: return 800 elif n_carrier < 1e17: return 650 elif n_carrier < 5e17: return 450 elif n_carrier < 1e18: return 300 elif n_carrier < 5e18: return 150 else: return 80 # 高浓度时迁移率低 # 数据预处理(筛选有效干涉条纹) # wave_number: 波数数据,单位cm⁻¹ # reflectivity: 反射率数据,单位% # 返回: 处理后的波长、反射率数据及极值点位置 def preprocess_data(self, wave_number, reflectivity): # 波数转波长 wavelength = 1e4 / wave_number # 限制波长为3~8μm(碳化硅透明窗口) # 此范围外数据可能受吸收影响,不适合干涉分析 valid_mask = (wavelength >= 3.0) & (wavelength <= 8.0) wavelength = wavelength[valid_mask] reflectivity = reflectivity[valid_mask] if len(wavelength) == 0: raise ValueError("无有效波长数据(需3~8μm范围)") # 平滑处理 - 使用Savitzky-Golay滤波器去除噪声 # 保持信号特征的同时减少随机噪声 n_points = len(reflectivity) window_length = max(11, int(n_points * 0.05)) # 动态窗口大小 if window_length % 2 == 0: window_length += 1 # 确保窗口长度为奇数 reflectivity_smooth = savgol_filter(reflectivity, window_length=window_length, polyorder=3) # 极值点提取 - 寻找干涉条纹的峰值谷值 # 这些极值点对应干涉相长相消的条件 peaks, _ = find_peaks( reflectivity_smooth, prominence=0.2, # 最小 prominence 确保是显著峰值 distance=10, # 最小 peak 间距 width=3 # 最小 peak 宽度 ) valleys, _ = find_peaks( -reflectivity_smooth, # 找反射率的谷值(即-reflectivity的峰值) prominence=0.5, # 谷值需要更大的 prominence distance=20, # 谷值间距通常较大 width=3 ) return wavelength, reflectivity, reflectivity_smooth, peaks, valleys # 使用干涉模型计算厚度 # wavelength: 波长数组 # reflectivity: 反射率数组 # extrema_indices: 极值点索引数组 # theta_deg: 入射角度,单位度 # n_carrier: 载流子浓度 # 返回: 优化后的厚度拟合质量信息 def calculate_thickness(self, wavelength, reflectivity, extrema_indices, theta_deg, n_carrier=5e18): theta_rad = np.radians(theta_deg) # 角度转弧度 initial_thicknesses = [] # 存储初步估算的厚度 refractive_indices = [] # 存储对应的折射率 # 双光束干涉的相位差修正 phase_correction = True # 遍历所有极值点对,计算初步厚度估计 for i in range(len(extrema_indices) - 1): idx1, idx2 = extrema_indices[i], extrema_indices[i + 1] lambda1, lambda2 = wavelength[idx1], wavelength[idx2] # 过滤过近的极值点(避免噪声导致的虚假条纹) if abs(lambda2 - lambda1) < 0.1: continue # 计算平均波长处的折射率(Sellmeier+Drude) lambda_avg = (lambda1 + lambda2) / 2 n_int = self.sellmeier(lambda_avg) n, _, _, _, _ = self.drude(lambda_avg, n_int, n_carrier) refractive_indices.append(n) # 记录折射率 # 斯涅尔定律计算折射角 sin_theta_i = np.sin(theta_rad) / n if abs(sin_theta_i) >= 1.0: # 全反射检查 continue theta_i = np.arcsin(sin_theta_i) # 相位差修正 - 计算厚度 # 基于相邻极值点波长差计算厚度 if phase_correction: # 考虑相位突变的修正公式 d = (lambda1 * lambda2) / (2 * n * np.cos(theta_i) * abs(lambda2 - lambda1)) else: # 标准公式 d = 1 / (2 * n * np.cos(theta_i) * abs(1 / lambda1 - 1 / lambda2)) # 碳化硅外延层工艺厚度范围(2~20μm) # 过滤不合理的结果 if 2 < d < 20: initial_thicknesses.append(d) if not initial_thicknesses: raise ValueError("未找到有效的厚度估计") # 计算平均折射率 avg_refractive_index = np.mean(refractive_indices) if refractive_indices else 2.65 # 优化厚度反射率常数 # 使用最小二乘法进一步优化初步估计结果 optimized_params = self._optimize_params( wavelength, reflectivity, np.median(initial_thicknesses), # 使用中位数作为初始值 theta_rad, n_carrier ) d_opt, n_scale_opt = optimized_params # 计算拟合质量 fit_quality = self.calculate_fit_quality( wavelength, reflectivity, d_opt, theta_deg, n_carrier, n_scale_opt ) # 添加折射率到质量评估中 fit_quality['refractive_index'] = avg_refractive_index * n_scale_opt return d_opt, fit_quality # 优化厚度反射率常数 # 使用最小二乘法优化参数,使理论反射率最接近实验值 def _optimize_params(self, wavelength, reflectivity, initial_d, theta_rad, n_carrier): def objective(params): # 参数: [厚度d, 相位φ, 折射率缩放因子n_scale] d_opt, phi, n_scale = params residuals = [] # 残差数组 # 每个波长单独计算折射率(避免平均波长导致的色散误差) for i, wl in enumerate(wavelength): n_int = self.sellmeier(wl) # 本征折射率 n_real, *_ = self.drude(wl, n_int, n_carrier) # Drude修正 n_opt = n_real * n_scale # 应用缩放因子 # 斯涅尔定律计算折射角 sin_theta_i = np.sin(theta_rad) / n_opt if abs(sin_theta_i) >= 1.0: # 全反射检查 return np.full(len(wavelength), 1000) # 返回大残差 theta_i = np.arcsin(sin_theta_i) # 计算R1(空气-外延层界面反射率) # 菲涅尔反射公式 R1 = ((1 - n_opt) / (1 + n_opt)) ** 2 # 使用精确的反射率公式(考虑多次反射干涉) phase = 4 * np.pi * n_opt * d_opt * np.cos(theta_i) / wl + phi + np.pi numerator = R1 + self.R2 - 2 * np.sqrt(R1 * self.R2) * np.cos(phase) denominator = 1 + R1 * self.R2 - 2 * np.sqrt(R1 * self.R2) * np.cos(phase) R_theoretical = numerator / denominator # 转换为百分比(实验数据通常是百分比形式) R_theoretical *= 100 # 反射率物理限制:0~100% R_theoretical = np.clip(R_theoretical, 0, 100) # 计算残差平方(最小二乘法的目标函数) residuals.append((reflectivity[i] - R_theoretical) ** 2) return np.array(residuals) # 初始值:基于碳化硅特性设定 A_guess = np.clip(np.mean(reflectivity), 15, 35) # 反射率基线15~35%(符合空气-SiC反射特性) B_guess = np.clip((np.max(reflectivity) - np.min(reflectivity)) / 2, 5, 20) # 振幅5~20% initial_guess = [initial_d, A_guess, B_guess, 0, 1.0] # [厚度, A, B, 相位, 折射率缩放] # 参数边界:严格贴合碳化硅工程范围 bounds = ( [2, 10, 2, -2 * np.pi, 0.95], # 下限:厚度≥2μm,A≥10%,B≥2%,n_scale≥0.95 [20, 40, 25, 2 * np.pi, 1.05] # 上限:厚度≤20μm,A≤40%,B≤25%,n_scale≤1.05 ) # 优化算法:使用最小二乘法 result = least_squares( objective, initial_guess, bounds=bounds, max_nfev=3000, # 最大函数评估次数 ftol=1e-8, # 函数容差 xtol=1e-8 # 参数容差 ) return result.x[0], result.x[1], result.x[2], result.x[4] # 返回优化后的参数 # 计算拟合质量指标 # 评估理论模型与实验数据的吻合程度 def calculate_fit_quality(self, wavelength, reflectivity, d, A, B, theta_deg, n_carrier, n_scale=1.0): theta_rad = np.radians(theta_deg) lambda_avg = np.mean(wavelength) # 平均波长 # 计算平均波长处的折射率 n_int = self.sellmeier(lambda_avg) n_real, *_ = self.drude(lambda_avg, n_int, n_carrier) n_opt = n_real * n_scale # 计算折射角 sin_theta_i = np.sin(theta_rad) / n_opt if abs(sin_theta_i) >= 1.0: return {"error": "Invalid refraction angle"} # 全反射错误 theta_i = np.arcsin(sin_theta_i) # 计算理论反射率曲线 theoretical = [] for wl in wavelength: # 计算R1(空气-外延层界面反射率) R1 = ((1 - n_opt) / (1 + n_opt)) ** 2 # 使用精确的反射率公式 phase = 4 * np.pi * n_opt * d * np.cos(theta_i) / wl numerator = R1 + self.R2 - 2 * np.sqrt(R1 * self.R2) * np.cos(phase) denominator = 1 + R1 * self.R2 - 2 * np.sqrt(R1 * self.R2) * np.cos(phase) R_theoretical = numerator / denominator # 转换为百分比 R_theoretical *= 100 theoretical.append(R_theoretical) theoretical = np.array(theoretical) # 计算残差:实验值 - 理论值 residuals = reflectivity - theoretical # 计算质量指标 ss_res = np.sum(residuals ** 2) # 残差平方 ss_tot = np.sum((reflectivity - np.mean(reflectivity)) ** 2) # 总平方 return { 'r_squared': 1 - (ss_res / ss_tot) if ss_tot > 0 else 0, # 决定系数,越接近1越好 'rmse': np.sqrt(np.mean(residuals ** 2)), # 均方根误差,越小越好 'mae': np.mean(np.abs(residuals)), # 平均绝对误差,越小越好 'refractive_index': n_opt, # 优化后的折射率 'residuals_std': np.std(residuals) # 残差标准差 } 】
09-08
代码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}") 为何两个代码,输出的畸变率数值不同,请检查产生的原因
最新发布
12-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值