在計算機視覺工程落地中我們常遇到一種現象:模型在驗證集上表現完美,但是一旦部署到生產環境準確率卻莫名下跌。這種“性能衰退”往往不源於模型架構本身而是歸咎於預處理管道的脆弱性。數據類型的隱式轉換、縮放算法的細微差異、或是未被矯正的幾何形變,這些看似微不足道的工程細節往往是系統失效的根源。
相比於盲目調整超參數,建立一套確定性強的預處理流程性價比更高。本文總結了基於 scikit-image 的十個工程化模式,旨在幫助開發者消除輸入數據的不確定性將雜亂的原始圖像轉化為對模型真正友好的高質量張量。
1、統一數據類型(dtype)
scikit-image 中的大多數濾波器都默認輸入是
[0, 1]
範圍內的浮點數。在工程實現上最好選定一種內部 dtype,並在數據進入管道的邊界處完成轉換,而不是在中間環節反覆橫跳。
import numpy as np
from skimage import img_as_float32, io
def load_and_normalize(path: str) -> np.ndarray:
img = io.imread(path) # could be uint8/uint16/RGBA
img = img_as_float32(img) # -> float32 in [0,1]
return img[..., :3] if img.shape[-1] == 4 else img # drop alpha if present
這種做法能最大限度減少意外(比如數據被靜默截斷),保證跨機器行為的確定性,調試起來也更省心。
2、顯式指定顏色空間與通道軸
注意庫版本的 API 變動,很多 API 已經從
multichannel=
切換到了
channel_axis
。另外,必須明確模型到底需要灰度圖還是 RGB。
from skimage.color import rgb2gray
def to_gray(img: np.ndarray) -> np.ndarray:
# img: float32 [0,1], shape (H,W,3)
g = rgb2gray(img) # returns (H,W) float in [0,1]
return g
如果保留 3 通道,儘量優先使用 RGB 順序並在文檔中寫死。調用濾波器時記得傳入
channel_axis=-1
以便算法正確感知顏色維度。
3、縮放必須抗鋸齒(Anti-aliasing)並統一幾何策略
不帶抗鋸齒的下采樣簡直是災難,不僅會引入摩爾紋還會導致邊緣信息丟失。
from skimage.transform import resize
def resize_safe(img: np.ndarray, size=(224, 224)) -> np.ndarray:
return resize(
img, size + ((img.shape[-1],) if img.ndim == 3 else ()),
anti_aliasing=True, preserve_range=False
).astype("float32")
在生產環境中,寬高比策略的一致性比算法的巧妙更重要。如果你決定用中心填充(center-pad)那就全鏈路都用;如果選了留白(letterbox)就一直到底。
4、關鍵區域使用自適應對比度(CLAHE)
全局直方圖均衡化往往用力過猛容易讓圖像“過曝”。CLAHE(限制對比度自適應直方圖均衡化)則好得多它能在不破壞高光的前提下提取局部細節。
from skimage import exposure
def local_contrast(img_gray: np.ndarray) -> np.ndarray:
# img_gray: (H,W) float in [0,1]
return exposure.equalize_adapthist(img_gray, clip_limit=0.02)
這招在處理文檔、醫學影像或照明昏暗的場景時特別管用,但如果場景本身對比度已經很高就別用了,否則只是在徒增噪聲。
5、去噪要選對先驗知識
噪聲類型千差萬別沒有萬能的方案,這裏有三個實用的默認方案:
from skimage.restoration import denoise_bilateral, denoise_tv_chambolle, estimate_sigma
def denoise(img_gray: np.ndarray, mode="tv") -> np.ndarray:
if mode == "bilateral":
return denoise_bilateral(img_gray, sigma_color=0.05, sigma_spatial=3)
if mode == "tv": # edges preserved, good for text/edges
return denoise_tv_chambolle(img_gray, weight=0.1)
if mode == "auto":
sig = estimate_sigma(img_gray, channel_axis=None)
w = min(0.2, max(0.05, sig * 2))
return denoise_tv_chambolle(img_gray, weight=w)
raise ValueError("unknown mode")
去噪更像是一個需要根據攝像頭模組或場景特性單獨調節的旋鈕,而不是一個全局通用的常量。
6、識別前的去偏斜
對於 OCR 和條形碼模型來説微小的旋轉都是致命的,所以可以利用圖像矩或霍夫變換(Hough lines)估計傾斜角,然後進行矯正。
import numpy as np
from skimage.transform import rotate
from skimage.filters import sobel
from skimage.feature import canny
from skimage.transform import hough_line, hough_line_peaks
def deskew(img_gray: np.ndarray) -> np.ndarray:
edges = canny(img_gray, sigma=2.0)
hspace, angles, dists = hough_line(edges)
_, angles_peaks, _ = hough_line_peaks(hspace, angles, dists, num_peaks=5)
if len(angles_peaks):
# Convert from radians around vertical to degrees
angle = np.rad2deg(np.median(angles_peaks) - np.pi/2)
return rotate(img_gray, angle=angle, mode="edge", preserve_range=True)
return img_gray
哪怕只是修正 1-2 度文本識別的準確率往往也能上一個台階。
7、去除不均勻背景(Rolling Ball 或形態學開運算)
遇到光照不均可以試着減去一個平滑後的背景層。
import numpy as np
from skimage.morphology import white_tophat, disk
def remove_background(img_gray: np.ndarray, radius=30) -> np.ndarray:
# white_tophat = image - opening(image)
return white_tophat(img_gray, footprint=disk(radius))
在處理收據小票、顯微鏡玻片或者白底產品圖時這個技巧非常有用。
8、智能二值化
全局 Otsu 算法作為理論的標準答案沒問題,但在有陰影或光照漸變的實際場景中局部(Local) 閾值方法往往表現更好。
fromskimage.filtersimportthreshold_local, threshold_otsu
defbinarize(img_gray: np.ndarray, method="local") ->np.ndarray:
ifmethod=="otsu":
t=threshold_otsu(img_gray)
return (img_gray>t).astype("uint8") # {0,1}
# local "window" around each pixel
T=threshold_local(img_gray, block_size=35, offset=0.01)
return (img_gray>T).astype("uint8")
二值化之後還可以配合形態學操作清理噪點。
9、形態學操作:清理、連接與測量
這一步的目的是去除孤立噪點、連接斷裂的筆畫,並保留有意義的區塊(Blobs)。
from skimage.morphology import remove_small_objects, remove_small_holes, closing, square
from skimage.measure import label, regionprops
def clean_and_props(mask: np.ndarray, area_min=64) -> list:
mask = closing(mask.astype(bool), square(3))
mask = remove_small_objects(mask, area_min)
mask = remove_small_holes(mask, area_min)
lbl = label(mask)
return list(regionprops(lbl))
一旦Mask變得乾淨,後續的對象級推理,比如數藥片、定位 Logo、測量缺陷尺寸就變得非常簡單了。
10、透視與幾何歸一化(讓輸入可比)
對於文檔或平面物體,在提取特徵前先做視點歸一化很有必要。
import numpy as np
from skimage.transform import ProjectiveTransform, warp
def four_point_warp(img: np.ndarray, src_pts: np.ndarray, dst_size=(800, 1100)) -> np.ndarray:
# src_pts: 4x2 float32 (tl, tr, br, bl) in image coordinates
w, h = dst_size
dst = np.array([[0,0],[w-1,0],[w-1,h-1],[0,h-1]], dtype=np.float32)
tform = ProjectiveTransform()
tform.estimate(dst, src_pts)
out = warp(img, tform, output_shape=(h, w), preserve_range=True)
return out.astype("float32")
不過要注意,如果你依賴模型或啓發式算法來檢測角點,必須記錄成功/失敗的監控指標,因為一旦 Warp 算錯了後果很嚴重。
總結
預處理是計算機視覺從“學術算法”走向“工程”的分水嶺。使用 scikit-image只要選對了模式,就能兼顧速度、清晰度和控制力。建議從簡單的做起:統一 dtype,帶抗鋸齒的 Resize,加上自適應對比度。然後再根據需求疊加去偏斜、背景去除和形態學操作,你會發現模型似乎變“聰明”了,其實模型沒變只是輸入的數據終於變得講道理了。
https://avoid.overfit.cn/post/f9c16dc30adc4a52926b2831a9252d30
作者:Nexumo