戰(zhàn):從大圖到有效特征的關(guān)鍵步驟)
說句實(shí)在話圖像類項(xiàng)目的成敗往往不是死在模型結(jié)構(gòu)上而是死在這些最基礎(chǔ)的數(shù)據(jù)環(huán)節(jié)上。這第10篇我終于把主題從表格數(shù)據(jù)轉(zhuǎn)到圖像來了。今天要聊的“圖像切割與特征提取”屬于數(shù)據(jù)預(yù)處理和特征工程里最容易翻車、也最值得摳細(xì)節(jié)的板塊。很多人拿到的圖像不是網(wǎng)上現(xiàn)成的小尺寸圖庫而是相機(jī)采集的高分辨率大圖——工業(yè)零件、遙感影像、醫(yī)學(xué)切片、街景監(jiān)控幾千萬像素是常態(tài)。這時(shí)候你要是把原圖直接喂給模型顯存第一個(gè)不同意你要是暴力縮放成224×224那些細(xì)小目標(biāo)和關(guān)鍵紋理基本就廢了。所以怎么做切割、切完之后怎么提取特征是每個(gè)視覺項(xiàng)目都躲不開的第一道坎。這篇文章適合兩類人一類是剛接觸計(jì)算機(jī)視覺、想知道圖像數(shù)據(jù)該怎么下手的初學(xué)者另一類是模型已經(jīng)能跑通、但準(zhǔn)確率和魯棒性一直上不去的工程師。我會(huì)結(jié)合實(shí)際項(xiàng)目經(jīng)驗(yàn)把切割策略、特征設(shè)計(jì)、常見坑一次講透。1. 圖像切割為什么要?jiǎng)拥对趺磩?dòng)刀才不傷數(shù)據(jù)1.1 切割在圖像項(xiàng)目里的三個(gè)真實(shí)價(jià)值你可能會(huì)想切圖不就是把大圖裁小嗎有什么好講的。實(shí)際項(xiàng)目里這一步的核心價(jià)值至少有三個(gè)而且每個(gè)都直接關(guān)系到你的模型能不能訓(xùn)練起來、能不能訓(xùn)好。第一個(gè)價(jià)值是適配算力。工業(yè)相機(jī)的圖像動(dòng)不動(dòng)就是6000×4000像素一張單通道灰度圖按uint8算就要24MB左右三通道RGB直接翻三倍。你以batch size 32往里塞光是輸入數(shù)據(jù)就是幾個(gè)GBGPU顯存直接爆炸。就算你勉強(qiáng)塞進(jìn)去了訓(xùn)練時(shí)的數(shù)據(jù)讀取和預(yù)處理也會(huì)變成瓶頸GPU吃不滿整個(gè)訓(xùn)練流程慢得像蝸牛。切塊之后比如切成512×512一張大圖變成幾十個(gè)小塊顯存壓力瞬間緩解。第二個(gè)價(jià)值是保留小目標(biāo)信息。這個(gè)更致命。很多任務(wù)里關(guān)鍵目標(biāo)在整幅圖中只占很小一塊面積。比如一張零件圖上有一條幾十像素寬的劃痕你把整張圖縮放到224×224這條劃痕可能就變成了一兩個(gè)像素的模糊噪聲什么特征都提取不出來。切塊相當(dāng)于讓模型用原始分辨率去觀察局部區(qū)域“一整張圖看不清”變成了“每一塊都能看清細(xì)節(jié)”這是切割最本質(zhì)的收益。第三個(gè)價(jià)值是樣本量擴(kuò)充。一個(gè)數(shù)據(jù)集中如果有1000張大圖切成16塊就是16000個(gè)樣本切成49塊就是49000個(gè)樣本。對(duì)深度學(xué)習(xí)或者對(duì)數(shù)據(jù)量敏感的模型來說這等于免費(fèi)多了一批訓(xùn)練數(shù)據(jù)。當(dāng)然樣本量變大不代表樣本獨(dú)立這里面的坑我后面會(huì)專門講。1.2 三種常用切割策略與適用場(chǎng)景我平時(shí)用到的切分方式主要有三種它們不是互斥的很多時(shí)候要組合著用。第一種是固定網(wǎng)格切分。就是把大圖按行列均勻分成固定大小的塊比如2048×2048的圖按512切成4×4共16塊。優(yōu)點(diǎn)是實(shí)現(xiàn)最簡(jiǎn)單、覆蓋完整不會(huì)漏掉任何區(qū)域缺點(diǎn)是如果目標(biāo)恰好落在切分邊界上目標(biāo)會(huì)被硬生生劈成兩半兩塊圖里各留一半特征都不完整。這種切法適合目標(biāo)分布相對(duì)均勻、圖幅尺寸規(guī)整的場(chǎng)景比如把整幅遙感影像分成標(biāo)準(zhǔn)瓦片。第二種是滑動(dòng)窗口切分。設(shè)定窗口大小和滑動(dòng)步長(zhǎng)窗口按步長(zhǎng)在圖上移動(dòng)。關(guān)鍵參數(shù)就是步長(zhǎng)與窗口大小的比值。舉個(gè)例子2048×2048圖像窗口512步長(zhǎng)256那x方向和y方向各能得到7個(gè)起點(diǎn)位置一共49個(gè)patch。相比網(wǎng)格切法的16塊重疊率50%讓樣本量直接翻了3倍多。重疊的意義在于目標(biāo)如果恰好跨在邊界上相鄰窗口會(huì)把它完整包含進(jìn)去至少有一張patch能看清全貌。步長(zhǎng)越小重疊越大目標(biāo)被切碎的概率越低但樣本量膨脹也越厲害訓(xùn)練耗時(shí)同步上漲。第三種是隨機(jī)裁剪。在訓(xùn)練階段從原圖像上隨機(jī)取一個(gè)子區(qū)域配合隨機(jī)翻轉(zhuǎn)、旋轉(zhuǎn)、亮度擾動(dòng)一起用這是深度學(xué)習(xí)中非常常見的數(shù)據(jù)增強(qiáng)手段。它的優(yōu)勢(shì)是每次epoch看到的樣本都在變化模型泛化能力更好缺點(diǎn)是非確定性的覆蓋推理時(shí)需要用全圖或多窗口預(yù)測(cè)來保證不漏檢。三種策略的取舍我整理成了表策略覆蓋完整性樣本量增幅適用場(chǎng)景固定網(wǎng)格切分完整但邊界無保護(hù)適中目標(biāo)分布均勻、圖幅規(guī)整滑動(dòng)窗口切分完整且邊界有重疊保護(hù)較大目標(biāo)大小不定、對(duì)召回要求高隨機(jī)裁剪訓(xùn)練時(shí)不保證完整靈活控制數(shù)據(jù)增強(qiáng)、樣本量充足時(shí)實(shí)際項(xiàng)目里我常用的組合是訓(xùn)練階段用滑動(dòng)窗口切塊重疊率設(shè)在50%上下如果數(shù)據(jù)仍然不夠再疊加隨機(jī)裁剪做增強(qiáng)。1.3 切分時(shí)必須守住的三個(gè)底線切圖看似簡(jiǎn)單但我見過太多項(xiàng)目在這里埋雷后面怎么調(diào)都救不回來。三個(gè)底線必須守住。底線一是標(biāo)簽必須跟著patch走。大圖的標(biāo)簽不能直接復(fù)制給每一塊。比如大圖上有缺陷切成49塊可能只有3塊包含這個(gè)缺陷你把缺陷標(biāo)簽給全部49塊模型直接學(xué)出一堆噪聲。正確做法是切割時(shí)記錄patch在大圖中的坐標(biāo)與標(biāo)注框求交疊交疊比例超過閾值的patch才繼承正樣本標(biāo)簽或者按實(shí)際包含目標(biāo)的比例來標(biāo)。底線二是切分之后立即檢查樣本分布。大圖上目標(biāo)占比可能只有1%切塊后如果不做篩選你的訓(xùn)練集里99%都是純背景patch正負(fù)樣本比例極端不平衡模型會(huì)傾向于把所有東西都預(yù)測(cè)成負(fù)樣本。先統(tǒng)計(jì)一下各類別patch數(shù)量不平就要在特征工程之前處理掉。底線三是按原圖劃分?jǐn)?shù)據(jù)集而不是按patch劃分。同一張大圖切出來的patch之間有很強(qiáng)的相關(guān)性如果隨機(jī)把這些patch分進(jìn)訓(xùn)練集和測(cè)試集模型等于提前見過“同一場(chǎng)景的親戚”驗(yàn)證分?jǐn)?shù)虛高上線后立刻現(xiàn)原形。正確做法是先把大圖按比例分成訓(xùn)練、驗(yàn)證、測(cè)試組再對(duì)組內(nèi)圖像切塊保證同一個(gè)來源的大圖不會(huì)同時(shí)出現(xiàn)在兩個(gè)集合里。2. 特征提取從切割圖里撈什么怎么撈才劃算2.1 不訓(xùn)練網(wǎng)絡(luò)也能用的三組手工特征切完圖之后每個(gè)patch還是一張像素圖直接扔給模型不是不行但如果你要走的是經(jīng)典機(jī)器學(xué)習(xí)路線或者想在深度學(xué)習(xí)之前先建一個(gè)baseline手工特征是最快的路徑。我在做圖像分類、缺陷檢測(cè)項(xiàng)目時(shí)最常撈的是三組特征顏色灰度特征、紋理特征、形狀特征。顏色特征最簡(jiǎn)單的做法是直方圖。要注意的是直接在RGB空間做直方圖對(duì)光照變化非常敏感同一個(gè)物體打光不同RGB值可能差出一大截。更穩(wěn)的做法是把圖像轉(zhuǎn)到HSV或LAB空間對(duì)H通道或ab通道做統(tǒng)計(jì)把色相和亮度剝離開?;叶葓D更簡(jiǎn)單直接統(tǒng)計(jì)灰度直方圖、均值、標(biāo)準(zhǔn)差成本極低信息量卻不小。紋理特征里L(fēng)BP局部二值模式和GLCM灰度共生矩陣是我最常用的兩個(gè)。LBP的基本思路很樸素把一個(gè)像素和它的鄰居比較鄰居值比中心大記1否則記0一圈下來得到一個(gè)二進(jìn)制數(shù)這個(gè)數(shù)字就描述了局部的紋理模式。統(tǒng)計(jì)整張圖里所有模式的出現(xiàn)頻率得到直方圖就是LBP特征。GLCM則統(tǒng)計(jì)兩個(gè)相距一定距離的像素在不同灰度組合下出現(xiàn)的次數(shù)再從這個(gè)矩陣抽象出對(duì)比度、能量、均勻性、相關(guān)性等標(biāo)量。這兩個(gè)特征擅長(zhǎng)抓“表面質(zhì)感”比如金屬拉絲、木頭紋路、布料織法這類人類覺得“手感不同”但顏色相近的區(qū)域恰恰是它們的用武之地。形狀特征的代表是HOG方向梯度直方圖。思路是先把圖像分成小格子在每個(gè)格子里統(tǒng)計(jì)梯度方向的分布再把鄰近格子的直方圖拼接起來。梯度大的地方往往就是邊緣和輪廓HOG等于把“長(zhǎng)了什么樣、輪廓清不清楚”數(shù)值化。早期行人檢測(cè)就是靠HOGSVM撐起來的到今天用在零件輪廓、字符識(shí)別這類場(chǎng)景依然有效。這三組特征不是互斥的實(shí)際項(xiàng)目里我通常把它們拼起來用。特征之間互補(bǔ)性越強(qiáng)分類器的上限越高。2.2 手工特征里最容易被忽視的維度與量綱問題手工特征拼起來有個(gè)直接的麻煩維度爆炸。一個(gè)patch上LBP直方圖256維GLCM五個(gè)統(tǒng)計(jì)量灰度統(tǒng)計(jì)四五項(xiàng)HOG按常見參數(shù)輕松上千維。加在一起一個(gè)patch輕松有幾千維而你的有效樣本可能只有幾千到幾萬。高維度小樣本直接喂線性模型或者SVM過擬合幾乎是必然的。所以流程上我是這么走的。第一步先做低方差過濾那些幾乎所有樣本上取值都差不多的特征列對(duì)區(qū)分類別沒有貢獻(xiàn)直接刪掉。第二步做標(biāo)準(zhǔn)化顏色直方圖的取值可能是幾百GLCM的對(duì)比度可能是零點(diǎn)幾HOG的值可能到幾千這些量綱差異會(huì)讓距離類模型完全被大數(shù)值特征主導(dǎo)。常見做法是z-score標(biāo)準(zhǔn)化把每列變成均值0、方差1。第三步做PCA降維保留95%方差對(duì)應(yīng)的主成分?jǐn)?shù)量把幾千維壓到幾十上百維。這一步在去掉共線性冗余的同時(shí)也讓后續(xù)分類器更穩(wěn)。這里有一個(gè)特別常見的錯(cuò)誤我必須單獨(dú)拎出來說標(biāo)準(zhǔn)化和PCA的擬合只能用訓(xùn)練集驗(yàn)證集和測(cè)試集要復(fù)用訓(xùn)練集擬合好的轉(zhuǎn)換器來transform。有人圖省事把全部數(shù)據(jù)一起fit測(cè)試集的信息就在這一步泄漏進(jìn)了訓(xùn)練過程驗(yàn)證分?jǐn)?shù)虛高沒參考意義。2.3 什么時(shí)候別折騰手工特征直接上深度特征手工特征雖然穩(wěn)定但天花板確實(shí)存在。復(fù)雜的語義信息——比如“這個(gè)缺陷是焊接時(shí)留下的氣孔”還是“是磕碰出來的凹痕”——靠直方圖和梯度很難描述清楚。這種情況下深度特征才是正解。做法也很簡(jiǎn)單拿一個(gè)在ImageNet上預(yù)訓(xùn)練過的CNN模型比如ResNet18或ResNet50把最后用于分類的全連接層去掉只保留前面的卷積特征提取部分。輸入一張224×224的圖輸出一個(gè)幾百維的向量這個(gè)向量就是模型理解的“圖像語義壓縮包”。你不再需要手工設(shè)計(jì)任何特征需要做的只是把每張patch套進(jìn)模型過一遍拿到特征向量存成npy后面該訓(xùn)練什么分類器就訓(xùn)練什么分類器。我在項(xiàng)目里會(huì)先看數(shù)據(jù)量和任務(wù)復(fù)雜度。數(shù)據(jù)量只有幾千張、類別差異又比較表象比如不同顏色的零件手工特征往往更快更穩(wěn)。數(shù)據(jù)量達(dá)到幾萬張、類別差異很抽象比如不同類型的產(chǎn)品缺陷或者場(chǎng)景里有大量光照和角度變化深度特征的優(yōu)勢(shì)就非常明顯了。兩者也不沖突深度特征為主再拼接上灰度均值方差這類基礎(chǔ)統(tǒng)計(jì)特征偶爾會(huì)有驚喜。3. 實(shí)操在一塊表面缺陷檢測(cè)數(shù)據(jù)上走通切割特征提取3.1 案例背景與整體流程我拿一個(gè)做過的表面缺陷檢測(cè)項(xiàng)目舉例細(xì)節(jié)做了脫敏但流程完全真實(shí)。背景是某工廠要對(duì)零件表面做自動(dòng)質(zhì)檢相機(jī)采集到的灰度大圖尺寸為2048×2048標(biāo)簽分三類正常、劃痕、表面污漬。缺陷在整圖中占比很小直接縮小整圖訓(xùn)練不現(xiàn)實(shí)所以整個(gè)流程定為大圖切割生成patch對(duì)每個(gè)patch提取特征用特征矩陣訓(xùn)練分類器完成檢測(cè)。流程拆開就是“讀圖→切塊→特征提取→標(biāo)準(zhǔn)化→降維→分類訓(xùn)練→評(píng)估”看著平平無奇但每一步都在前面講的坑里蹚過一遍。下面直接上代碼。3.2 圖像切割實(shí)現(xiàn)與邊界處理切割我選了滑動(dòng)窗口窗口大小512步長(zhǎng)256重疊率50%。這個(gè)組合怎么定的一是512×512在顯卡上可以開一個(gè)不算小的batch訓(xùn)練效率能接受二是步長(zhǎng)256給了足夠重疊細(xì)小劃痕跨塊被切碎的概率大幅降低?;A(chǔ)切分用OpenCV就能寫import cv2 import numpy as np def split_into_patches(image, patch_size512, stride256): h, w image.shape[:2] patches [] coords [] for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patches.append(image[y:y patch_size, x:x patch_size]) coords.append((x, y)) return patches, coords img cv2.imread(part_surface.png, cv2.IMREAD_GRAYSCALE) patches, coords split_into_patches(img) print(f共生成 {len(patches)} 個(gè)patch) # 2048x2048 圖像窗口512步長(zhǎng)256理論上是 7x7 49 個(gè)這個(gè)寫法在圖像尺寸剛好匹配時(shí)沒問題但真實(shí)場(chǎng)景里圖像尺寸經(jīng)常是2047、2049這種“差一點(diǎn)”的怪?jǐn)?shù)字。for range到不了最后一行/列尾部像素就被丟棄了。我實(shí)際生產(chǎn)里會(huì)換成while循環(huán)保證最后一個(gè)窗口緊貼邊界收尾寧可尾部多一次重疊也不能丟失圖像內(nèi)容。還有一個(gè)細(xì)節(jié)2048×2048、窗口512、步長(zhǎng)256x方向能取到的起始點(diǎn)是0,256,512,768,1024,1280,1536一共7個(gè)位置7×7得到49個(gè)patch。如果你用固定網(wǎng)格512無重疊只能得到16個(gè)patch。重疊切法讓樣本量翻了三倍還多對(duì)于小樣本項(xiàng)目這是一個(gè)非??捎^的增幅。3.3 特征提取與特征矩陣構(gòu)建特征提取我選了LBP直方圖、GLCM統(tǒng)計(jì)特征、灰度與梯度統(tǒng)計(jì)三組。LBP參數(shù)上我直接用24個(gè)采樣點(diǎn)、半徑3uniform模式。這里有個(gè)細(xì)節(jié)要提醒uniform模式下LBP的取值只有0到P1也就是0到25共26個(gè)可能值所以直方圖bins要設(shè)成n_points 2而不是想當(dāng)然的16或256否則高段位的模式會(huì)被直接丟掉。from skimage.feature import local_binary_pattern def lbp_histogram(patch, n_points24, radius3): lbp local_binary_pattern(patch, Pn_points, Rradius, methoduniform) n_bins n_points 2 hist, _ np.histogram(lbp.ravel(), binsn_bins, range(0, n_bins)) hist hist.astype(np.float32) hist / (hist.sum() 1e-6) return histGLCM部分灰度共生矩陣對(duì)灰度級(jí)數(shù)量很敏感直接拿0到255的原始灰度做矩陣矩陣會(huì)非常大而且稀疏統(tǒng)計(jì)量反而失穩(wěn)。我的做法是先量化到32個(gè)灰度級(jí)距離取1個(gè)像素方向取0度、45度、90度、135度四個(gè)方向然后對(duì)每個(gè)方向的統(tǒng)計(jì)量取均值。from skimage.feature import graycomatrix, graycoprops def glcm_features(patch, levels32): patch_q (patch / 256.0 * levels).astype(np.uint8) glcm graycomatrix(patch_q, distances[1], angles[0, np.pi/4, np.pi/2, 3*np.pi/4], levelslevels, symmetricTrue, normedTrue) props [] for prop in [contrast, dissimilarity, homogeneity, energy, correlation]: vals graycoprops(glcm, prop) props.append(vals.mean()) return np.array(props, dtypenp.float32)最后是一組最樸素的灰度與梯度統(tǒng)計(jì)特征?;叶染岛蜆?biāo)準(zhǔn)差捕捉整體明暗Sobel梯度幅值的均值與標(biāo)準(zhǔn)差捕捉邊緣強(qiáng)度。別小看這幾個(gè)數(shù)字它們經(jīng)常能在分類器里貢獻(xiàn)意想不到的區(qū)分度。def gray_stats(patch): mean patch.mean() std patch.std() gx cv2.Sobel(patch, cv2.CV_64F, 1, 0, ksize3) gy cv2.Sobel(patch, cv2.CV_64F, 0, 1, ksize3) grad np.sqrt(gx ** 2 gy ** 2) return np.array([mean, std, grad.mean(), grad.std()], dtypenp.float32)三個(gè)函數(shù)提取的結(jié)果拼成一行特征def build_feature_matrix(patches): rows [] for p in patches: lbp lbp_histogram(p) glcm glcm_features(p) stats gray_stats(p) row np.concatenate([lbp, glcm, stats]) rows.append(row) return np.vstack(rows) X build_feature_matrix(patches) print(特征矩陣形狀:, X.shape) # 26 5 4 35 維這里你可能會(huì)說不是說特征幾千維嗎這個(gè)例子我刻意選了較輕量的特征組合35維對(duì)于小樣本也足夠了。實(shí)戰(zhàn)中如果疊加HOG維度會(huì)明顯變大到時(shí)候就必須加上PCA。3.4 標(biāo)準(zhǔn)化、降維與分類評(píng)估特征矩陣建好后面的處理鏈?zhǔn)菢?biāo)準(zhǔn)三步標(biāo)準(zhǔn)化、PCA、分類器。為了展示信息泄漏的坑代碼里特意把scaler和pca只fit在訓(xùn)練集上from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.svm import LinearSVC from sklearn.model_selection import train_test_split # 假設(shè) X 是特征矩陣y 是對(duì)應(yīng)的patch標(biāo)簽 X_train, X_val, y_train, y_val train_test_split(X, y, test_size0.2, random_state42) scaler StandardScaler().fit(X_train) X_train_s scaler.transform(X_train) X_val_s scaler.transform(X_val) # 關(guān)鍵只準(zhǔn)transform不準(zhǔn)fit pca PCA(n_components0.95).fit(X_train_s) X_train_pca pca.transform(X_train_s) X_val_pca pca.transform(X_val_s) clf LinearSVC(max_iter5000) clf.fit(X_train_pca, y_train) acc clf.score(X_val_pca, y_val) print(f驗(yàn)證集準(zhǔn)確率: {acc:.4f})分類器選了LinearSVC原因是特征維度不高、樣本量有限時(shí)線性SVM在小樣本分類上非常穩(wěn)而且訓(xùn)練速度快。如果你的數(shù)據(jù)量更大、特征和類別關(guān)系更復(fù)雜換成隨機(jī)森林或梯度提升樹也可以但前處理邏輯完全不變。跑完之后我還會(huì)看一眼混淆矩陣特別是劃痕和污漬這兩類是否互相混淆這往往能指引下一步該怎么補(bǔ)特征。4. 圖像特征工程翻車實(shí)錄高頻問題與排查清單4.1 切完片后樣本不平衡反而更嚴(yán)重切塊后最典型的問題就是正負(fù)樣本比例急劇惡化。原始大圖里缺陷面積可能只占1%按512窗口、50%重疊切完含缺陷的patch可能只有幾個(gè)剩下全是背景。模型訓(xùn)練出來永遠(yuǎn)只預(yù)測(cè)“正?!睖?zhǔn)確率看著還有99%——因?yàn)檎n惐旧砭驼?9%一點(diǎn)意義都沒有。我常用的解法有兩路。一路從數(shù)據(jù)層入手對(duì)包含缺陷的patch做擴(kuò)充旋轉(zhuǎn)、翻轉(zhuǎn)、小幅縮放、調(diào)整對(duì)比度都行讓模型在同一個(gè)缺陷上看到更多變化同時(shí)對(duì)純正常的patch做下采樣把比例控制到能接受的范圍。另一路從模型層入手改用帶類別權(quán)重的損失函數(shù)或者簡(jiǎn)單一點(diǎn)在SVM或樹模型里把class_weight設(shè)成balanced。實(shí)踐經(jīng)驗(yàn)是兩個(gè)手段一起用比單用一個(gè)穩(wěn)得多。4.2 目標(biāo)跨塊被切碎模型怎么都認(rèn)不出滑動(dòng)窗口重疊50%之后跨塊問題好了很多但依然存在。劃痕這種細(xì)長(zhǎng)形態(tài)的目標(biāo)可能橫跨四個(gè)patch每個(gè)patch里只有一小段弧線單看哪一塊都像噪聲。我踩過這個(gè)坑后的改進(jìn)方式是訓(xùn)練時(shí)只要該缺陷與當(dāng)前patch有實(shí)質(zhì)性交疊就把這個(gè)patch標(biāo)成正樣本讓模型見過“半個(gè)缺陷”的樣子推理時(shí)用更小的步長(zhǎng)再做一次滑動(dòng)窗口讓同一個(gè)缺陷出現(xiàn)在多個(gè)窗口里最后把多個(gè)窗口的預(yù)測(cè)結(jié)果做投票或平均。這相當(dāng)于把單個(gè)patch的判斷升級(jí)成“多窗口協(xié)同判斷”誤檢率明顯下降。4.3 特征維度太高導(dǎo)致過擬合手工特征最誘惑人的地方就是可以不斷往上堆顏色直方圖加完加紋理紋理加完加關(guān)鍵點(diǎn)特征最后一個(gè)patch上萬維。但維度一旦超過樣本量能支撐的范圍分類器就會(huì)開始背樣本而不是學(xué)規(guī)律訓(xùn)練集分?jǐn)?shù)極高、驗(yàn)證集崩盤。我的排查思路是分三步。第一步看每個(gè)特征列的方差方差接近0的直接刪這種列在樣本間沒有區(qū)分信息。第二步看相關(guān)性把相關(guān)性極高的特征列保留其中之一LBP直方圖的相鄰bin之間有時(shí)高度冗余GLCM的dissimilarity和contrast也常常接近線性相關(guān)。第三步PCA壓到90%到95%的方差保留量。如果你用的是樹模型也可以直接看feature_importance砍掉那些重要性長(zhǎng)期墊底的特征。4.4 測(cè)評(píng)指標(biāo)虛高數(shù)據(jù)泄漏藏在前處理里這個(gè)坑最隱蔽也最坑。前面我反復(fù)強(qiáng)調(diào)Scaler和PCA只能在訓(xùn)練集上fit但更隱蔽的是數(shù)據(jù)集劃分層面的泄漏。假如一張大圖切成49個(gè)patch隨機(jī)按patch劃分訓(xùn)練集和測(cè)試集那么同一張大圖的patch很可能同時(shí)出現(xiàn)在兩邊。這些patch共享同一光照、同一個(gè)背景、甚至同一個(gè)缺陷形態(tài)相當(dāng)于模型在測(cè)試時(shí)遇到了“訓(xùn)練時(shí)見過的相似場(chǎng)景”指標(biāo)自然漂亮。但如果換一批全新的大圖效果立刻打回原形。我的做法是周一上來就按大圖路徑做分組劃分把每張大圖對(duì)應(yīng)的所有patch綁定在一起要么全在訓(xùn)練集要么全在測(cè)試集。有時(shí)候更嚴(yán)格一點(diǎn)按采集批次切分比如前三個(gè)批次做訓(xùn)練、后一個(gè)批次做驗(yàn)證模擬“模型上線后見到新環(huán)境”的真實(shí)場(chǎng)景。4.5 切圖太慢IO卡死數(shù)據(jù)量大時(shí)切圖環(huán)節(jié)很容易被低估。幾千張2048×2048的圖逐張讀取、逐張切塊、再逐張?zhí)崛√卣鲉尉€程跑起來可能要好幾個(gè)小時(shí)GPU基本在空轉(zhuǎn)。我踩過幾次之后改成離線預(yù)處理模式先把所有大圖一次性切成patch存成npy或者小尺寸文件訓(xùn)練時(shí)直接讀已經(jīng)切好、提取好特征的矩陣而不是訓(xùn)練循環(huán)里現(xiàn)切現(xiàn)算。切分和特征提取過程用multiprocessing并行按CPU核心數(shù)切任務(wù)。實(shí)測(cè)下來同樣的數(shù)據(jù)量離線并行預(yù)處理比邊訓(xùn)練邊處理快一個(gè)數(shù)量級(jí)。這里再給一個(gè)與LBP有關(guān)的冷門提示local_binary_pattern的計(jì)算其實(shí)不便宜radius和n_points越大越明顯。如果你有幾萬個(gè)patch先用小參數(shù)如P8、R1跑通整個(gè)流程確認(rèn)有效再上大參數(shù)不然調(diào)試一次要等半天。上面這些坑有些是我一開始就意識(shí)到的有些是上線后被實(shí)際效果打臉才回頭補(bǔ)的。做圖像特征工程最核心的體會(huì)就是每一步都要問一句“這個(gè)操作有沒有讓信息在訓(xùn)練和測(cè)試之間偷偷流動(dòng)”。切塊大小、重疊率、特征組合這三個(gè)東西沒有一勞永逸的黃金參數(shù)。我現(xiàn)在的習(xí)慣是固定一套代碼先在小批量數(shù)據(jù)上把整個(gè)流程串起來用結(jié)果反推參數(shù)怎么調(diào)目標(biāo)很小就縮小窗口目標(biāo)細(xì)長(zhǎng)就加大重疊率特征區(qū)分度不夠就加一組深度向量再比一把。這個(gè)流程跑順之后你手里的數(shù)據(jù)會(huì)從一堆原始像素變成干凈、穩(wěn)定、有區(qū)分度的特征矩陣后續(xù)做什么模型都順手得多。