ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

混凝土预制梁异常裂缝检测—PatchCore实战

混凝土预制梁异常裂缝检测—PatchCore实战 前置必要步骤export HF_ENDPOINThttps://hf-mirror.comAnomalib 启动 PatchCore 时需要用 timm 从 HuggingFace 下载resnet18.a1_in1k的预训练权重。HuggingFace 在国内访问极不稳定需要提前修改镜像路径。第一阶段下载代码并安装依赖在终端执行命令git clone https://github.com/openvinotoolkit/anomalib.git执行完毕会看到一个anomalib文件夹。进入代码目录并安装依赖cd anomalib pip install -e . -i https://pypi.tuna.tsinghua.edu.cn/simple验证安装python -c import anomalib; print(anomalib.__version__)如输出以下内容则表示成功(torch) rootjybiwoceggtnfpfq-snow-69bf86cf9-wngdq:/data/coding/anomalib# python -c import anomalib; print(anomalib.__version__) 2.6.3.dev0第二阶段数据采集和清洗PatchCore 需要一个特定的文件夹结构。用以下命令创建工作目录cd .. mkdir -p data/train/good data/test/{good,crack,honeycomb}1、产线大图切片创建src/big_img_split.py代码如下 等距切片有重叠把 data/big_img/ 里的大图切成 256×256 小图 统一输出到 data/test/good/。 from pathlib import Path import cv2 # 配置区 INPUT_DIR Path(data/big_img) # 大图目录 OUTPUT_DIR Path(data/test/good) # 切片输出目录 TILE_SIZE 512 # 切片尺寸 STRIDE 256 # 滑动步长 TILE_SIZE/250% 重叠 JPEG_QUALITY 95 # 保存质量 # def slice_image(img_path: Path, out_dir: Path, start_idx: int 0): 对单张大图做等距切片返回生成的切片数量和新的起始编号。 img cv2.imread(str(img_path)) if img is None: print(f[SKIP] 读不了: {img_path}) return 0, start_idx h, w img.shape[:2] # 生成所有滑动窗口位置 y_positions list(range(0, h - TILE_SIZE 1, STRIDE)) x_positions list(range(0, w - TILE_SIZE 1, STRIDE)) # 边缘补一块保证 100% 覆盖 if y_positions[-1] TILE_SIZE h: y_positions.append(h - TILE_SIZE) if x_positions[-1] TILE_SIZE w: x_positions.append(w - TILE_SIZE) idx start_idx for y in y_positions: for x in x_positions: tile img[y:yTILE_SIZE, x:xTILE_SIZE] fname f{img_path.stem}_{idx:05d}.jpg cv2.imwrite( str(out_dir / fname), tile, [cv2.IMWRITE_JPEG_QUALITY, JPEG_QUALITY], ) idx 1 n idx - start_idx print(f[OK] {img_path.name}: {w}×{h} → {n} 张切片) return n, idx def main(): OUTPUT_DIR.mkdir(parentsTrue, exist_okTrue) # 支持 jpg/jpeg/png/bmp exts {.jpg, .jpeg, .png, .bmp, .webp} images sorted( p for p in INPUT_DIR.iterdir() if p.suffix.lower() in exts ) if not images: print(f[ERROR] {INPUT_DIR} 里没有图片) return print(f[INFO] 找到 {len(images)} 张大图开始切片...\n) total 0 idx 0 for img_path in images: n, idx slice_image(img_path, OUTPUT_DIR, idx) total n print(f\n[INFO] 全部完成共生成 {total} 张切片 - {OUTPUT_DIR}) if __name__ __main__: main()2、网图爬虫创建爬虫代码实例src/bing_downloader.py代码如下 Bing 图片半自动下载器 用法 python src/bing_downloader.py --keyword concrete surface --save_dir data/train/good --max 100 import argparse import asyncio import hashlib import json import re from pathlib import Path from urllib.parse import quote from playwright.async_api import async_playwright def safe_filename(url: str) - str: return hashlib.md5(url.encode()).hexdigest()[:16] .jpg async def download_image(context, url, save_path, min_size_kb20): try: resp await context.request.get(url, timeout20000) if not resp.ok: return False body await resp.body() if len(body) min_size_kb * 1024: return False # 判断是不是图片避免下到 HTML 错误页 if body[:2] ! b\xff\xd8 and body[:4] ! b\x89PNG: return False save_path.write_bytes(body) print(f ✅ 已保存: {save_path.name} ({len(body) // 1024} KB)) return True except Exception as e: print(f ❌ 下载失败: {url[:80]}... ({type(e).__name__})) return False async def main(): parser argparse.ArgumentParser() parser.add_argument(--keyword, requiredTrue) parser.add_argument(--save_dir, requiredTrue) parser.add_argument(--max, typeint, default100) parser.add_argument(--min_size_kb, typeint, default20) args parser.parse_args() save_dir Path(args.save_dir) save_dir.mkdir(parentsTrue, exist_okTrue) downloaded 0 seen set() async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) context await browser.new_context( viewport{width: 1440, height: 900}, user_agent( Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ), ) page await context.new_page() # Bing 图片搜索 URL search_url fhttps://www.bing.com/images/search?q{quote(args.keyword)}formHDRSC2first1 print(f\n 打开 Bing 图片{search_url}) print(f 保存目录{save_dir}) print(f 目标最多 {args.max} 张\n) await page.goto(search_url, wait_untildomcontentloaded) await asyncio.sleep(2) # 反复下滑加载更多 last_count 0 no_new_rounds 0 while downloaded args.max: # 滚动到底 await page.mouse.wheel(0, 3000) await asyncio.sleep(1.5) # 提取所有 a.iusc 标签的 m 属性里的高清原图 URL items await page.query_selector_all(a.iusc) for item in items: try: m_attr await item.get_attribute(m) if not m_attr: continue meta json.loads(m_attr) url meta.get(murl) if not url or url in seen: continue seen.add(url) save_path save_dir / safe_filename(url) if save_path.exists(): continue ok await download_image(context, url, save_path, args.min_size_kb) if ok: downloaded 1 if downloaded args.max: break except Exception: continue print(f 进度{downloaded}/{args.max}) if downloaded last_count: no_new_rounds 1 else: no_new_rounds 0 last_count downloaded if no_new_rounds 5: print(⚠️ 连续 5 次没有新图可能已经到底了提前结束。) break print(f\n 完成共下载 {downloaded} 张到 {save_dir}) await browser.close() if __name__ __main__: asyncio.run(main())在终端执行以下命令进行切图、爬虫python src/downloader.py python src/big_img_split.py爬虫完成后需人工删除带水印及不相关的图片避免污染数据集。在终端执行以下命令查看图片数量cd /data/coding echo 图片数量统计 for dir in data/train/good data/test/good data/test/crack data/test/honeycomb; do if [ -d $dir ]; then count$(find $dir -type f \( -iname *.jpg -o -iname *.jpeg -o -iname *.png -o -iname *.bmp -o -iname *.webp \) | wc -l) printf %-30s %s\n $dir $count else printf %-30s %s\n $dir (目录不存在) fi done echo 终端会输出如下结果 图片数量统计 data/train/good 168 data/test/good 17 data/test/crack 41 data/test/honeycomb 15 第三阶段训练与迭代用终端写入yaml配置文件代码如下cd configs # 删除旧的 rm -f exp1_normal_200.yaml exp1_normal_500.yaml exp2_defect_hole.yaml # exp1_normal_50.yaml cat exp1_normal_50.yaml EOF experiment_name: exp1_normal_50 data: name: concrete root: ./data normal_dir: train/good abnormal_dir: test/crack normal_test_dir: test/good train_batch_size: 8 eval_batch_size: 8 num_workers: 2 model: name: patchcore backbone: resnet18 layers: [layer2, layer3] coreset_sampling_ratio: 0.1 num_neighbors: 9 trainer: max_epochs: 1 accelerator: gpu devices: 1 EOF # exp1_normal_100.yaml cat exp1_normal_100.yaml EOF experiment_name: exp1_normal_100 data: name: concrete root: ./data normal_dir: train/good abnormal_dir: test/crack normal_test_dir: test/good train_batch_size: 8 eval_batch_size: 8 num_workers: 2 model: name: patchcore backbone: resnet18 layers: [layer2, layer3] coreset_sampling_ratio: 0.1 num_neighbors: 9 trainer: max_epochs: 1 accelerator: gpu devices: 1 EOF # exp2_defect_crack.yaml cat exp2_defect_crack.yaml EOF experiment_name: exp2_defect_crack data: name: concrete root: ./data normal_dir: train/good abnormal_dir: test/crack normal_test_dir: test/good train_batch_size: 8 eval_batch_size: 8 num_workers: 2 model: name: patchcore backbone: resnet18 layers: [layer2, layer3] coreset_sampling_ratio: 0.1 num_neighbors: 9 trainer: max_epochs: 1 accelerator: gpu devices: 1 EOF # exp2_defect_honeycomb.yaml cat exp2_defect_honeycomb.yaml EOF experiment_name: exp2_defect_honeycomb data: name: concrete root: ./data normal_dir: train/good abnormal_dir: test/honeycomb normal_test_dir: test/good train_batch_size: 8 eval_batch_size: 8 num_workers: 2 model: name: patchcore backbone: resnet18 layers: [layer2, layer3] coreset_sampling_ratio: 0.1 num_neighbors: 9 trainer: max_epochs: 1 accelerator: gpu devices: 1 EOF # 验证 echo configs 目录 ls -la echo echo 检查关键字段 grep -E experiment_name|abnormal_dir|accelerator *.yaml终端输出如下则成功 检查关键字段 exp1_normal_100.yaml:experiment_name: exp1_normal_100 exp1_normal_100.yaml: abnormal_dir: test/crack exp1_normal_100.yaml: accelerator: gpu exp1_normal_50.yaml:experiment_name: exp1_normal_50 exp1_normal_50.yaml: abnormal_dir: test/crack exp1_normal_50.yaml: accelerator: gpu exp2_defect_crack.yaml:experiment_name: exp2_defect_crack exp2_defect_crack.yaml: abnormal_dir: test/crack exp2_defect_crack.yaml: accelerator: gpu exp2_defect_honeycomb.yaml:experiment_name: exp2_defect_honeycomb exp2_defect_honeycomb.yaml: abnormal_dir: test/honeycomb exp2_defect_honeycomb.yaml: accelerator: gpu创建run_anomalib.py是运行的关键import argparse import yaml from pathlib import Path # 导入 Anomalib 的核心模块 from anomalib.data import Folder from anomalib.models import Patchcore, Padim from anomalib.engine import Engine # 模型名称映射表 MODEL_MAP { patchcore: Patchcore, padim: Padim, } def load_config(config_path): 读取 yaml 配置文件 with open(config_path, r, encodingutf-8) as f: return yaml.safe_load(f) def main(): parser argparse.ArgumentParser(description运行 Anomalib 实验) parser.add_argument(--config, typestr, requiredTrue, help配置文件的路径) args parser.parse_args() # 1. 读取配置 cfg load_config(args.config) print(f[INFO] 已加载配置{args.config}) print(f[INFO] 实验名称{cfg[experiment_name]}) # 2. 构建数据模块 datamodule Folder( namecfg[data][name], rootcfg[data][root], normal_dircfg[data][normal_dir], abnormal_dircfg[data][abnormal_dir], normal_test_dircfg[data].get(normal_test_dir), train_batch_sizecfg[data].get(train_batch_size, 32), eval_batch_sizecfg[data].get(eval_batch_size, 32), num_workerscfg[data].get(num_workers, 4), ) # 3. 构建模型 model_cls MODEL_MAP[cfg[model][name].lower()] model model_cls( backbonecfg[model].get(backbone, resnet18), layerscfg[model].get(layers, [layer2, layer3]), coreset_sampling_ratiocfg[model].get(coreset_sampling_ratio, 0.1), num_neighborscfg[model].get(num_neighbors, 9), ) # 4. 构建 Engine engine Engine( max_epochscfg[trainer].get(max_epochs, 1), acceleratorauto, devices1, default_root_dirfresults/{cfg[experiment_name]}, ) # 5. 训练PatchCore 无参数只是建立记忆库 print([INFO] 开始训练建立记忆库...) engine.fit(modelmodel, datamoduledatamodule) # 6. 测试 print([INFO] 开始测试...) engine.test(modelmodel, datamoduledatamodule) print(f[INFO] 实验完成结果保存在 results/{cfg[experiment_name]} 目录下) if __name__ __main__: main()一切就绪后可以开启首轮冒烟测试cd .. #回到项目根目录 export HF_ENDPOINThttps://hf-mirror.com python src/run_anomalib.py --config configs/exp1_normal_50.yaml完成后终端输出如下则成功┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Test metric ┃ DataLoader 0 ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ image_AUROC │ 0.9583333134651184 │ │ image_F1Score │ 0.8421052694320679 │ └───────────────────────────┴───────────────────────────┘依次执行剩下的命令python src/run_anomalib.py --config configs/exp2_defect_honeycomb.yaml python src/run_anomalib.py --config configs/exp2_defect_crack.yaml python src/run_anomalib.py --config configs/exp1_normal_100.yaml第四阶段评估与结论全部运行完毕后会看到各版本的指标择最高分做推理模型。Experiment,image_AUROC,image_F1Score,metrics_file exp1_normal_100,0.9978070259094238,0.991304337978363,results/exp1_normal_100/metrics.json exp1_normal_50,0.778508722782135,0.9661017060279846,results/exp1_normal_50/metrics.json exp2_defect_crack,0.9824561476707458,0.9357798099517822,results/exp2_defect_crack/metrics.json exp2_defect_honeycomb,0.9407894611358643,0.9047619104385376,results/exp2_defect_honeycomb/metrics.json推理完成后程序会输出如下图片。至此项目完成整个框架如下Concrete_PatchCore_Project/ │ ├── README.md # 写清楚项目背景、如何运行、实验结论 ├── requirements.txt # 记录所有依赖库版本云端 pip install -r 就能配好 ├── run_all_experiments.sh # 核心一键跑完所有实验的 Shell 脚本 │ ├── configs/ # 存放所有实验配置文件yaml格式 │ ├── exp1_normal_50.yaml │ ├── exp1_normal_100.yaml │ ├── exp1_normal_200.yaml │ ├── exp1_normal_500.yaml │ ├── exp2_defect_crack.yaml │ ├── exp2_defect_hole.yaml │ ├── exp2_defect_honeycomb.yaml │ └── exp3_model_padim.yaml │ ├── data/ # ⚠️ 本地存放数据上传时压缩这个文件夹 │ ├── train/ │ │ └── good/ # 存放所有正常混凝土图片比如 1000 张 │ │ │ └── test/ │ ├── good/ # 测试集中的正常样本 │ ├── crack/ # 测试集裂缝缺陷 │ ├── honeycomb/ # 测试集蜂窝麻面缺陷 │ ├── src/ # 存放你的核心 Python 代码 │ ├── dataset_builder.py # 负责按实验要求从全量数据中抽样生成对应子集 │ ├── run_anomalib.py # 封装调用 anomalib 的脚本代替命令行 │ └── evaluate.py # 统一读取结果生成表格AUROC/F1等 │ └── results/ # 跑完实验后云端自动生成的文件夹不用本地建 ├── exp1_normal_50/ ├── exp1_normal_100/ └── ...
返回列表