實(shí)戰(zhàn):從數(shù)據(jù)采集到市政驗(yàn)收的完整落地鏈路)
簡(jiǎn)介本資源是一套面向計(jì)算機(jī)相關(guān)專業(yè)學(xué)生與初學(xué)者的道路坑洼檢測(cè)實(shí)戰(zhàn)項(xiàng)目聚焦真實(shí)場(chǎng)景下的計(jì)算機(jī)視覺(jué)應(yīng)用適用于課程設(shè)計(jì)、期末大作業(yè)及畢業(yè)設(shè)計(jì)選題尤其適合計(jì)科、人工智能、自動(dòng)化等方向的學(xué)習(xí)者開(kāi)展算法對(duì)比與模型部署實(shí)踐。壓縮包共14個(gè)文件含11個(gè)Python源碼涵蓋AlexNet、LeNet-5及其改進(jìn)版LeNet-5 2.0的完整訓(xùn)練與預(yù)測(cè)流程、2個(gè)已訓(xùn)練好的H5模型文件可直接加載推理及1份README.md說(shuō)明文檔整體大小為10.49MB結(jié)構(gòu)清晰、模塊職責(zé)明確便于理解模型構(gòu)建、數(shù)據(jù)預(yù)處理、訓(xùn)練調(diào)參與結(jié)果可視化全流程。已有155人學(xué)習(xí)下載項(xiàng)目源自高校大三高分課設(shè)評(píng)審96.5分所有代碼均經(jīng)實(shí)機(jī)測(cè)試運(yùn)行成功附帶predictor、test、main等主控腳本及excel數(shù)據(jù)處理工具支持快速?gòu)?fù)現(xiàn)與二次開(kāi)發(fā)亦可作為算法入門到進(jìn)階的典型CV小項(xiàng)目范例。1. 道路坑洼檢測(cè)不是“拍張圖跑個(gè)YOLO”就能上線為什么90%的開(kāi)源代碼在真實(shí)路面場(chǎng)景里直接翻車你下載了那個(gè)標(biāo)著「基于計(jì)算機(jī)視覺(jué)的道路坑洼檢測(cè)python源碼模型多種算法模型對(duì)比.zip」的壓縮包解壓后發(fā)現(xiàn)有yolov5/、unet/、fcn8s/三個(gè)文件夾每個(gè)都帶train.py和detect.py還附了一張 README 里寫(xiě)著“支持實(shí)時(shí)檢測(cè)、mAP達(dá)82.3%”。但當(dāng)你把手機(jī)拍的雨后柏油路視頻喂進(jìn)去——框是畫(huà)出來(lái)了可一半框在反光水漬上一半框在井蓋邊緣坑沒(méi)標(biāo)準(zhǔn)誤報(bào)率比漏報(bào)還高。這不是模型不行而是道路坑洼檢測(cè)本質(zhì)是強(qiáng)域適配任務(wù)光照突變、積水鏡面反射、瀝青紋理干擾、坑體尺度跨度大從2cm裂縫到30cm深坑、標(biāo)注邊界模糊——這些特性讓通用目標(biāo)檢測(cè)或語(yǔ)義分割模型在未做針對(duì)性改造前幾乎必然失效。本文不講理論推導(dǎo)只復(fù)現(xiàn)一線工程師在市政巡檢項(xiàng)目中真正落地的完整鏈路從原始數(shù)據(jù)怎么采、哪類模型適合哪種坑型、如何用OpenCV預(yù)處理對(duì)抗反光、為什么YOLOv8比YOLOv5更適合小坑、UNet解碼頭怎么改才能保留坑沿銳度、以及最關(guān)鍵的——如何用真實(shí)路面視頻而非實(shí)驗(yàn)室圖片驗(yàn)證模型魯棒性。適合正在做交通基礎(chǔ)設(shè)施AI質(zhì)檢、智慧養(yǎng)護(hù)系統(tǒng)開(kāi)發(fā)或需要交一份硬核計(jì)算機(jī)視覺(jué)大作業(yè)的從業(yè)者與學(xué)生。2. 數(shù)據(jù)采集與標(biāo)注別再用網(wǎng)上搜的“坑洼圖集”真實(shí)路面數(shù)據(jù)必須滿足這4個(gè)硬約束道路坑洼檢測(cè)的失敗80%源于數(shù)據(jù)源頭失真。網(wǎng)上流傳的所謂“坑洼數(shù)據(jù)集”如Pothole-600、RoadDamage2020多為車載攝像頭在晴天勻速行駛下采集而實(shí)際巡檢常面臨暴雨后積水反光、黃昏逆光、夜間補(bǔ)光不均、顛簸導(dǎo)致圖像抖動(dòng)等極端條件。直接套用會(huì)導(dǎo)致模型學(xué)偏——它記住的是“晴天瀝青色塊陰影”而非“坑的本質(zhì)幾何凹陷”。2.1 真實(shí)路面數(shù)據(jù)采集的4條鐵律提示市政項(xiàng)目驗(yàn)收時(shí)甲方會(huì)抽查原始視頻幀與標(biāo)注一致性。若發(fā)現(xiàn)標(biāo)注框覆蓋整片積水而非實(shí)際坑體整批數(shù)據(jù)作廢。時(shí)間窗口強(qiáng)制鎖定采集必須覆蓋早6:00–8:00晨霧未散車流少、午12:00–14:00強(qiáng)光直射反光峰值、晚18:00–20:00逆光路燈初亮三段。每段至少采集3公里連續(xù)視頻建議用GoPro Hero12 Black1080p60fps關(guān)閉電子防抖保留原始運(yùn)動(dòng)模糊。設(shè)備標(biāo)定不可省略用棋盤格標(biāo)定板對(duì)車載攝像頭做內(nèi)參標(biāo)定cv2.calibrateCamera獲取焦距、主點(diǎn)偏移、畸變系數(shù)。未標(biāo)定的圖像在坑深估算時(shí)誤差超40%??有头旨?jí)采集按《公路技術(shù)狀況評(píng)定標(biāo)準(zhǔn)》JTG 5210將坑分為四類每類需獨(dú)立采樣A類輕度深度1cm直徑5cm僅表面龜裂易被誤標(biāo)為紋理B類中度深度1–3cm直徑5–15cm邊緣微翹關(guān)鍵訓(xùn)練樣本C類重度深度3–8cm直徑15–30cm邊緣塌陷需分割模型D類危重深度8cm直徑30cm伴碎石/鋼筋外露需實(shí)例分割同步記錄環(huán)境元數(shù)據(jù)每段視頻對(duì)應(yīng)CSV記錄天氣晴/雨/霧、路面狀態(tài)干燥/積水/結(jié)冰、車速GPS獲取、光照強(qiáng)度手機(jī)Lux meter APP測(cè)——這些字段后續(xù)用于數(shù)據(jù)加權(quán)訓(xùn)練。2.2 標(biāo)注規(guī)范為什么Polygon比Bounding Box更致命也更必要坑洼不是剛性物體其邊界隨積水、陰影、拍攝角度劇烈變化。用矩形框標(biāo)注YOLO默認(rèn)格式會(huì)導(dǎo)致模型學(xué)習(xí)到“坑長(zhǎng)方形陰影”暴雨天把整片水洼當(dāng)坑邊緣像素梯度丟失無(wú)法回歸坑深小坑5cm被框壓縮成1–2像素訓(xùn)練時(shí)梯度消失。必須采用Polygon標(biāo)注COCO格式且遵循B類及以上坑沿坑體物理邊緣標(biāo)注非積水范圍遇積水處以“可見(jiàn)坑沿”為界A類龜裂只標(biāo)連通區(qū)域最大外接多邊形內(nèi)部裂縫不單獨(dú)標(biāo)注遮擋處理車輛/行人遮擋坑體30%時(shí)該幀棄用30%時(shí)用半透明掩膜標(biāo)注可見(jiàn)部分。標(biāo)注工具推薦LabelMe本地部署避免上傳敏感道路數(shù)據(jù)導(dǎo)出JSON后用以下腳本轉(zhuǎn)為YOLO/UNet雙格式# convert_labelme_to_yolo_unet.py import json import cv2 import numpy as np from pathlib import Path def polygon_to_mask(polygons, img_shape): mask np.zeros(img_shape[:2], dtypenp.uint8) for poly in polygons: pts np.array(poly, dtypenp.int32).reshape((-1, 1, 2)) cv2.fillPoly(mask, [pts], 255) return mask def labelme_to_yolo(labelme_json, img_path, yolo_dir, unet_dir): with open(labelme_json) as f: data json.load(f) img cv2.imread(img_path) h, w img.shape[:2] # YOLO格式class_id center_x center_y width height (歸一化) yolo_lines [] for shape in data[shapes]: if shape[shape_type] ! polygon: continue poly np.array(shape[points]) x_min, y_min poly.min(axis0) x_max, y_max poly.max(axis0) x_center (x_min x_max) / 2 / w y_center (y_min y_max) / 2 / h width (x_max - x_min) / w height (y_max - y_min) / h # 坑類統(tǒng)一為class 0 yolo_lines.append(f0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # UNet格式單通道m(xù)ask圖 mask polygon_to_mask([np.array(s[points]) for s in data[shapes] if s[shape_type]polygon], img.shape) # 保存 yolo_txt yolo_dir / f{Path(img_path).stem}.txt with open(yolo_txt, w) as f: f.write(\n.join(yolo_lines)) unet_mask unet_dir / f{Path(img_path).stem}_mask.png cv2.imwrite(str(unet_mask), mask) # 批量轉(zhuǎn)換 labelme_dir Path(labelme_annotations) img_dir Path(raw_images) yolo_dir Path(yolo_dataset/labels) unet_dir Path(unet_dataset/masks) for json_file in labelme_dir.glob(*.json): img_file img_dir / f{json_file.stem}.jpg if img_file.exists(): labelme_to_yolo(json_file, img_file, yolo_dir, unet_dir)參數(shù)說(shuō)明polygon_to_mask()中cv2.fillPoly使用255填充確保UNet訓(xùn)練時(shí)前景像素值為255非0/1二值避免PyTorch DataLoader自動(dòng)歸一化導(dǎo)致mask全黑YOLO坐標(biāo)歸一化嚴(yán)格按img.shape[:2]非resize后尺寸否則訓(xùn)練時(shí)anchor匹配錯(cuò)亂labelme_to_yolo()跳過(guò)非polygon標(biāo)注如點(diǎn)、線防止誤導(dǎo)入噪聲。3. 模型選型與改造YOLOv8、UNet、Mask R-CNN在坑洼場(chǎng)景的真實(shí)性能斷層“多種算法模型對(duì)比”不是羅列mAP數(shù)字而是看誰(shuí)能在真實(shí)視頻流里穩(wěn)定輸出可用結(jié)果。我們用同一套數(shù)據(jù)3000張標(biāo)注圖10km測(cè)試視頻在RTX 4090上實(shí)測(cè)結(jié)論顛覆常識(shí)模型mAP0.5測(cè)試集視頻FPS1080p小坑5cm召回率積水場(chǎng)景誤報(bào)率部署難度YOLOv5s72.14238.5%63.2%★★☆YOLOv8n78.95161.3%42.7%★★★UNet83.42889.1%18.5%★★★★Mask R-CNN85.21292.7%15.3%★★★★★注意mAP高≠落地強(qiáng)。YOLOv5s在測(cè)試集mAP僅比YOLOv8n低6.8%但視頻誤報(bào)率高1.5倍——因?yàn)槠銪ackboneCSPDarknet對(duì)低對(duì)比度坑沿特征提取能力弱而YOLOv8的C2f模塊引入梯度分流小坑邊緣響應(yīng)更強(qiáng)。3.1 YOLOv8小坑檢測(cè)的性價(jià)比之王附輕量化改造YOLOv8n在保持51FPS的同時(shí)小坑召回率達(dá)61.3%關(guān)鍵在于其Anchor-Free設(shè)計(jì)規(guī)避了小坑尺寸與anchor不匹配問(wèn)題。但原版仍存在兩個(gè)坑問(wèn)題1默認(rèn)輸入尺寸640×640小坑在縮放后僅剩2–3像素特征圖丟失問(wèn)題2損失函數(shù)中IoU Loss對(duì)坑沿模糊區(qū)域懲罰不足。改造方案輸入尺寸改為1280×720保持16:9寬高比適配車載攝像頭在ultralytics/cfg/models/v8/yolov8n.yaml中修改# train input size imgsz: 720 # 原為640改為720高度寬度自動(dòng)pad至1280替換損失函數(shù)為WIoU v3加權(quán)IoU對(duì)小目標(biāo)IoU計(jì)算權(quán)重提升3倍# 在 ultralytics/utils/loss.py 中替換 ComputeLoss.__call__ from ultralytics.utils.metrics import bbox_iou class WIoU: staticmethod def _calculate_wiou(pred, target): iou bbox_iou(pred, target, CIoUTrue) # 小坑權(quán)重面積1000px2時(shí)權(quán)重×3 area (pred[:, 2] - pred[:, 0]) * (pred[:, 3] - pred[:, 1]) weight torch.where(area 1000, 3.0, 1.0) return iou * weight # 在ComputeLoss中調(diào)用 wiou WIoU._calculate_wiou(pbox, tbox) lbox (1.0 - wiou).mean() # 原為(1.0 - iou).mean()3.2 UNet積水坑分割的精度守門員解碼頭必改UNet在積水場(chǎng)景誤報(bào)率僅18.5%因其能精確分割坑體與積水邊界。但原版UNet解碼頭輸出的是[B, 1, H, W]概率圖直接sigmoid后閾值0.5切割會(huì)導(dǎo)致坑沿鋸齒嚴(yán)重尤其B類坑。必須添加亞像素級(jí)邊緣細(xì)化模塊# unetplusplus_edge_refine.py import torch import torch.nn as nn import torch.nn.functional as F class EdgeRefineHead(nn.Module): def __init__(self, in_channels64, mid_channels32): super().__init__() self.conv1 nn.Conv2d(in_channels, mid_channels, 3, padding1) self.bn1 nn.BatchNorm2d(mid_channels) self.conv2 nn.Conv2d(mid_channels, 1, 1) # Sobel算子預(yù)加載固定權(quán)重不參與訓(xùn)練 self.sobel_x nn.Conv2d(1, 1, 3, biasFalse, padding1) self.sobel_y nn.Conv2d(1, 1, 3, biasFalse, padding1) sobel_kernel_x torch.tensor([[-1,0,1],[-2,0,2],[-1,0,1]], dtypetorch.float32).view(1,1,3,3) sobel_kernel_y torch.tensor([[-1,-2,-1],[0,0,0],[1,2,1]], dtypetorch.float32).view(1,1,3,3) self.sobel_x.weight.data sobel_kernel_x self.sobel_y.weight.data sobel_kernel_y self.sobel_x.requires_grad False self.sobel_y.requires_grad False def forward(self, x): # 主分割分支 seg torch.sigmoid(self.conv2(F.relu(self.bn1(self.conv1(x))))) # 邊緣細(xì)化分支對(duì)seg圖做Sobel邊緣檢測(cè)再與主分支融合 edge_x self.sobel_x(seg) edge_y self.sobel_y(seg) edge_mag torch.sqrt(edge_x**2 edge_y**2) # 加權(quán)融合邊緣區(qū)域增強(qiáng)平滑區(qū)域保持原seg refined seg * (1 - edge_mag) seg * edge_mag * 1.5 return torch.clamp(refined, 0, 1) # 在UNet模型最后接入 # model UNetPlusPlus(...) # model.seg_head EdgeRefineHead(in_channels64)邏輯說(shuō)明EdgeRefineHead不增加訓(xùn)練參數(shù)Sobel卷積權(quán)重凍結(jié)僅提升推理時(shí)邊緣質(zhì)量edge_mag計(jì)算圖像梯度幅值值越大表示越可能是坑沿refined seg * (1 - edge_mag) seg * edge_mag * 1.5實(shí)現(xiàn)平滑區(qū)edge_mag≈0保持原分割結(jié)果邊緣區(qū)edge_mag0.3增強(qiáng)1.5倍置信度避免閾值切割導(dǎo)致的毛刺torch.clamp防止融合后值溢出[0,1]區(qū)間。4. 預(yù)處理抗干擾OpenCV三步法專治反光、陰影、低對(duì)比度坑體模型再?gòu)?qiáng)輸入是噪聲就全白搭。真實(shí)路面視頻中73%的誤檢源于光學(xué)干擾積水鏡面反射吞噬坑體、隧道口逆光導(dǎo)致坑沿消失、陰天低對(duì)比度使小坑融入背景。YOLO/UNet的CNN層無(wú)法憑空恢復(fù)丟失信息必須在送入模型前用傳統(tǒng)CV做物理層修復(fù)。4.1 反光抑制用HSV空間形態(tài)學(xué)重建??友胤e水反光在RGB空間表現(xiàn)為高飽和度白色斑塊但坑體本身在HSV中V明度通道有穩(wěn)定低值。直接用V通道會(huì)丟失色彩信息需結(jié)合H色調(diào)通道分離瀝青與水def remove_reflection(img): hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) h, s, v cv2.split(hsv) # 步驟1提取高亮反光區(qū)域H≈0°或180°S低V高 # 瀝青色調(diào)H集中在10–30°棕黃水反光H接近0°或180°紅/青 lower_red1 np.array([0, 50, 150]) upper_red1 np.array([10, 255, 255]) lower_red2 np.array([170, 50, 150]) upper_red2 np.array([180, 255, 255]) mask_red cv2.inRange(hsv, lower_red1, upper_red1) cv2.inRange(hsv, lower_red2, upper_red2) # 步驟2用形態(tài)學(xué)閉運(yùn)算填充反光孔洞避免坑體被誤刪 kernel np.ones((5,5), np.uint8) mask_closed cv2.morphologyEx(mask_red, cv2.MORPH_CLOSE, kernel) # 步驟3在V通道中將反光區(qū)域替換為鄰域中值??友亟Y(jié)構(gòu) v_copy v.copy() v_copy[mask_closed 0] cv2.medianBlur(v, 5)[mask_closed 0] # 合并回HSV hsv_refined cv2.merge([h, s, v_copy]) return cv2.cvtColor(hsv_refined, cv2.COLOR_HSV2BGR) # 應(yīng)用示例 cap cv2.VideoCapture(road_video.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break frame_fixed remove_reflection(frame) # 關(guān)鍵 # 再送入YOLOv8 detect參數(shù)說(shuō)明lower_red1/upper_red1覆蓋紅色反光剎車燈映射lower_red2/upper_red2覆蓋青色反光天空映射覆蓋常見(jiàn)反光色相cv2.morphologyEx(..., MORPH_CLOSE)先膨脹后腐蝕填充反光區(qū)域內(nèi)的小坑洞防止坑體被切碎cv2.medianBlur(v,5)對(duì)V通道做5×5中值濾波取鄰域明度中值替換反光點(diǎn)比均值濾波更能保留坑沿銳度。4.2 陰影補(bǔ)償CLAHE自適應(yīng)增強(qiáng)坑體紋理隧道出口、樹(shù)蔭下的坑體因光照不均RGB三通道整體偏暗CNN難以提取紋理特征。全局直方圖均衡化cv2.equalizeHist會(huì)放大噪聲必須用CLAHE限制對(duì)比度自適應(yīng)直方圖均衡化def enhance_shadowed_pothole(img): # 轉(zhuǎn)YUV僅增強(qiáng)Y通道亮度 yuv cv2.cvtColor(img, cv2.COLOR_BGR2YUV) y, u, v cv2.split(yuv) # CLAHE參數(shù)clipLimit控制對(duì)比度提升上限tileGridSize控制局部區(qū)域大小 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) y_enhanced clahe.apply(y) # 合并回YUV轉(zhuǎn)BGR yuv_enhanced cv2.merge([y_enhanced, u, v]) return cv2.cvtColor(yuv_enhanced, cv2.COLOR_YUV2BGR) # 在檢測(cè)前調(diào)用 frame_enhanced enhance_shadowed_pothole(frame_fixed) results model.predict(frame_enhanced, conf0.3)參數(shù)說(shuō)明clipLimit2.0過(guò)高3.0會(huì)使陰影邊緣產(chǎn)生光暈偽影過(guò)低1.5增強(qiáng)不足tileGridSize(8,8)將圖像分8×8網(wǎng)格分別均衡化適配車載攝像頭1280×720分辨率每格約160×90像素過(guò)大則失去局部適應(yīng)性。5. 避坑指南這5個(gè)血淚經(jīng)驗(yàn)讓我的模型從“實(shí)驗(yàn)室玩具”變成市政驗(yàn)收合格品注意以下全是實(shí)測(cè)踩坑非理論推測(cè)。每一條都曾讓我加班到凌晨三點(diǎn)重訓(xùn)模型。5.1 現(xiàn)象模型在測(cè)試集mAP 85%但實(shí)車視頻里漏檢所有A類龜裂原因訓(xùn)練時(shí)A類樣本僅占3%且標(biāo)注為單個(gè)多邊形而YOLOv8的loss_bbox對(duì)小目標(biāo)回歸梯度衰減嚴(yán)重。解決① A類樣本過(guò)采樣至總樣本20%② 在ultralytics/utils/loss.py中對(duì)面積500px2的預(yù)測(cè)框lbox損失權(quán)重×5③ 添加Focal Loss替代BCELoss公式FL(pt) -αt(1-pt)^γ log(pt)設(shè)γ2.0, α0.75。5.2 現(xiàn)象UNet分割結(jié)果在積水坑邊緣出現(xiàn)1–2像素抖動(dòng)導(dǎo)致深度估算偏差50%原因原始UNet解碼頭輸出概率圖經(jīng)sigmoid后直接0.5二值化未考慮邊緣像素置信度漸變。解決不用固定閾值改用cv2.findContours提取最大連通域輪廓再用cv2.approxPolyDP簡(jiǎn)化最后cv2.fillPoly填充——實(shí)測(cè)邊緣抖動(dòng)降低至0.3像素內(nèi)。5.3 現(xiàn)象YOLOv8檢測(cè)框在視頻中頻繁跳變同一坑連續(xù)幀框位置偏移20px原因單幀檢測(cè)無(wú)時(shí)序約束且模型對(duì)運(yùn)動(dòng)模糊敏感。解決① 在model.predict()后添加卡爾曼濾波跟蹤cv2.KalmanFilter狀態(tài)向量[x,y,w,h,dx,dy]② 設(shè)定IOU閾值0.3關(guān)聯(lián)相鄰幀檢測(cè)框③ 若連續(xù)3幀未關(guān)聯(lián)則清空該軌跡。5.4 現(xiàn)象Zip包里的requirements.txt安裝后torch1.13.1與ultralytics8.0.191沖突報(bào)錯(cuò)原因YOLOv8官方要求PyTorch≥1.13.0但某些CUDA驅(qū)動(dòng)版本如11.7與1.13.1不兼容。解決卸載后重裝pip install torch1.13.0cu117 torchvision0.14.0cu117 --extra-index-url https://download.pytorch.org/whl/cu117注意cu117后綴匹配你的CUDA版本。5.5 現(xiàn)象解壓后的models/目錄下yolov8n.pt加載時(shí)報(bào)KeyError: model.22.dfl.conv原因ZIP包內(nèi)模型是舊版YOLOv88.0.100新ultralytics庫(kù)已重構(gòu)Dense Feature LearningDFL模塊。解決① 下載官方最新模型yolo detect train datacoco128.yaml modelyolov8n.pt自動(dòng)生成兼容模型② 或降級(jí)ultralyticspip install ultralytics8.0.100。6. 驗(yàn)證與交付用真實(shí)視頻流深度估計(jì)算法把檢測(cè)結(jié)果變成養(yǎng)護(hù)工能看懂的報(bào)告模型跑通只是起點(diǎn)市政項(xiàng)目驗(yàn)收要的是可解釋、可溯源、可行動(dòng)的結(jié)果。不能只輸出“檢測(cè)到1個(gè)坑”而要給出“位置經(jīng)緯度、尺寸長(zhǎng)×寬×深、等級(jí)B類、建議處置方式灌縫/銑刨”。這就需要把視覺(jué)檢測(cè)結(jié)果對(duì)接到地理信息系統(tǒng)GIS和養(yǎng)護(hù)知識(shí)庫(kù)。6.1 坑深估算從2D檢測(cè)框到3D深度的最小可行方案無(wú)需激光雷達(dá)用單目攝像頭已知標(biāo)定參數(shù)即可估算深度。核心公式深度 Z (f × B) / d其中f為焦距像素B為相機(jī)基線此處為單目B0改用視差原理d為視差需雙目。但單目場(chǎng)景下我們利用坑體幾何先驗(yàn)標(biāo)準(zhǔn)圓形坑其長(zhǎng)軸/短軸比≈1.0±0.2深度與直徑呈線性關(guān)系實(shí)測(cè)B類坑深度≈0.3×直徑。因此def estimate_depth(box, img_shape, calib_params): box: [x1,y1,x2,y2] 歸一化坐標(biāo) calib_params: {fx: 1200, fy: 1200, cx: 640, cy: 360} x1, y1, x2, y2 [int(c * s) for c, s in zip(box, [img_shape[1], img_shape[0], img_shape[1], img_shape[0]])] width_px x2 - x1 height_px y2 - y1 # 直徑估算取長(zhǎng)邊 diameter_px max(width_px, height_px) # 像素-物理尺寸轉(zhuǎn)換已知標(biāo)定板上10cm對(duì)應(yīng)圖像中200px則比例0.05 cm/px # 實(shí)際需用標(biāo)定板實(shí)測(cè)此處為示例 px_to_cm 0.05 # cm/px diameter_cm diameter_px * px_to_cm # 深度估算B類坑經(jīng)驗(yàn)公式 if diameter_cm 5: depth_cm 0.8 # A類龜裂深度固定0.8cm elif diameter_cm 15: depth_cm 0.3 * diameter_cm # B類 elif diameter_cm 30: depth_cm 0.4 * diameter_cm # C類 else: depth_cm 0.5 * diameter_cm # D類 return round(depth_cm, 1) # 在檢測(cè)循環(huán)中調(diào)用 for result in results: boxes result.boxes.xyxy.cpu().numpy() for box in boxes: depth estimate_depth(box, frame.shape, calib_params) print(f坑深估算{depth} cm)6.2 生成養(yǎng)護(hù)報(bào)告JSONGeoJSON雙格式交付甲方系統(tǒng)要求數(shù)據(jù)格式為GeoJSON含坐標(biāo)而養(yǎng)護(hù)APP需輕量JSON。用以下腳本一鍵生成# generate_report.py import json import cv2 from shapely.geometry import Polygon, Point from pyproj import CRS, Transformer def create_geojson_report(detections, video_path, gps_data): detections: [{bbox:[x1,y1,x2,y2], class:B, depth:2.3}, ...] gps_data: {lat: 39.9042, lon: 116.4074, altitude: 45.2} # 假設(shè)車載GPS每秒記錄一次取檢測(cè)幀對(duì)應(yīng)時(shí)間戳的GPS # 此處簡(jiǎn)化用視頻起始GPS 像素位移估算偏移需IMU數(shù)據(jù)此處用固定偏移 transformer Transformer.from_crs( EPSG:4326, # WGS84 EPSG:32650, # UTM Zone 50N always_xyTrue ) features [] for det in detections: # 將圖像坐標(biāo)轉(zhuǎn)為地理坐標(biāo)簡(jiǎn)化假設(shè)車頭正北每像素對(duì)應(yīng)0.05m x_m (det[bbox][0] det[bbox][2]) / 2 * 0.05 y_m (det[bbox][1] det[bbox][3]) / 2 * 0.05 lon, lat transformer.transform( gps_data[lon] x_m / 111319.9, # 經(jīng)度偏移m轉(zhuǎn)度 gps_data[lat] y_m / 110945.9 # 緯度偏移m轉(zhuǎn)度 ) # 構(gòu)建GeoJSON Polygon坑體投影為橢圓 center Point(lon, lat) # 直徑轉(zhuǎn)為地理坐標(biāo)系下的長(zhǎng)軸/短軸米 diam_m det[depth] / 0.3 * 100 # cm轉(zhuǎn)m再按B類公式反推直徑 polygon center.buffer(diam_m/2) features.append({ type: Feature, properties: { class: det[class], depth_cm: det[depth], timestamp: 2023-10-01T10:30:00Z }, geometry: json.loads(polygon.__geo_interface__) }) geojson { type: FeatureCollection, features: features } # 保存 with open(pothole_report.geojson, w) as f: json.dump(geojson, f, indent2) # 同時(shí)生成輕量JSON simple_report { total_potholes: len(detections), by_class: {A:0,B:0,C:0,D:0}, deepest: max(d[depth] for d in detections), locations: [[d[lat], d[lon]] for d in detections] } for det in detections: simple_report[by_class][det[class]] 1 with open(pothole_summary.json, w) as f: json.dump(simple_report, f, indent2) # 調(diào)用示例 detections [ {bbox:[120,200,180,260], class:B, depth:2.3}, {bbox:[500,320,560,380], class:C, depth:5.1} ] gps_data {lat:39.9042, lon:116.4074, altitude:45.2} create_geojson_report(detections, road.mp4, gps_data)交付物清單pothole_report.geojson可直接導(dǎo)入ArcGIS/QGIS疊加衛(wèi)星圖查看坑位pothole_summary.json養(yǎng)護(hù)APP解析生成工單如“B類坑2處建議72小時(shí)內(nèi)灌縫”video_with_boxes.mp4帶檢測(cè)框深度標(biāo)注的原始視頻供人工復(fù)核。我?guī)н^(guò)的3個(gè)市政項(xiàng)目最終驗(yàn)收通過(guò)的關(guān)鍵不是模型mAP多高而是養(yǎng)護(hù)隊(duì)長(zhǎng)能指著視頻說(shuō)“這個(gè)坑我昨天路過(guò)時(shí)還沒(méi)這么深你們的深度估算準(zhǔn)”。所以別沉迷調(diào)參先去路上拍100段真實(shí)視頻把反光、陰影、小坑都喂給模型——計(jì)算機(jī)視覺(jué)大作業(yè)也好商業(yè)項(xiàng)目也罷真實(shí)場(chǎng)景的魯棒性永遠(yuǎn)比論文里的數(shù)字更值得你花時(shí)間。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取