[LAB 11 · 从 RGB 到像素:读出、FCN 与边界](/course/notebooks/9e268706471944e3a59e6f568f9ce9a3.html) # 三类逐像素预测的可执行定义 · 实现代码 [独立阅读](/course/notebook-blocks/1086347139a144a386633c49d3bc189a.html) 按问题继续 前景/背景/mIoU口径、RGB到mask训练、FCN与1×1读出及边界 展开完整实现代码 ``` %%writefile lab11_segmentation.py """LAB11: sampling warm-up, learned pixel readout, and image-to-mask FCN. Run: python lab11_segmentation.py --out lab11-results --epochs 20 Dependencies: torch numpy matplotlib. Self-contained; CPU only. """ """Shared transparent geometry and image construction for LAB10/11; no downloads.""" import json, hashlib, platform, sys, time from pathlib import Path from datetime import datetime, timezone import numpy as np import torch from torch import nn from torch.nn import functional as F import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt from matplotlib.patches import Rectangle def seed_all(seed=7): np.random.seed(seed); torch.manual_seed(seed); torch.set_num_threads(2) def iou_matrix(a,b): if len(a)==0 or len(b)==0: return torch.zeros((len(a),len(b))) lo=torch.maximum(a[:,None,:2],b[None,:,:2]); hi=torch.minimum(a[:,None,2:],b[None,:,2:]) inter=(hi-lo).clamp(min=0).prod(-1) area_a=(a[:,2:]-a[:,:2]).clamp(min=0).prod(-1) area_b=(b[:,2:]-b[:,:2]).clamp(min=0).prod(-1) return inter/(area_a[:,None]+area_b[None,:]-inter).clamp(min=1e-8) def make_scenes(n,seed,size=48,scale=1.0): """Independent seed defines original sample, scale changes only object size. Boxes xyxy normalized; red rectangle=1, blue ellipse=2; zero to three objects. Images are rasterized from latent scene, labels are never a network input. """ rng=np.random.default_rng(seed); xs=[]; ys=[]; masks=[] yy,xx=np.mgrid[:size,:size] for index in range(n): image=rng.uniform(.035,.095,(3,size,size)).astype('float32') num=int(rng.choice([0,1,2,3],p=[.18,.30,.36,.16])) boxes=[]; labels=[]; mask=np.zeros((size,size),np.int64) original=[] for obj in range(num): for attempt in range(60): w,h=rng.integers(9,19,size=2); x=rng.integers(2,size-w-2); y=rng.integers(2,size-h-2) box=np.array([x,y,x+w,y+h],np.float32)/size if not original or iou_matrix(torch.tensor(np.array(original)),torch.tensor(box[None])).max()<.08: break original.append(box); label=int(rng.integers(1,3)); color=rng.uniform(.7,.98) cx,cy=(box[0]+box[2])/2,(box[1]+box[3])/2 sw,sh=(box[2]-box[0])*scale,(box[3]-box[1])*scale x1,y1,x2,y2=np.round(np.array([cx-sw/2,cy-sh/2,cx+sw/2,cy+sh/2])*size).astype(int) x2=max(x2,x1+2); y2=max(y2,y1+2) region=(xx>=x1)&(xx=y1)&(yy=score_threshold: rows.append((float(p['scores'][j]),i,j,p['boxes'][j])) rows.sort(key=lambda r:(-r[0],r[1],r[2])); tp=[]; fp=[] for score,i,j,b in rows: ok=False if len(gt[i]): overlaps=iou_matrix(b[None],gt[i])[0]; best=int(overlaps.argmax()) ok=float(overlaps[best])>=iou_threshold and best not in used[i] if ok:used[i].add(best) tp.append(int(ok));fp.append(int(not ok)) tp=np.cumsum(tp);fp=np.cumsum(fp) recall=tp/max(total,1); precision=tp/np.maximum(tp+fp,1) mr=np.r_[0,recall,1];mp=np.r_[0,precision,0] mp=np.maximum.accumulate(mp[::-1])[::-1];loc=np.where(mr[1:]!=mr[:-1])[0] ap=float(((mr[loc+1]-mr[loc])*mp[loc+1]).sum()) if total else None result[str(cls)]={'AP':ap,'GT':total,'TP':int(tp[-1]) if len(tp) else 0,'FP':int(fp[-1]) if len(fp) else 0} all_tp+=result[str(cls)]['TP'];all_fp+=result[str(cls)]['FP'];all_gt+=total return {'per_class':result,'mAP':float(np.mean([r['AP'] for r in result.values() if r['AP'] is not None])), 'precision':all_tp/max(all_tp+all_fp,1),'recall':all_tp/max(all_gt,1),'iou_threshold':iou_threshold,'score_threshold':score_threshold} def provenance(path,started): p=Path(path) return {'utc_started':started,'utc_completed':datetime.now(timezone.utc).isoformat(),'execution':'CPU, complete script', 'python':sys.version,'torch':torch.__version__,'numpy':np.__version__,'platform':platform.platform(),'threads':torch.get_num_threads(), 'code_sha256':hashlib.sha256(p.read_bytes()).hexdigest(), 'device':'CPU', 'source_file':p.name} def save_json(path,data):Path(path).write_text(json.dumps(data,indent=2,ensure_ascii=False)) def segmentation_metrics(pred,target): valid=target!=255; t=target[valid];p=pred[valid] cm=torch.bincount(t*3+p,minlength=9).reshape(3,3).double() intersection=cm.diag();union=cm.sum(0)+cm.sum(1)-intersection iou=intersection/union.clamp(min=1) return {'IoU_background':float(iou[0]),'IoU_red_rectangle':float(iou[1]),'IoU_blue_ellipse':float(iou[2]), 'mIoU_3_classes':float(iou[union>0].mean()),'pixel_accuracy':float(intersection.sum()/cm.sum()), 'valid_pixels':int(valid.sum()),'ignored_pixels':int((~valid).sum()),'confusion_matrix_true_rows':cm.int().tolist()} import argparse ```