LIDC肺结节CT数据预处理实战:从DICOM到Mask的完整链路
简介面向医学影像处理与Python数据分析人群的LIDC数据集说明与标签文件适用于肺结节检测、分割和特征提取研究中的前期数据准备与结构理解尤其适合具备一定编程基础的研究生或算法工程师。压缩包共1323个文件大小仅6.44MB包含1319个XML格式的结节标注以及CSV、XLS、XLSX、TXT等辅助文档XML逐例记录了病人ID、扫描序列、结节三维坐标、直径和放射科医生评级表格文件中含有元数据与全库结节统计TXT则给出原始数据下载说明。已有282人学习下载。借助pydicom、lxml、pandas等Python库可快速解析CT影像与标注信息梳理由结节位置、尺寸、纹理特征构成的标签体系为后续构建机器学习或深度学习模型提供可直接使用的数据输入。这些文件能有效减少整理原始LIDC数据的时间帮助研究者将精力集中在模型设计和实验验证上。1. LIDC 不只是数据集更是一套带“标准答案”的肺结节标注体系LIDCLung Image Database Consortium在肺结节 AI 检测这个方向几乎是绕不开的基准。很多团队第一次做结节检测、良恶性分类、甚至三维分割拿来练手的第一份公开数据都是它。它的特殊之处在于不光是给了 CT 影像还给了四位放射科医生对每个结节的独立标注包括轮廓坐标、良恶性评分、内部结构、钙化情况等。也就是说它把“金标准”的构建过程也公开了。适合谁用做医学影像深度学习的新手、要跑基线实验的算法工程师、以及需要标准评测集的科研人员。这篇笔记会把数据目录、XML 标签结构、DICOM 转 nii.gz、标签转 mask 的完整链路拆开讲最后落到验证方法和几个我实际踩过的坑上。2. 先把家底摸清LIDC 的目录结构与文件格式2.1 两种常见的数据形态TCIA 原始包与重打包版LIDC 数据集一般通过 TCIAThe Cancer Imaging Archive公开。它有两种获取形态一种是直接从 TCIA 拉下来的原始 DICOM 文件按病例Patient和采集序列Study/Series层级存放另一种是社区里有人做过预处理的重打包版直接把每个病例整理成一个 .nii.gz 文件附带一个 CSV 或 XML 文件记录结节信息。我建议第一次跑流程的从业者直接用原始 DICOM 包因为重打包版虽然省事但各家用的重采样参数、窗宽窗位甚至方向处理都不同拿来做自己项目的 baseline 容易引入隐性偏差。原始包虽然文件数量多、读取慢但信息完整后面无论你要做二维检测还是三维分割都有回旋余地。原始包的顶层通常是这样组织LIDC-IDRI/ ├── LIDC-IDRI-0001/ │ ├── 1.3.6.1.4.1.14519.5.2.1.6279.6001.xxxx/ │ │ ├── 1.3.6.1.4.1.14519.5.2.1.6279.6001.xxxx/ │ │ │ └── *.dcm │ └── 1.3.6.1.4.1.14519.5.2.1.6279.6001.yyyy/ │ └── *.dcm └── LIDC-IDRI-0002/每个病例目录下可能有一个或多个序列目录序列目录下才是真正的 DICOM 切片。这里要特别注意同一个病例可能同时存在“薄层”和“厚层”两个序列通常薄层层厚 ≤ 1.25mm才是做结节检测主流的选择厚层数据一般用于对照。如果你直接遍历所有 .dcm 文件不区分序列后面重建三维体时会把不同层厚的切片混在一起重建出来的体积在 Z 轴上是错乱的。我在第一次处理时就犯过这个错后来强制规定每个序列单独建一个子目录所有处理脚本都基于序列级路径去读。2.2 DICOM 与 XML两个必须分开对待的文件体系LIDC 的标签并不存在 DICOM 文件里而是单独打包在一个 XML 文件里。每个病例对应一个 XML 文件命名通常是LIDC-IDRI-xxxx.xml。这个 XML 里记录了所有结节的边界、属性、医生评分是整套标注的核心。而 DICOM 文件只负责图像本身。也就是说要做训练样本必须自己写代码把 XML 里的相对坐标换算到 DICOM 的像素坐标再和图像叠加。XML 的顶层结构大致如下?xml version1.0 encodingUTF-8? ReadSession ResponseHeader.../ResponseHeader ResponseBody LIDCReadSession ... StudyInstanceUID.../StudyInstanceUID SeriesInstanceUID.../SeriesInstanceUID ReadingSession.../ReadingSession /LIDCReadSession /ResponseBody /ReadSession这里的关键是SeriesInstanceUID它把 XML 和具体的某个 DICOM 序列对应起来。一个病例如果有多个序列XML 里会有多个SeriesInstanceUID块每块下面才是该序列里找到的结节标注。解析的时候你先匹配 UID 再往下读才不会张冠李戴。2.3 病例目录的命名规则SeriesInstanceUID 才是锚点很多新手会按“LIDC-IDRI-0001”这个病例 ID 去关联图像和标签。但更准确的做法是病例 ID 只用来定位病人序列 UID 才是真正连接图像和标签的钥匙。因为一个病例下可能有多个序列每个序列的命名是一长串 DICOM UID比如1.3.6.1.4.1.14519.5.2.1.6279.6001.298806379830352084031707658958。在代码里我习惯把所有序列 UID 先扫出来然后与 XML 里的 SeriesInstanceUID 做交集只处理能匹配上的序列import os import pydicom from pathlib import Path def collect_series_uids(patient_dir): series_uids {} for dcm_path in Path(patient_dir).rglob(*.dcm): try: ds pydicom.dcmread(dcm_path, stop_before_pixelsTrue) suid ds.SeriesInstanceUID series_uids.setdefault(suid, []).append(dcm_path) except Exception: continue return series_uids这段代码用stop_before_pixelsTrue只读元数据速度快很多因为一个病例的 DICOM 文件动辄几百个完整读像素会非常耗时。series_uids以序列 UID 为主键值为该序列下所有切片路径。后面跟 XML 标签关联时直接拿这个字典来查匹配。import xml.etree.ElementTree as ET def parse_series_from_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() series_list [] for session in root.iter(LIDCReadSession): reading session.find(ResponseBody/LIDCReadSession) # 实际结构根据命名空间可能略有差异 for uid_elem in session.iter(SeriesInstanceUID): series_list.append(uid_elem.text) return set(series_list)这里有个很容易踩的坑LIDC 的 XML 文件带了命名空间不同版本解析方式不太一样。我一般用.iter(SeriesInstanceUID)这种忽略层级路径的方式直接遍历节点文本比严格按 XPath 拿更稳。拿到序列 UID 集合后与 DICOM 扫描结果做差集就能快速发现哪些序列没有对应标签、哪些标签找不到图像。匹配逻辑其实一句话只有图像序列 ∈ XML 序列的样本才有资格进训练集。那些只标了但没图像、或有图像没标的病例直接跳过比强行补标签更安全。3. 把 XML 拆成可用标签结节属性与坐标解析3.1 从 XML 里拆出结节的坐标与 malignancyLIDC 的 XML 里每个结节由一组unblindedReadNodule描述内部包含noduleID、characteristics、roi三个核心部分。roi里是一系列边缘点坐标这些坐标描述的是结节的轮廓——不是中心点也不是框。每个标记医生给出一组自己的轮廓点同一结节在不同医生那里坐标不完全相同。关键字段主要有这几个字段含义取值范围malignancy恶性程度评分1良性到 5恶性subtlety微妙程度1 到 5internalStructure内部结构1 软组织 / 2 液体 / 3 脂肪 / 4 空气calcification钙化情况1 到 6sphericity球形度1 到 5margin边缘描述1 到 5lobulation分叶1 到 5spiculation毛刺1 到 5texture纹理1 到 5diameter平均直径mm浮点数这里malignancy是最常用的标签。很多分类任务直接把 1/2 当良性、4/5 当恶性、3 剔除也有把 3 归为良性的做法。这个阈值决策会直接影响实验结论最好在论文里写清楚否则实验复现时别人跟你对不上。轮廓坐标在 XML 里以xCoord和yCoord形式出现它们是在该序列 DICOM 的二维切片上的坐标单位是像素。注意这些坐标不是物理坐标毫米而是图像坐标且 Z 轴方向由该切片在序列里的位置决定。roi里还会带上imageZPosition或imageSopUid来指明是哪一个切片解析时要保留这个对应关系。3.2 标注者不一致时怎么办多数表决与共识掩码四位医生标注同一个结节轮廓和评分都可能有差异。处理方式通常有两种第一种是结节级多数表决。对malignancy字段取四位医生评分的平均值或中位数作为该结节的最终标签。我一般建议取中位数因为评分是离散值平均数容易出现 2.75 这种不好归类的数字。第二种是轮廓级合并。常见做法是把所有医生的轮廓点都保留取并集区域作为结节的粗分割掩码。这种“宽松掩码”对检测任务好使漏检率低如果要精确分割就得拿每位医生的标注单独训练或者做某种投票。我习惯的做法是import numpy as np def majority_vote_malignancy(scores): # 剔除无效评分后再取中位数 valid [s for s in scores if s is not None and s 0] if not valid: return None return int(np.median(valid))注意这里的经验s 0的过滤很关键因为 XML 里未评分的医生节点会把字段留空或置 0直接参与计算会把中位数拉低。3.3 用 python 把 XML 转成 COCO 风格的 JSON如果要跑现成的检测框架把 LIDC 转成 COCO 格式是最省事的互通方式。COCO 的 JSON 核心是images、annotations、categories三个数组转换时把每个结节轮廓做成一个多边形segmentation同时附上bbox和category_id。如果结节在某张切片上只有轮廓点、没有检测框可以用cv2.boundingRect从轮廓点生成import json import cv2 import numpy as np def nodule_to_coco_ann(nodule_id, image_id, contour_points, malignancy): contour np.array(contour_points, dtypenp.int32).reshape(-1, 2) x, y, w, h cv2.boundingRect(contour) flatten contour.flatten().tolist() return { id: nodule_id, image_id: image_id, category_id: 1 if malignancy 4 else 0, bbox: [int(x), int(y), int(w), int(h)], area: int(cv2.contourArea(contour)), segmentation: [flatten], iscrowd: 0, }这里把 malignancy ≥ 4 视为恶性、否则良性是极简的二分类划分。cv2.boundingRect返回的 bbox 是轴对称矩形跟医学上常用的直径标注不同——COCO 评测用的是 IoU而结节检测的评测有时用中心距离或体积重合率注意别把评测口径混了。转换完成后建议做一个自检随机挑几个结节把轮廓画到对应 DICOM 切片上肉眼确认轮廓位置没有错位。这一步虽然土但能发现坐标轴搞反、切片序号错位、DICOM 重采样后坐标没同步改等一大批隐蔽问题。我第一次转完愣是对着一个结节轮廓在天上飘了半小时才反应过来是重采样之后忘同步改坐标了。4. 用 Python 重建三维体并生成 Mask管道可以这样搭4.1 读取 DICOM 系列并重建三维体拿到一个序列的全部切片后重建三维体最好用pydicom配合numpy而不是直接上SimpleITK。原因很简单LIDC 的切片之间不一定严格按 ImagePositionPatient 排序直接用SimpleITK.ReadImage可能得到乱序的体数据。手动排序更可控。import pydicom import numpy as np from pathlib import Path def load_series(slice_paths): slices [] for path in slice_paths: ds pydicom.dcmread(path) slices.append(ds) # 按切片位置排序 slices.sort(keylambda ds: float(ds.ImagePositionPatient[2])) return slices排序键用的是ImagePositionPatient里的 Z 坐标。这是最可靠的排序依据不要用文件名排序文件名在 TCIA 里经常没有规律。读完后把每个切片的像素数组沿 Z 轴堆叠成三维数组def series_to_volume(slices): pixel_data [ds.pixel_array for ds in slices] volume np.stack(pixel_data, axis0) # shape: (Z, H, W) return volume这里有个性能提醒pixel_array返回的是原始像素值单位取决于 DICOM 的RescaleSlope和RescaleIntercept。LIDC 的像素值是原始的 CT 值需要转成亨斯菲尔德单位HU再使用。4.2 把 XML 坐标映射到像素坐标并生成 MaskXML 里的xCoord/yCoord是像素坐标但它是基于原始 DICOM 的。如果你对图像做过裁剪、重采样或归一化缩放坐标必须同步变换。标准的映射顺序是先按 DICOM 的RescaleSlope/Intercept转 HU再做窗宽窗位裁剪或归一化最后才把坐标映射到处理后的图像上。def apply_hu_windowing(volume, slope, intercept, window_width, window_level): volume volume * slope intercept lower window_level - window_width / 2 upper window_level window_width / 2 volume np.clip(volume, lower, upper) return volume参数说明LIDC 常见窗宽窗位组合是窗宽 1500、窗位 -600肺窗。但做检测任务时不建议裁剪到太窄的窗宽因为结节内部的钙化和软组织细节在窄窗下会丢失。我一般用窗宽 15002000、窗位 -600 这个区间做 2D 检测时也见过有人用 400/40纵隔窗但结节边缘对比度会差很多。坐标映射的代码逻辑是这样的scale_x new_width / original_width scale_y new_height / original_height new_x round(x * scale_x) new_y round(y * scale_y)这个映射看着简单但最容易漏的是旋转如果 DICOM 采集时病人躺的方向不是标准的ImageOrientationPatient里的方向余弦会告诉你是否需要翻转。LIDC 的多数序列方向是标准的但不要默认所有序列都标准写代码时加一个判断题orientation ds.ImageOrientationPatient # 如果方向余弦不等于 [1,0,0,0,1,0]就得做翻转或转置翻车场景很典型不做方向判断mask 画出来跟实际解剖位置差 90 度。我在一个模型里踩过这个坑模型训练 loss 降得很好验证时发现结节位置全偏了最后排查到 DICOM 方向矩阵上。4.3 输出 nii.gz 与 mask 的完整流程把 CT 体积和 mask 存成 nii.gz是为了后续直接喂给 nnUNet 或 MONAI 这类框架。nii.gz 的好处是自带 affine 矩阵能记录物理坐标信息转出来之后不用再回头找 DICOM。import nibabel as nib def save_nii(volume, affine, path): nii_img nib.Nifti1Image(volume, affine) nib.save(nii_img, path) # 生成 mask把每个结节的轮廓填充为 1背景为 0 mask np.zeros_like(volume, dtypenp.uint8) for contour_points in nodule_contours: # 将轮廓填充到对应切片 cv2.fillPoly(mask[slice_idx], [contour_points], 1)注意fillPoly是在任意一张二维切片上填充需要先把 Z 轴切片索引算准。XML 里的roi提供了imageSopUid来定位切片最稳的方法是用这个 UID 去 DICOM 里查对应切片号而不是按 Z 坐标排序后数顺序。因为两个序列的 Z 方向可能相反同样物理位置排序出来的索引会不一致。4.4 保存所有中间产物到一个项目目录processed/ ├── LIDC-IDRI-0001/ │ ├── volume.nii.gz │ ├── mask.nii.gz │ ├── nodule_info.csv │ └── metadata.json └── LIDC-IDRI-0002/每个病例的metadata.json记录原始 DICOM 的 slope、intercept、方向矩阵、切片厚度、像素间距等关键参数。这些参数在复现实验时极其重要换台机器重新处理数据时能省掉大量排查时间。5. 避坑与常见问题五个我交过学费的 LIDC 预处理现场5.1 亏一CT 值没转 HU模型直接废掉现象训练时 loss 收敛慢且抖动剧烈模型输出概率始终在 0.5 附近晃。原因pixel_array返回的是设备原始值LIDC 里很多序列的原始值与 HU 不是一一对应关系有的甚至带负值偏移不做slope * pixel intercept转换输入分布就被整体平移了。解决统一在加载阶段先转 HU再进后续流程。我习惯把这一步写成一个全局函数所有数据加载都必须经过它不接受绕过的路径。5.2 亏二把 XML 的坐标当成物理坐标现象mask 画出来后结节的轮廓在图像里整体偏移越靠近图像边缘偏得越多。原因XML 里的坐标是像素索引而 DICOM 的坐标原点在图像左上角但部分序列的PixelSpacing不为 1直接拿像素坐标当毫米坐标去和物理空间对齐就会偏。解决所有坐标运算统一走“先取像素坐标 → 再做缩放/裁剪变换”的流程。如果要用物理坐标必须手动乘PixelSpacing并加上ImagePositionPatient的偏移不要简写成一个乘法。5.3 亏三忽略 DICOM 方向余弦导致 mask 翻转现象验证时发现预测 mask 与原图重叠度很高但左右颠倒。原因ImageOrientationPatient指示了采集方向部分序列不是标准方向直接按数组索引堆叠会得到镜像体。解决读序列时统一读取方向矩阵生成体数据时把非标准方向的维度翻转成标准朝向。一个通用做法是保存时写入 affine后续用 nibabel 加载时自动对齐。5.4 亏四结节直径标注当成半径用现象预处理时把diameter字段当作半径做膨胀结果生成 mask 区域是真实结节的 4 倍大。原因LIDC 的diameter字段是平均直径不是半径。字面含义和直觉相反不仔细看注释很容易写反。解决用到diameter的地方一律除以 2 再参与半径运算。建议在代码注释里写清楚“这是直径”防止几个月后自己回来改代码时再看错一次。5.5 亏五多个序列不区分层厚混在一起重建现象三维重建后体数据在 Z 轴上有明显的“错层”切片之间间隔不一致模型训练报维度错误。原因一个病例下同时有 1.25mm 和 2.5mm 层厚序列时代码把所有.dcm文件全读进来不同层厚的切片堆叠在同一个 volume 里。解决按序列 UID 区分读取处理前先打印序列信息人工确认当前处理的是薄层序列再继续。层厚信息在 DICOM 里是SliceThickness字段读取时一并输出。6. 进阶构建可复现的训练验证划分让实验不翻车6.1 病例级划分与结节级划分的区别做肺结节任务时数据划分必须按病例Patient粒度来切不能按结节粒度切。原因很直观同一个病人的多个结节在影像特征上高度相似如果把同一病人的结节同时分到训练集和验证集模型会“作弊”——它可能通过记住病人特征来间接记住结节特征验证集的指标虚高。我见过有人按结节划分后报告 AUC 0.96换按病例划分后一测只有 0.88差距就是这么来的。import pandas as pd from sklearn.model_selection import GroupKFold df pd.read_csv(nodule_info.csv) # 假设 df 每个结节一行且有 patient_id 字段 gkf GroupKFold(n_splits5) for train_idx, val_idx in gkf.split(df, groupsdf[patient_id]): train_set df.iloc[train_idx] val_set df.iloc[val_idx]GroupKFold的参数要讲清楚groups是病例 ID 列表长度一定要跟df行数一致否则会报索引错误。输出后建议检查一下 val_set 里的patient_id是否与 train_set 完全没有交集这一步用集合运算检查一下一秒搞定但能防住大坑。6.2 用 pandas 维护数据清单防止数据泄漏处理完所有病例后维护一张总清单非常关键。清单至少包含这些列病例 ID、序列 UID、切片数、层厚、像素间距、结节数量、结节最大直径、恶性评分中位数、文件路径、mask 路径。后续做模型训练时直接用这张表避免每次都要重新扫 DICOM 目录。更重要的是这张表能让你在实验复现时快速回溯某个数据是从哪个原始文件来的。data_records [] for patient in patient_list: records build_patient_record(patient) data_records.extend(records) summary_df pd.DataFrame(data_records) summary_df.to_csv(lidc_processed_index.csv, indexFalse)build_patient_record函数返回该病例所有结节的摘要。表里的“结节数量”如果是 0通常表示该病例只有“非结节”non-nodule标注这种病例在构建检测任务时可以作为纯负样本用但别删掉。6.3 校准mask 与图像层面对齐的自动化检查训练前做一次自动对齐校验能省下后面排查 bug 的大量时间。校验逻辑很简单随机抽 N 个病例对每个病例的每个结节做一次坐标反向映射——把 mask 里的非零区域取出来计算它的质心像素坐标再和 XML 原始坐标对比看误差是否在可接受范围内。def validate_alignment(volume, mask, original_coords, tol_px3): # 找 mask 中非零坐标的质心 ys, xs np.where(mask 0) if len(xs) 0: return False centroid_x np.mean(xs) centroid_y np.mean(ys) orig_centroid np.mean(original_coords, axis0) dist np.sqrt((centroid_x - orig_centroid[0])**2 (centroid_y - orig_centroid[1])**2) return dist tol_px这个校验函数把误差容忍度设为 3 像素因为重采样和整数取整会带来小偏差超过这个范围就要停下来找原因。我一般会在处理完一批数据后跑一遍全量校验把失败病例单独列出来人工检查而不是直接删掉。删掉可能把罕见但有效的病例丢掉得不偿失。最后一个技巧把整个预处理管道做成幂等的——同一份输入跑两次输出完全一致。具体做法是把处理后的中间文件如 volume.nii.gz、mask.nii.gz单独存到 processed 目录原始 DICOM 目录不写任何东西。这样即使脚本改了只要 processed 目录存在就不需要重新处理。从那以后我每次处理医学影像数据都强制走一遍这个流程先列序列清单、再做坐标映射、再保存 nii.gz 与元数据、最后跑对齐校验。这套习惯帮我挡住了不少后期训练时的玄学问题。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

同为 8B 梯队:Qwen3.8 与上一代 Qwen3-8B 到底差在哪

同为 8B 梯队:Qwen3.8 与上一代 Qwen3-8B 到底差在哪

同为 8B 梯队:Qwen3.8 与上一代 Qwen3-8B 到底差在哪 【免费下载链接】Qwen3.8-Flash-Next 项目地址: https://ai.gitcode.com/hf_mirrors/Qwen/Qwen3.8-Flash-Next 如果你 2025 年上半年刚把 Qwen3-8B 部署进生产管线,2026 年 8 月这波 Qwen3.8…

2026/10/12 22:55:45 阅读更多 →
Sirchmunk如何实现无向量索引检索?GrepRetriever并行搜索原理揭秘

Sirchmunk如何实现无向量索引检索?GrepRetriever并行搜索原理揭秘

【免费下载链接】sirchmunk 🐿️ Sirchmunk: Raw data to self-evolving intelligence, real-time. 项目地址: https://gitcode.com/gh_mirrors/si/sirchmunk 点击查看 免费下载 Sirchmunk 是一款开源的无索引智能搜索引擎:它完全不需要向量…

2026/10/12 22:55:45 阅读更多 →
C++在边缘计算中的核心实践:从交叉编译到稳定性调优

C++在边缘计算中的核心实践:从交叉编译到稳定性调优

接手过一个智能网关项目:一台体积不大的Linux单板,要同时承担几十路传感器数据的采集、清洗、特征计算、本地缓存和上行上报。刚开始我图开发快,用Python写了一个完整原型,功能很快跑通了。可一旦压力测试,问题就接踵而…

2026/10/12 22:55:45 阅读更多 →

最新新闻

机器学习期末复习题库:核心概念与高频考点全解析

机器学习期末复习题库:核心概念与高频考点全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:36:10 阅读更多 →
灯塔ARL V2.6.2 Docker Compose 部署全指南

灯塔ARL V2.6.2 Docker Compose 部署全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:36:10 阅读更多 →
eNSP校园网六层联动实验:VLAN、LACP、OSPF、NAT与ACL闭环实践

eNSP校园网六层联动实验:VLAN、LACP、OSPF、NAT与ACL闭环实践

简介:本资源是一份面向网络工程初学者与高校实验教学的eNSP综合组网实践指南,聚焦VLAN划分、VLAN间通信、ACL访问控制、DHCP自动分配、OSPF动态路由、NAT地址转换及LACP链路聚合等核心技能,适用于华为设备模拟环境下的实验室组网训练与课程设…

2026/10/13 1:36:10 阅读更多 →
带权并查集入门:从A Bug‘s Life理解关系维护与路径压缩

带权并查集入门:从A Bug‘s Life理解关系维护与路径压缩

刷POJ的并查集题单,有一道题绕不开:A Bugs Life。我第一次做它的时候,用普通并查集硬怼了半天,WA了快十发才反应过来——这题根本不是“把虫子分成几堆”那么简单,它需要给并查集的每条边加上权值,去表示集…

2026/10/13 1:36:10 阅读更多 →
数据库课设工艺卡片系统:数据建模、事务与版本控制实践

数据库课设工艺卡片系统:数据建模、事务与版本控制实践

简介:一份面向数据库课程设计的完整工艺卡片系统项目,适用于需要完成数据库课设、并希望顺利通过老师验收的高校学生。项目覆盖需求分析、E-R概念模型、关系表结构、SQL实现到系统测试的全过程,并包含管理员与普通员工两类用户权限管理的设计…

2026/10/13 1:36:09 阅读更多 →
Python二维码生成实战:从基础到批量处理与Logo嵌入

Python二维码生成实战:从基础到批量处理与Logo嵌入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:35:09 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →