EXT2.0 grid 统计 sum

请问ext grid 中如何实现在每页的结尾显示一个可以自己定义的sum行
eg:
1 10
2 54
3 20
sum 84
import pandapower as pp import numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec from matplotlib.font_manager import FontProperties import matplotlib as mpl import os # 解决中文字符显示问题 def set_chinese_font(): """设置支持中文的字体""" try: # Windows 系统 if os.name == 'nt': font_path = 'C:/Windows/Fonts/simhei.ttf' # 黑体 if os.path.exists(font_path): chinese_font = FontProperties(fname=font_path) mpl.rcParams['font.family'] = chinese_font.get_name() mpl.rcParams['axes.unicode_minus'] = False # 正常显示负号 else: # 尝试其他常见字体 font_names = ['Microsoft YaHei', 'SimHei', 'SimSun', 'KaiTi'] for name in font_names: if name in [f.name for f in mpl.font_manager.fontManager.ttflist]: mpl.rcParams['font.family'] = name mpl.rcParams['axes.unicode_minus'] = False break # Linux/Mac 系统 else: font_names = ['WenQuanYi Micro Hei', 'Noto Sans CJK SC', 'Droid Sans Fallback'] for name in font_names: if name in [f.name for f in mpl.font_manager.fontManager.ttflist]: mpl.rcParams['font.family'] = name mpl.rcParams['axes.unicode_minus'] = False break # 如果仍未找到中文字体,尝试使用默认设置 if 'font.family' not in mpl.rcParams or not mpl.rcParams['font.family']: print("警告: 未找到合适的中文字体,图表标题可能无法正常显示中文") except Exception as e: print(f"字体设置错误: {e}") # 在程序开始时设置字体 set_chinese_font() def create_tree_network(): """创建树干式配电网模型""" net = pp.create_empty_network() # 创建母线 buses = [pp.create_bus(net, vn_kv=10.0, name=f"Bus_{i}") for i in range(6)] # 创建外部电网(平衡节点) pp.create_ext_grid(net, bus=buses[0], vm_pu=1.0, name="Grid") # 树干式线路连接 line_definitions = [ (0, 1, 1.0), (1, 2, 0.8), (2, 3, 0.7), (3, 4, 0.6), (4, 5, 0.5) ] for i, (fb, tb, length) in enumerate(line_definitions): pp.create_line(net, from_bus=buses[fb], to_bus=buses[tb], length_km=length, std_type="NAYY 4x150 SE", name=f"Line_{fb}-{tb}") # 创建负荷 load_params = [ (1, 0.3, 0.1), (2, 0.4, 0.15), (3, 0.5, 0.2), (4, 0.6, 0.25), (5, 0.7, 0.3) ] for i, (bus, p, q) in enumerate(load_params): pp.create_load(net, bus=buses[bus], p_mw=p, q_mvar=q, name=f"Load_{bus}") return net def plot_network_topology(net, filename='network_topology.png'): """绘制配电网拓扑结构图""" plt.figure(figsize=(14, 10)) # 增加高度以适应中文标题 # 节点坐标布局 node_positions = { 0: (0, 0.5), # 变电站 1: (1, 0.5), # 节点1 2: (2, 0.5), # 节点2 3: (3, 0.5), # 节点3 4: (4, 0.5), # 节点4 5: (5, 0.5) # 节点5 } lines = net.line loads = net.load # 绘制线路 for _, line in lines.iterrows(): fb, tb = line['from_bus'], line['to_bus'] x1, y1 = node_positions[fb] x2, y2 = node_positions[tb] plt.plot([x1, x2], [y1, y2], color='dodgerblue', linewidth=3, solid_capstyle='round') # 标注线路长度 mid_x, mid_y = (x1 + x2)/2, (y1 + y2)/2 + 0.15 plt.text(mid_x, mid_y, f"{line['length_km']}km", ha='center', va='center', fontsize=10, bbox=dict(boxstyle='round,pad=0.3', fc='lightblue', ec='navy', alpha=0.7)) # 绘制节点和负荷 for bus_idx, pos in node_positions.items(): x, y = pos # 节点样式 node_color = 'red' if bus_idx == 0 else 'darkgreen' node_size = 800 if bus_idx == 0 else 500 plt.scatter(x, y, s=node_size, color=node_color, edgecolor='black', zorder=5) # 节点标签 plt.text(x, y-0.1, f"节点 {bus_idx}", ha='center', va='top', fontsize=12, weight='bold') # 变电站标注 if bus_idx == 0: plt.text(x, y+0.1, "变电站\n(Vm=1.0 p.u.)", ha='center', va='bottom', fontsize=10) # 负荷标注 if bus_idx != 0: bus_loads = loads[loads['bus'] == bus_idx] if not bus_loads.empty: load = bus_loads.iloc[0] plt.text(x, y-0.18, f"负荷: {load['p_mw']}MW\n+ j{load['q_mvar']}Mvar", ha='center', va='top', fontsize=9, bbox=dict(boxstyle='round,pad=0.4', fc='lightyellow', ec='gold', alpha=0.8)) # 图例 plt.scatter([], [], s=200, color='red', label='变电站 (平衡节点)') plt.scatter([], [], s=150, color='darkgreen', label='负荷节点') plt.plot([], [], color='dodgerblue', linewidth=3, label='线路段') # 标题和底部信息 plt.title('树干式配电网络拓扑结构', fontsize=16, weight='bold') plt.legend(loc='upper left', bbox_to_anchor=(1, 1)) plt.grid(False) plt.axis('equal') plt.axis('off') total_p = sum(loads['p_mw']) total_q = sum(loads['q_mvar']) total_len = sum(lines['length_km']) plt.figtext(0.5, 0.02, f"总负荷: {total_p:.1f}MW + j{total_q:.1f}Mvar | " f"线路: {len(lines)}段 | " f"总长度: {total_len:.1f}km", ha='center', fontsize=11, bbox=dict(boxstyle='round', fc='lightgray', alpha=0.5)) plt.tight_layout() plt.savefig(filename, dpi=150) plt.close() print(f"拓扑图已保存为 '{filename}'") return filename def analyze_dg_impact(): """分析分布式电源接入影响""" results = { 'voltage': {}, 'losses': {} } # 参数设置 capacities = np.linspace(0, 2.0, 21) # 0-2MW, 21个点 positions = range(1, 6) # 节点1-5 # 基准情况 net = create_tree_network() pp.runpp(net) base_voltage = net.res_bus.vm_pu.copy() base_loss = net.res_line.pl_mw.sum() # 参数扫描 for pos in positions: volt_list = [] loss_list = [] for cap in capacities: net = create_tree_network() # 添加分布式电源 pp.create_sgen(net, bus=pos, p_mw=cap, q_mvar=0, name=f"DG_{pos}_{cap}") pp.runpp(net) # 记录电压偏差(末端节点) volt_dev = abs(net.res_bus.vm_pu[5] - 1.0) * 1000 # 毫伏 volt_list.append(volt_dev) # 记录网损 loss_list.append(net.res_line.pl_mw.sum()) results['voltage'][pos] = volt_list results['losses'][pos] = loss_list # 可视化结果 - 移除结论部分 plot_results(results, capacities, positions, base_voltage[5], base_loss) return results def plot_results(results, capacities, positions, base_volt, base_loss): """ 绘制DG影响分析结果图 移除结论部分,保留电压偏差和网络损耗曲线 """ plt.figure(figsize=(15, 10)) # 调整高度 # 创建网格布局 - 只保留2行 gs = gridspec.GridSpec(2, 1, height_ratios=[1, 1]) # 电压偏差曲线 ax1 = plt.subplot(gs[0]) for pos in positions: ax1.plot(capacities, results['voltage'][pos], marker='o', linestyle='-', markersize=8, linewidth=2, label=f'DG接入节点 {pos}') ax1.axhline(y=abs(base_volt-1)*1000, color='k', linestyle='--', linewidth=2, label='基准情况') ax1.set_title('末端节点(节点5)电压偏差', fontsize=14, weight='bold') ax1.set_xlabel('DG容量(MW)', fontsize=12) ax1.set_ylabel('电压偏差(mV)', fontsize=12) ax1.grid(True, linestyle='--', alpha=0.7) ax1.legend(loc='best', fontsize=10) # 网损曲线 ax2 = plt.subplot(gs[1]) for pos in positions: ax2.plot(capacities, results['losses'][pos], marker='s', linestyle='-', markersize=8, linewidth=2, label=f'DG接入节点 {pos}') ax2.axhline(y=base_loss, color='k', linestyle='--', linewidth=2, label='基准情况') ax2.set_title('网络损耗', fontsize=14, weight='bold') ax2.set_xlabel('DG容量(MW)', fontsize=12) ax2.set_ylabel('总损耗(MW)', fontsize=12) ax2.grid(True, linestyle='--', alpha=0.7) ax2.legend(loc='best', fontsize=10) plt.tight_layout() plt.savefig('dg_impact_analysis.png', dpi=150) plt.close() print("DG影响分析图已保存为 'dg_impact_analysis.png'") def main(): """主程序""" # 步骤1: 创建配电网模型 net = create_tree_network() # 步骤2: 绘制网络拓扑图 topology_file = plot_network_topology(net) # 步骤3: 执行DG影响分析 print("开始执行分布式电源影响分析...") results = analyze_dg_impact() print("分析完成!") # 步骤4: 展示关键结果 print("\n网络关键数据:") print(f"- 节点数量: {len(net.bus)}") print(f"- 线路数量: {len(net.line)}") print(f"- 负荷数量: {len(net.load)}") print(f"- 总负荷功率: {sum(net.load.p_mw):.2f} MW") print(f"- 总负荷无功: {sum(net.load.q_mvar):.2f} Mvar") print("\n分析结果保存:") print(f"- 网络拓扑图: {topology_file}") print("- DG影响分析图: dg_impact_analysis.png") if __name__ == "__main__": main() 修改一下这段代码的注释,不要出现避免什么错误的字眼
11-06
function reflow_oven_temperature_simulation() speed=78; v = speed/ 100 / 60; % 传送带速度 (cm/min -> m/s) T_initial = 25; % 初始温度 (°C) L = 150e-6; % 焊接区域厚度 (m) % 2. 温区温度设定 (根据题目要求) zone_temps = [25, 182, 182, 182, 182, 203, 237, 254, 254, 25]; % 3. 优化后的热参数 (来自反演结果) a_optimal = [8.42e-10, 8.42e-10, 8.42e-10, 8.42e-10, 8.42e-10, ... 8.07e-11, 1.55e-09, 1.22e-09, 1.22e-09, 1.14e-09]; beta_optimal = [1.42e-06, 1.42e-06, 1.42e-06, 1.42e-06, 1.42e-06, ... 3.39e-06, 2.50e-06, 1.37e-06, 1.37e-06, 6.48e-07]; % 4. 合并参数 (前10个是a,后10个是beta) params = [a_optimal, beta_optimal]; % 5. 计算电路板通过炉子的总时间 total_length = 4.355; % 炉子总长度 (m) total_time = total_length / v; % 总通过时间 (s) % 6. 设置时间网格 (每隔0.5s) t = 0:0.5:ceil(total_time); % 7. 求解PDE获取温度曲线 T_center = solve_pde(params, t, v, T_initial, L); % 8. 绘制炉温曲线 plot_temperature_curve(t, T_center); end function plot_temperature_curve(t, T) % 绘制炉温曲线图 figure('Position', [100, 100, 800, 500]); plot(t, T, 'b-', 'LineWidth', 1.5); % 设置图形属性 title('焊接区域中心温度变化曲线', 'FontSize', 14); xlabel('时间 (s)', 'FontSize', 12); ylabel('温度 (°C)', 'FontSize', 12); grid on; % 添加制程界限参考线 yline(217, '--g', '217°C', 'LabelHorizontalAlignment', 'left'); yline(240, '--m', '240°C', 'LabelHorizontalAlignment', 'left'); yline(250, '--r', '250°C', 'LabelHorizontalAlignment', 'left'); legend('温度曲线', 'Location', 'northwest'); xlim([0, max(t)]); ylim([0, max(T)*1.1]); end function T_sim = solve_pde(params, t, v, T0, L) % 空间离散 xmesh = linspace(0, L, 30); % 空间离散点(30个点) % 解热传导方程PDE sol = pdepe(0, @heat_pde, @heat_ic, @heat_bc, xmesh, t, ... odeset('RelTol', 1e-3, 'AbsTol', 1e-6), ... params, v, T0); % 提取温度(中心点) T_sim = sol(:, round(end/2)); % 空间中心点 end function [c, f, s] = heat_pde(x, t, u, DuDx, params, v, T0) % 获取当前区域的参数 y = v * t; [a, beta] = get_zone_params(y, params); % PDE系数 c = 1; % 热容系数 f = a * DuDx; % 热流 s = 0; % 无源项 end function u0 = heat_ic(x, params, v, T0) u0 = T0; % 均匀初始温度 end function [pl, ql, pr, qr] = heat_bc(xl, ul, xr, ur, t, params, v, T0) % 获取当前区域的参数 y = v * t; [~, beta] = get_zone_params(y, params); T_ext = get_zone_temp(y); % 边界条件 pl = -beta * (ul - T_ext); ql = 1; pr = beta * (ur - T_ext); qr = 1; end function [a, beta] = get_zone_params(y, params) % 温区边界 (米) zone_boundaries = [0, 0.25, 1.975, 2.33, 2.685, 3.395, 4.355]; if y < zone_boundaries(2) zone = 1; elseif y < zone_boundaries(3) zone = 1; elseif y < zone_boundaries(4) zone = 2; elseif y < zone_boundaries(5) zone = 3; elseif y < zone_boundaries(6) zone = 4; else zone = 5; end a = params(zone); beta = params(zone + 10); % β参数在索引11-20 end function T_ext = get_zone_temp(y) zone_boundaries = [0, 0.05, 0.30, 0.605, 1.665, 1.92, 2.07, 2.275, 2.425, 2.63, ... 2.78, 3.085, 3.235, 3.49, 3.64, 4.25, 4.355]; % 温区温度设定值 zone_temps = [25, 182, 182, 182, 182, 203, 237, 254, 254, 25]; % 检查位置是否在有效范围内 if y < 0 || y > 4.355 error('位置 y 超出回焊炉范围 (0-4.355 m)'); end % 过渡区处理 if y < zone_boundaries(2) % 0-0.05m: 炉前区 T_ext = 25 + (182 - 25) * (y / 0.05); elseif y >= zone_boundaries(6) && y < zone_boundaries(7) % 1.92-2.07m: 温区5到温区6过渡 T_ext = 182 + (203 - 182) * (y - 1.92) / 0.15; elseif y >= zone_boundaries(8) && y < zone_boundaries(9) % 2.275-2.425m: 温区6到温区7过渡 T_ext = 203 + (237 - 203) * (y - 2.275) / 0.15; elseif y >= zone_boundaries(10) && y < zone_boundaries(11) % 2.63-2.78m: 温区7到温区8过渡 T_ext = 237 + (254 - 237) * (y - 2.63) / 0.15; elseif y >= zone_boundaries(14) && y < zone_boundaries(15) % 3.49-3.64m: 温区10到温区11过渡 T_ext = 254 + (25 - 254) * (y - 3.49) / 0.15; else % 恒温区处理 if y < zone_boundaries(3) % 0.05-0.30m: 温区1 T_ext = zone_temps(2); elseif y < zone_boundaries(6) % 0.30-1.92m: 温区2-5 T_ext = zone_temps(2); elseif y < zone_boundaries(8) % 1.92-2.275m: 温区6 T_ext = zone_temps(6); elseif y < zone_boundaries(10) % 2.275-2.63m: 温区7 T_ext = zone_temps(7); elseif y < zone_boundaries(14) % 2.63-3.49m: 温区8-9 T_ext = zone_temps(8); else % 3.49-4.355m: 温区10-11 T_ext = zone_temps(10); end end end先阅读代码理解机理模型,然后使用混沌采样的方法寻找最大传送带速度
08-14
import os import gc import glob import shutil import warnings warnings.filterwarnings("ignore") import numpy as np import pandas as pd from tqdm import tqdm # 图像处理与特征 import cv2 from skimage import feature from skimage.feature import canny # 自动兼容旧版或新版 scikit-image if hasattr(feature, 'graycomatrix'): graycomatrix = feature.graycomatrix graycoprops = feature.graycoprops else: graycomatrix = feature.greycomatrix graycoprops = feature.greycoprops # 统计学与评估 from scipy.stats import skew, kurtosis # 机器学习 from sklearn.model_selection import train_test_split, StratifiedKFold from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA from sklearn.linear_model import LogisticRegression from sklearn.metrics import (confusion_matrix, roc_curve, roc_auc_score, precision_recall_fscore_support, brier_score_loss) # 可视化(全部保存到 result1) import matplotlib matplotlib.use('Agg') # 不显示,只保存 import matplotlib.pyplot as plt import os import glob # ====================== # 一、基础设置与工具函数 # ====================== # 结果输出目录 RESULT_DIR = "result1" os.makedirs(RESULT_DIR, exist_ok=True) # 可能的图像与标签目录(两种常见组织方式) CANDIDATE_DIRS = [ # YOLO标准组织 {"img_dir": "images/train", "lab_dir": "labels/train"}, {"img_dir": "images/val", "lab_dir": "labels/val"}, # JPEGImages + labels 组织 {"img_dir": "JPEGImages", "lab_dir": "labels"}, ] # 允许的图片后缀 IMG_EXT = ["*.jpg", "*.jpeg", "*.png", "*.bmp"] def list_images(img_dir): """列出目录下的所有图片路径(兼容多后缀)""" paths = [] for ext in IMG_EXT: paths.extend(glob.glob(os.path.join(img_dir, ext))) return sorted(paths) def yolo_label_path_for_image(img_path, lab_root): """根据图像路径与标签根目录,推断同名YOLO标签文件路径(.txt)""" base = os.path.splitext(os.path.basename(img_path))[0] return os.path.join(lab_root, base + ".txt") def read_classes_map(classes_file="classes.txt"): """读取类别映射(若存在),仅用于信息提示;问题1只需二分类标签""" if os.path.exists(classes_file): try: with open(classes_file, "r", encoding="utf-8") as f: lines = [x.strip() for x in f.readlines() if x.strip()] return {i: name for i, name in enumerate(lines)} except Exception: return None return None def imread_rgb(path, resize_to=None): """读取并返回RGB图像,必要时缩放;对异常图像进行鲁棒处理""" img = cv2.imread(path, cv2.IMREAD_COLOR) if img is None: raise IOError(f"无法读取图像:{path}") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if resize_to is not None: img = cv2.resize(img, resize_to, interpolation=cv2.INTER_AREA) return img def safe_minmax_norm(gray): """对灰度图做Min-Max归一化到[0,1]""" gmin, gmax = np.min(gray), np.max(gray) if gmax > gmin: return (gray - gmin) / (gmax - gmin) return gray * 0.0 def edge_density(gray_norm): """基于Canny的边缘密度(归一化灰度作为输入)""" # 根据图像对比度自适应阈值(简单启发式) v = np.median(gray_norm) lower = max(0, (1.0 - 0.33) * v) upper = min(1.0, (1.0 + 0.33) * v) # skimage.feature.canny 接受 [0,1] 浮点图像 edges = canny(gray_norm, sigma=1.5, low_threshold=lower, high_threshold=upper) return edges.mean(), edges # 返回比例与边缘图 def gradient_stats(gray_norm): """Sobel梯度模的统计量""" g = (gray_norm * 255.0).astype(np.uint8) gx = cv2.Sobel(g, cv2.CV_32F, 1, 0, ksize=3) gy = cv2.Sobel(g, cv2.CV_32F, 0, 1, ksize=3) mag = np.sqrt(gx * gx + gy * gy) mag = mag.flatten() return np.mean(mag), np.std(mag), skew(mag), kurtosis(mag, fisher=True) def gray_hist_stats(gray_norm, bins=32): """灰度直方图统计:均值、方差、偏度、峰度、熵、能量等""" g = (gray_norm * 255.0).astype(np.uint8) hist = cv2.calcHist([g], [0], None, [bins], [0, 256]).ravel().astype(np.float32) hist /= (hist.sum() + 1e-8) vals = np.arange(bins, dtype=np.float32) mean_ = (hist * vals).sum() var_ = (hist * (vals - mean_) ** 2).sum() sk_ = ((hist * (vals - mean_) ** 3).sum()) / (np.sqrt(var_) ** 3 + 1e-8) ku_ = ((hist * (vals - mean_) ** 4).sum()) / (var_ ** 2 + 1e-8) - 3.0 entropy_ = -(hist * np.log(hist + 1e-12)).sum() energy_ = (hist ** 2).sum() return mean_, var_, sk_, ku_, entropy_, energy_ def glcm_features(gray_norm, distances=(1, 2, 3), angles=(0, np.pi / 4, np.pi / 2, 3 * np.pi / 4), levels=32): """GLCM纹理特征(多距离、多角度求均值)""" g = (gray_norm * (levels - 1)).astype(np.uint8) glcm = graycomatrix(g, distances=distances, angles=angles, levels=levels, symmetric=True, normed=True) feats = {} for prop in ["contrast", "dissimilarity", "homogeneity", "energy", "correlation", "ASM"]: vals = graycoprops(glcm, prop).ravel() feats[prop + "_mean"] = float(np.mean(vals)) feats[prop + "_std"] = float(np.std(vals)) return feats def color_steady_features(img_rgb): """颜色稳态特征(HSV的H/S/V统计 + 对比度受限自适应直方图前后的亮度稳态差异)""" hsv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HSV) H, S, V = hsv[..., 0], hsv[..., 1], hsv[..., 2] # 归一化 Hn = H / 180.0 Sn = S / 255.0 Vn = V / 255.0 feats = { "H_mean": float(Hn.mean()), "H_std": float(Hn.std()), "S_mean": float(Sn.mean()), "S_std": float(Sn.std()), "V_mean": float(Vn.mean()), "V_std": float(Vn.std()), } # CLAHE 增强前后对比(度量光照鲁棒性) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) V_eq = clahe.apply((Vn * 255).astype(np.uint8)).astype(np.float32) / 255.0 feats["V_clahe_delta_mean"] = float((V_eq - Vn).mean()) feats["V_clahe_delta_std"] = float((V_eq - Vn).std()) return feats def extract_features_for_image(img_path, resize_to=(640, 640)): """对单张图像提取特征,返回特征字典与中间过程(可选)""" img = imread_rgb(img_path, resize_to=resize_to) # 灰度与归一化 gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32) gray = safe_minmax_norm(gray) # 直方图与统计 gh_mean, gh_var, gh_sk, gh_ku, gh_entropy, gh_energy = gray_hist_stats(gray) # 边缘密度与梯度 ed_ratio, ed_map = edge_density(gray) g_mean, g_std, g_sk, g_ku = gradient_stats(gray) # GLCM纹理 glcm_feats = glcm_features(gray) # 颜色稳态 color_feats = color_steady_features(img) # 汇总 feats = { "gray_hist_mean": gh_mean, "gray_hist_var": gh_var, "gray_hist_skew": gh_sk, "gray_hist_kurt": gh_ku, "gray_hist_entropy": gh_entropy, "gray_hist_energy": gh_energy, "edge_density": ed_ratio, "grad_mean": g_mean, "grad_std": g_std, "grad_skew": g_sk, "grad_kurt": g_ku, } feats.update(glcm_feats) feats.update(color_feats) return feats def determine_binary_label(label_txt_path): """根据YOLO标签txt是否存在且非空,确定是否有破损(1=有;0=无)""" if (not os.path.exists(label_txt_path)) or (os.path.getsize(label_txt_path) == 0): return 0 # 存在且非空 → 有破损 return 1 # ====================== # 二、数据发现:定位真实数据目录 # ====================== def discover_dataset(): """ 聚合训练样本(图像路径 + 二分类标签) 优先使用 images/train + labels/train 若val存在,追加;若只有 JPEGImages + labels,则以此为准 """ records = [] found_any = False # 优先 YOLO 组织:train + val for d in [{"img_dir": "images/train", "lab_dir": "labels/train"}, {"img_dir": "images/val", "lab_dir": "labels/val"}]: if os.path.isdir(d["img_dir"]) and os.path.isdir(d["lab_dir"]): imgs = list_images(d["img_dir"]) if len(imgs) > 0: for ip in imgs: lp = yolo_label_path_for_image(ip, d["lab_dir"]) y = determine_binary_label(lp) records.append({"img_path": ip, "label_path": lp, "y": y}) found_any = True # 若以上没有任何样本,再尝试 JPEGImages + labels if (not found_any) and os.path.isdir("JPEGImages") and os.path.isdir("labels"): imgs = list_images("JPEGImages") if len(imgs) > 0: for ip in imgs: lp = yolo_label_path_for_image(ip, "labels") y = determine_binary_label(lp) records.append({"img_path": ip, "label_path": lp, "y": y}) found_any = True # 收拢为 DataFrame df = pd.DataFrame(records) return df, found_any # ====================== # 三、主流程:特征构建 → LDA → 概率校准(可选) # ====================== def main(): # 读取类别映射(非必须,问题1只需要二分类标签) classes_map = read_classes_map("classes.txt") if classes_map is not None: with open(os.path.join(RESULT_DIR, "classes_map.txt"), "w", encoding="utf-8") as f: for k, v in classes_map.items(): f.write(f"{k}\t{v}\n") # 发现数据 df, ok = discover_dataset() if (not ok) or (len(df) == 0): # 如无可用数据,给出友好提示并退出 msg = ( "未发现可用的图像与标签,请检查根目录数据组织是否为以下任一形式:\n" " A) images/train + labels/train [+ images/val + labels/val]\n" " B) JPEGImages + labels\n" "同时确保图像为 jpg/png 等格式,标签为 YOLO 的 .txt 文件。" ) print(msg) # 保存提示到文件,便于排障 with open(os.path.join(RESULT_DIR, "DATA_NOT_FOUND.txt"), "w", encoding="utf-8") as f: f.write(msg) return # 打乱并划分训练/验证(保留标签分布) df = df.sample(frac=1.0, random_state=42).reset_index(drop=True) train_df, val_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df["y"]) # 提取特征(为节约时间,默认将图像缩放到 640x640;如需更快,可降到 480 或 384) def build_feature_table(sub_df, tag): feat_list = [] pbar = tqdm(sub_df.itertuples(index=False), total=len(sub_df), desc=f"提取特征[{tag}]") for row in pbar: feats = extract_features_for_image(row.img_path, resize_to=(640, 640)) feats["y"] = row.y feats["img_path"] = row.img_path feat_list.append(feats) feat_df = pd.DataFrame(feat_list) feat_df.to_csv(os.path.join(RESULT_DIR, f"features_{tag}.csv"), index=False, encoding="utf-8-sig") return feat_df train_feat = build_feature_table(train_df, "train") val_feat = build_feature_table(val_df, "val") # 特征矩阵与标签 feature_cols = [c for c in train_feat.columns if c not in ("y", "img_path")] X_train = train_feat[feature_cols].values y_train = train_feat["y"].values.astype(int) X_val = val_feat[feature_cols].values y_val = val_feat["y"].values.astype(int) # 标准化 + PCA(保留95%方差) scaler = StandardScaler() X_train_std = scaler.fit_transform(X_train) X_val_std = scaler.transform(X_val) pca = PCA(n_components=0.95, svd_solver="full", random_state=42) X_train_pca = pca.fit_transform(X_train_std) X_val_pca = pca.transform(X_val_std) # 训练 LDA(主模型) lda = LDA() lda.fit(X_train_pca, y_train) # 验证集预测 val_decision = lda.decision_function(X_val_pca) # 连续分数 # 将决策分数线性映射到(0,1)近似为概率(便于画 ROC/Brier),也可换用逻辑回归做校准 # 这里保持“不过度工程化”的原则,直接做min-max归一化 p_min, p_max = val_decision.min(), val_decision.max() if p_max > p_min: val_prob = (val_decision - p_min) / (p_max - p_min) else: val_prob = (val_decision - p_min) # 常数,退化情况 y_pred = (val_prob >= 0.5).astype(int) # 评估(混淆矩阵、ROC、分布可视化、PCA方差解释率、LDA系数解析) cm = confusion_matrix(y_val, y_pred) prc, rec, f1, _ = precision_recall_fscore_support(y_val, y_pred, average="binary", zero_division=0) auc = roc_auc_score(y_val, val_prob) if len(np.unique(y_val)) == 2 else np.nan brier = brier_score_loss(y_val, val_prob) # 保存评估结果 with open(os.path.join(RESULT_DIR, "eval_ql.txt"), "w", encoding="utf-8") as f: f.write("=== 问题1:有无破损二分类(LDA)评估 ===\n") f.write(f"样本数(训练/验证):{len(train_feat)} / {len(val_feat)}\n") f.write("\n[混淆矩阵]\n") f.write(pd.DataFrame(cm, index=["真0(无破损)", "真1(有破损)"], columns=["预测0", "预测1"]).to_string()) f.write("\n\n[指标]\n") f.write(f"Precision: {prc:.4f}\n") f.write(f"Recall: {rec:.4f}\n") f.write(f"F1: {f1:.4f}\n") f.write(f"ROC-AUC: {auc:.4f}\n") f.write(f"Brier: {brier:.6f}\n") f.write("\n[特征维度→PCA后维度]\n") f.write(f"{X_train.shape[1]} → {X_train_pca.shape[1]}\n") # ====================== # 四、可视化(全部保存到 result1) # ====================== # 1)混淆矩阵热力图 plt.figure(figsize=(5, 4)) plt.imshow(cm, cmap="Blues") plt.title("Confusion Matrix (LDA)") plt.colorbar() tick_labels = ["真0(无破损)", "真1(有破损)"] plt.xticks([0, 1], ["预测0", "预测1"]) plt.yticks([0, 1], tick_labels) for i in range(cm.shape[0]): for j in range(cm.shape[1]): plt.text(j, i, cm[i, j], ha="center", va="center", color="black") plt.tight_layout() plt.savefig(os.path.join(RESULT_DIR, "cm_lda.png"), dpi=200) plt.close() # 2)ROC 曲线 fpr, tpr, _ = roc_curve(y_val, val_prob) plt.figure(figsize=(5, 4)) plt.plot(fpr, tpr, lw=2) plt.plot([0, 1], [0, 1], linestyle="--") plt.xlabel("False Positive Rate") plt.ylabel("True Positive Rate") plt.title(f"ROC Curve (AUC={auc:.3f})") plt.tight_layout() plt.savefig(os.path.join(RESULT_DIR, "roc_lda.png"), dpi=200) plt.close() # 3)概率直方图(校准感) plt.figure(figsize=(5, 4)) plt.hist(val_prob[y_val == 0], bins=20, alpha=0.6, label="无破损") plt.hist(val_prob[y_val == 1], bins=20, alpha=0.6, label="有破损") plt.title("Predicted Score Distribution") plt.xlabel("score") plt.ylabel("count") plt.legend() plt.tight_layout() plt.savefig(os.path.join(RESULT_DIR, "score_hist.png"), dpi=200) plt.close() # 4)PCA 累计方差解释率 plt.figure(figsize=(6, 4)) cumvar = np.cumsum(pca.explained_variance_ratio_) plt.plot(np.arange(1, len(cumvar) + 1), cumvar, marker="o") plt.xlabel("Number of Components") plt.ylabel("Cumulative Explained Variance") plt.title("PCA Cumulative Explained Variance") plt.grid(alpha=0.3) plt.tight_layout() plt.savefig(os.path.join(RESULT_DIR, "pca_cumvar.png"), dpi=200) plt.close() # 5)LDA 投影分布 plt.figure(figsize=(5, 4)) proj = lda.transform(X_val_pca).ravel() plt.hist(proj[y_val == 0], bins=30, alpha=0.6, label="无破损") plt.hist(proj[y_val == 1], bins=30, alpha=0.6, label="有破损") plt.title("LDA Projection (Validation)") plt.xlabel("projection value") plt.ylabel("count") plt.legend() plt.tight_layout() plt.savefig(os.path.join(RESULT_DIR, "lda_projection.png"), dpi=200) plt.close() # 6)特征重要性近似可视化(以 LDA 在 PCA 空间的系数范数替代说明) # 注:LDA 在 PCA 空间的系数 → 回映射到原特征并不直接,一般作为示意 coefs = lda.coef_.ravel() plt.figure(figsize=(6, 3)) plt.plot(np.arange(len(coefs)), coefs, marker="o", linewidth=1) plt.title("LDA Coefficients (in PCA space)") plt.xlabel("component index") plt.ylabel("coefficient") plt.grid(alpha=0.3) plt.tight_layout() plt.savefig(os.path.join(RESULT_DIR, "lda_coefs.png"), dpi=200) plt.close() # 7)样例图像 + 边缘图叠加(增强可读性,随机抽取少量保存) sample_show = val_df.sample(n=min(6, len(val_df)), random_state=7) fig, axes = plt.subplots(2, 3, figsize=(10, 6)) axes = axes.ravel() for ax, (_, row) in zip(axes, sample_show.iterrows()): img = imread_rgb(row.img_path, resize_to=(640, 640)) gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32) gray = safe_minmax_norm(gray) _, edges = edge_density(gray) # 叠加显示 overlay = img.copy() overlay[edges] = [255, 0, 0] # 用红色标记边缘 ax.imshow(overlay) ax.set_title(f"y={row.y}") ax.axis("off") plt.tight_layout() plt.savefig(os.path.join(RESULT_DIR, "samples_overlay_edges.png"), dpi=200) plt.close() # 保存最终验证集预测结果(供溯源) out_df = val_feat[["img_path"]].copy() out_df["y_true"] = y_val out_df["y_prob"] = val_prob out_df["y_pred"] = y_pred out_df.to_csv(os.path.join(RESULT_DIR, "val_predictions.csv"), index=False, encoding="utf-8-sig") print("训练与评估完成。结果文件与图片已保存到 ./result1/ 目录。") if __name__ == "__main__": main()
10-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值