與去除實(shí)戰(zhàn):從OpenCV閾值法到Retinex與U-Net)
簡(jiǎn)介這份資源面向數(shù)字圖像處理學(xué)習(xí)者、課程設(shè)計(jì)選題學(xué)生及需要處理陰影干擾的開(kāi)發(fā)者聚焦陰影檢測(cè)與去除這一經(jīng)典難題。陰影普遍存在于自然圖像中會(huì)對(duì)特征提取、圖像識(shí)別與分割造成干擾資源通過(guò)Python實(shí)現(xiàn)從陰影檢測(cè)到去除的完整流程輸入一張圖片即可判斷是否存在陰影并嘗試消除。壓縮包共16個(gè)文件約10.13MB包含Python主程序與模塊、效果對(duì)比圖片、實(shí)驗(yàn)報(bào)告文檔、演示幻燈片及說(shuō)明文件覆蓋代碼、數(shù)據(jù)、報(bào)告與展示材料便于理解算法思路與實(shí)驗(yàn)結(jié)論。目前已有435人學(xué)習(xí)下載適合作為課程設(shè)計(jì)參考或圖像處理入門(mén)實(shí)踐。讀者可從中獲得可運(yùn)行的陰影檢測(cè)與去除腳本、SBU數(shù)據(jù)集相關(guān)實(shí)驗(yàn)素材、效果對(duì)比圖與報(bào)告撰寫(xiě)思路并借助幻燈片快速梳理方法框架為后續(xù)圖像預(yù)處理與視覺(jué)任務(wù)提供可復(fù)用的排錯(cuò)與調(diào)參參考。1. 陰影檢測(cè)與去除為什么你的 OpenCV 閾值法一換場(chǎng)景就翻車做過(guò)數(shù)字圖像處理的人都遇到過(guò)這種場(chǎng)景一段戶外監(jiān)控視頻陽(yáng)光把電線桿的影子投在路面上你想把陰影去掉還原路面紋理于是用 HSV 空間卡了個(gè) V 通道閾值實(shí)驗(yàn)室里效果不錯(cuò)換到另一段視頻陰影沒(méi)去掉路面反光倒被當(dāng)成陰影抹掉了。陰影檢測(cè)與去除這件事難就難在陰影和暗色物體在像素層面幾乎無(wú)法區(qū)分——兩者都暗但一個(gè)是光照被遮擋一個(gè)是本身反射率低。這個(gè)方向在智能交通、遙感影像、視頻監(jiān)控、自動(dòng)駕駛感知里都有實(shí)際需求。基于 Python 實(shí)現(xiàn)的好處是生態(tài)成熟OpenCV 做底層像素操作NumPy 做矩陣運(yùn)算scikit-image 做形態(tài)學(xué)和區(qū)域分析整套鏈路不需要編譯 C 就能跑通。本文面向的是有 Python 基礎(chǔ)、想把這個(gè)方向真正落地到項(xiàng)目里的從業(yè)者從物理模型講到可復(fù)現(xiàn)的代碼再到參數(shù)怎么調(diào)、坑在哪。讀完你應(yīng)該能自己搭一條從陰影檢測(cè)到去除的完整流水線并且知道什么場(chǎng)景下這套方法會(huì)失效。2. 陰影的物理模型與檢測(cè)路線選型從光照反射到可計(jì)算特征2.1 陰影在像素上到底長(zhǎng)什么樣要檢測(cè)陰影先得理解陰影是怎么形成的。光照模型里傳感器接收到的像素值可以近似寫(xiě)成I(x, y) R(x, y) × L(x, y)其中 R 是物體表面的反射率L 是到達(dá)該點(diǎn)的光照強(qiáng)度。陰影區(qū)域的特點(diǎn)是 L 被遮擋物降低了但 R 沒(méi)變。暗色物體則是 R 本身就低。這就是為什么單純看亮度無(wú)法區(qū)分二者——你需要找到「R 沒(méi)變但 L 變了」的證據(jù)。實(shí)際可用的證據(jù)有幾類。第一類是色度不變性陰影通常不改變色相只是降低亮度和飽和度而暗色物體往往有自己獨(dú)立的色相分布。第二類是紋理一致性陰影覆蓋區(qū)域的紋理梯度和周圍非陰影區(qū)域應(yīng)該相似因?yàn)榉瓷渎蕸](méi)變。第三類是邊界特征陰影邊界通常有較明顯的亮度躍變且邊界兩側(cè)色度接近。第四類是幾何先驗(yàn)如果知道光源方向陰影方向應(yīng)該和光源一致。工程上不會(huì)只用一種特征常見(jiàn)做法是把色度、紋理、亮度三類特征組合成一個(gè)特征向量再用分類器或規(guī)則判斷。但組合特征會(huì)帶來(lái)維度災(zāi)難和調(diào)參困難所以選型時(shí)要根據(jù)場(chǎng)景取舍。2.2 三條主流技術(shù)路線的適用邊界目前基于 Python 能落地的陰影檢測(cè)路線大致三條路線一基于色彩空間的閾值法。在 HSV 或 Lab 空間設(shè)定亮度比和色度差閾值。優(yōu)點(diǎn)是快單幀處理在毫秒級(jí)缺點(diǎn)是對(duì)光照變化極其敏感閾值換場(chǎng)景就得重調(diào)。適合光照穩(wěn)定、場(chǎng)景固定的工業(yè)檢測(cè)。路線二基于紋理和區(qū)域的方法。用局部二值模式LBP或 Gabor 濾波提取紋理特征再結(jié)合區(qū)域生長(zhǎng)或圖割做陰影區(qū)域分割。優(yōu)點(diǎn)是魯棒性比閾值法好能處理一定程度的暗色物體干擾缺點(diǎn)是計(jì)算量大參數(shù)多實(shí)時(shí)性差。適合離線處理或?qū)纫蟾叩倪b感影像。路線三基于深度學(xué)習(xí)的方法。用 U-Net 或 Mask R-CNN 做陰影分割再用 GAN 或 Retinex 做去除。優(yōu)點(diǎn)是泛化能力強(qiáng)缺點(diǎn)是需要標(biāo)注數(shù)據(jù)訓(xùn)練成本高部署時(shí)對(duì)硬件有要求。適合有數(shù)據(jù)積累、追求端到端效果的團(tuán)隊(duì)。我一般會(huì)建議如果場(chǎng)景固定、實(shí)時(shí)性要求高先走路線一把參數(shù)調(diào)透如果場(chǎng)景多變但算力有限走路線二用 LBP 加形態(tài)學(xué)后處理如果有標(biāo)注數(shù)據(jù)且能接受 GPU 推理直接上路線三。下面兩章分別展開(kāi)路線一和路線二的實(shí)現(xiàn)路線三在最后一章講進(jìn)階思路。2.3 環(huán)境準(zhǔn)備與最小可跑通示例先把環(huán)境搭起來(lái)。Python 3.8 以上裝 OpenCV、NumPy、scikit-image、matplotlibpip install opencv-python numpy scikit-image matplotlib如果你用 VSCode 或 PyCharm配好解釋器后直接跑下面的最小示例。這段代碼讀一張圖轉(zhuǎn) HSV用亮度比和飽和度比做初步陰影掩膜import cv2 import numpy as np def shadow_mask_hsv(img_bgr, brightness_ratio0.6, sat_ratio0.8): 基于 HSV 的陰影初步檢測(cè) brightness_ratio: 陰影區(qū)亮度 / 背景亮度 的上限 sat_ratio: 陰影區(qū)飽和度 / 背景飽和度 的上限 hsv cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV) h, s, v cv2.split(hsv) v v.astype(np.float32) s s.astype(np.float32) # 用大核中值濾波估計(jì)背景亮度和飽和度 v_bg cv2.medianBlur(v.astype(np.uint8), 31).astype(np.float32) s_bg cv2.medianBlur(s.astype(np.uint8), 31).astype(np.float32) # 避免除零 v_bg[v_bg 0] 1 s_bg[s_bg 0] 1 mask_v (v / v_bg) brightness_ratio mask_s (s / s_bg) sat_ratio mask (mask_v mask_s).astype(np.uint8) * 255 # 形態(tài)學(xué)去噪 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) return mask if __name__ __main__: img cv2.imread(test.jpg) mask shadow_mask_hsv(img) cv2.imshow(shadow, mask) cv2.waitKey(0)邏輯說(shuō)明先轉(zhuǎn) HSV 分離亮度和飽和度通道用大核中值濾波估計(jì)「如果沒(méi)有陰影這個(gè)位置應(yīng)該有多亮/多飽和」然后算比值。陰影區(qū)亮度比和飽和度比都會(huì)偏低兩個(gè)條件同時(shí)滿足才判為陰影。參數(shù)說(shuō)明brightness_ratio默認(rèn) 0.6意思是陰影區(qū)亮度不到背景的 60%sat_ratio默認(rèn) 0.8。這兩個(gè)值需要根據(jù)你的數(shù)據(jù)調(diào)后面避坑章節(jié)會(huì)講怎么調(diào)。提示中值濾波的核大小 31 是個(gè)經(jīng)驗(yàn)值圖像分辨率高時(shí)要加大否則背景估計(jì)會(huì)跟著陰影走導(dǎo)致漏檢。3. 從掩膜到去除用 Retinex 和紋理填充把陰影區(qū)域還原3.1 陰影去除的兩條思路光照補(bǔ)償 vs 紋理合成檢測(cè)出陰影掩膜后去除有兩條路。第一條是光照補(bǔ)償認(rèn)為陰影區(qū)只是光照被衰減把光照補(bǔ)回去就行。經(jīng)典方法是 Retinex 分解把圖像拆成反射率和光照兩部分只對(duì)光照做補(bǔ)償。第二條是紋理合成如果陰影區(qū)紋理丟失嚴(yán)重直接從周圍非陰影區(qū)采樣紋理填充類似圖像修復(fù)inpainting。實(shí)際工程里我一般先用 Retinex 做全局補(bǔ)償再用 inpainting 修補(bǔ)殘留的邊界偽影。單用 Retinex 在陰影邊緣容易產(chǎn)生光暈單用 inpainting 在陰影面積大時(shí)會(huì)出現(xiàn)紋理重復(fù)。兩者結(jié)合效果最穩(wěn)。3.2 Retinex 光照補(bǔ)償?shù)?Python 實(shí)現(xiàn)單尺度 Retinex 的核心是對(duì)數(shù)域做減法def retinex_shadow_removal(img_bgr, mask, sigma30): 對(duì)陰影區(qū)域做單尺度 Retinex 光照補(bǔ)償 sigma: 高斯核標(biāo)準(zhǔn)差控制光照估計(jì)的平滑程度 img img_bgr.astype(np.float32) 1.0 # 避免 log(0) # 對(duì)每個(gè)通道分別做 Retinex channels cv2.split(img) result [] for ch in channels: # 高斯模糊估計(jì)光照 blur cv2.GaussianBlur(ch, (0, 0), sigma) blur[blur 0] 1 # 對(duì)數(shù)域相減得到反射率 retinex np.log10(ch) - np.log10(blur) result.append(retinex) retinex_img cv2.merge(result) # 歸一化到 0-255 retinex_img cv2.normalize(retinex_img, None, 0, 255, cv2.NORM_MINMAX) retinex_img retinex_img.astype(np.uint8) # 只在陰影區(qū)域用 Retinex 結(jié)果非陰影區(qū)保留原圖 mask_3ch cv2.merge([mask, mask, mask]) / 255.0 output (retinex_img * mask_3ch img_bgr * (1 - mask_3ch)).astype(np.uint8) return output邏輯說(shuō)明Retinex 假設(shè)圖像 反射率 × 光照取對(duì)數(shù)后變成加法減去高斯模糊估計(jì)的光照就得到反射率。這里對(duì)每個(gè)通道獨(dú)立做最后歸一化。關(guān)鍵在最后一步——只在陰影掩膜內(nèi)替換非陰影區(qū)保持原樣避免整圖色彩偏移。參數(shù)說(shuō)明sigma控制光照估計(jì)的尺度值越大光照估計(jì)越平滑陰影補(bǔ)償越柔和但可能欠補(bǔ)償值越小補(bǔ)償越激進(jìn)但容易在邊緣產(chǎn)生光暈。一般從 30 開(kāi)始試圖像分辨率高時(shí)加到 50-80。3.3 邊界偽影修復(fù)與 inpainting 后處理Retinex 補(bǔ)償后陰影邊界往往還有一圈不自然的過(guò)渡。用 OpenCV 的 inpaint 對(duì)邊界帶做修復(fù)def refine_boundary(img_bgr, mask, dilate_iter2): 對(duì)陰影邊界做 inpainting 修復(fù) dilate_iter: 掩膜膨脹次數(shù)控制修復(fù)帶寬 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) # 膨脹掩膜得到邊界帶 mask_dilated cv2.dilate(mask, kernel, iterationsdilate_iter) # 邊界帶 膨脹后 - 原始 boundary cv2.subtract(mask_dilated, mask) # 用 Telea 算法修復(fù)邊界帶 result cv2.inpaint(img_bgr, boundary, 3, cv2.INPAINT_TELEA) return result邏輯說(shuō)明先把掩膜膨脹幾圈減去原始掩膜就得到邊界帶。用 inpaint 對(duì)邊界帶做修復(fù)Telea 算法適合小區(qū)域快速修復(fù)。參數(shù)說(shuō)明dilate_iter控制修復(fù)帶寬一般 2-3 就夠太大反而會(huì)把陰影內(nèi)部紋理也抹掉。inpaint的第三個(gè)參數(shù) 3 是修復(fù)半徑邊界帶窄時(shí)用 3寬時(shí)加到 5。3.4 完整流水線串起來(lái)把檢測(cè)和去除串成一條流水線def shadow_removal_pipeline(img_path): img cv2.imread(img_path) mask shadow_mask_hsv(img) compensated retinex_shadow_removal(img, mask) result refine_boundary(compensated, mask) return result if __name__ __main__: out shadow_removal_pipeline(test.jpg) cv2.imwrite(result.jpg, out)這條流水線在 1080p 圖像上單幀大約 200-400ms取決于中值濾波核大小和 inpaint 區(qū)域面積。如果要做視頻實(shí)時(shí)處理需要把中值濾波換成積分圖加速或者降分辨率處理后再上采樣。注意Retinex 對(duì)彩色圖像做逐通道處理會(huì)改變色相如果對(duì)色彩保真度要求高應(yīng)該在 Lab 空間只對(duì) L 通道做補(bǔ)償a、b 通道保持不變。4. 避坑與排查陰影檢測(cè)去除中最容易翻車的五個(gè)地方4.1 現(xiàn)象陰影沒(méi)檢出來(lái)暗色物體反而被標(biāo)成陰影原因HSV 閾值法只看亮度和飽和度暗色物體比如黑色汽車、深色路面的亮度和飽和度本來(lái)就低和陰影特征重疊。解決加入色度一致性判斷。陰影區(qū)的色相應(yīng)該和周圍背景接近而暗色物體往往有獨(dú)立色相。在掩膜生成后加一步色相距離過(guò)濾def filter_by_hue(img_bgr, mask, hue_thresh20): hsv cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV) h hsv[:, :, 0].astype(np.float32) # 用大核中值濾波估計(jì)背景色相 h_bg cv2.medianBlur(h.astype(np.uint8), 31).astype(np.float32) # 色相距離環(huán)形距離 diff np.abs(h - h_bg) diff np.minimum(diff, 180 - diff) # 只保留色相接近的區(qū)域 mask cv2.bitwise_and(mask, (diff hue_thresh).astype(np.uint8) * 255) return maskhue_thresh默認(rèn) 20OpenCV 的 H 通道范圍是 0-18020 大約對(duì)應(yīng) 40 度色相角。如果場(chǎng)景里陰影和背景色相差很大比如綠草地上的陰影這個(gè)值要放寬到 30。4.2 現(xiàn)象陰影邊緣有光暈過(guò)渡不自然原因Retinex 的高斯核尺度選擇不當(dāng)。核太小光照估計(jì)跟著陰影邊界走補(bǔ)償后在邊界產(chǎn)生亮環(huán)核太大光照估計(jì)過(guò)于平滑陰影內(nèi)部補(bǔ)償不足。解決用多尺度 RetinexMSR把多個(gè) sigma 的結(jié)果加權(quán)平均def msr_shadow_removal(img_bgr, mask, sigmas[15, 50, 100]): img img_bgr.astype(np.float32) 1.0 channels cv2.split(img) msr_result [] for ch in channels: retinex_sum np.zeros_like(ch) for sigma in sigmas: blur cv2.GaussianBlur(ch, (0, 0), sigma) blur[blur 0] 1 retinex_sum np.log10(ch) - np.log10(blur) msr_result.append(retinex_sum / len(sigmas)) msr_img cv2.merge(msr_result) msr_img cv2.normalize(msr_img, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) mask_3ch cv2.merge([mask, mask, mask]) / 255.0 return (msr_img * mask_3ch img_bgr * (1 - mask_3ch)).astype(np.uint8)三個(gè) sigma 分別覆蓋細(xì)節(jié)、中尺度、大尺度光照加權(quán)平均后光暈明顯減輕。代價(jià)是計(jì)算量翻三倍。4.3 現(xiàn)象視頻處理時(shí)幀間閃爍陰影掩膜跳變?cè)蛑饚?dú)立檢測(cè)沒(méi)有利用時(shí)域信息。相鄰幀之間光照微小變化就會(huì)導(dǎo)致掩膜邊界跳動(dòng)。解決加時(shí)域平滑。對(duì)掩膜做滑動(dòng)窗口平均或者用卡爾曼濾波跟蹤陰影區(qū)域。簡(jiǎn)單做法是維護(hù)一個(gè)掩膜隊(duì)列取最近 N 幀的中值from collections import deque class TemporalMaskSmoother: def __init__(self, window5): self.window window self.buffer deque(maxlenwindow) def update(self, mask): self.buffer.append(mask.astype(np.float32)) if len(self.buffer) self.window: return mask # 取中值 stacked np.stack(self.buffer, axis0) smoothed np.median(stacked, axis0).astype(np.uint8) return smoothedwindow默認(rèn) 5視頻幀率高時(shí)加到 7-9幀率低時(shí)減到 3。中值比平均好因?yàn)橹兄祵?duì)單幀異常不敏感。4.4 現(xiàn)象inpaint 后陰影區(qū)域紋理模糊像糊了一層原因inpaint 算法在區(qū)域較大時(shí)只能做擴(kuò)散填充無(wú)法恢復(fù)真實(shí)紋理。Telea 算法適合小區(qū)域大區(qū)域會(huì)變成一片模糊。解決大陰影區(qū)域不要用 inpaint改用紋理合成。簡(jiǎn)單做法是從陰影周圍非陰影區(qū)隨機(jī)采樣 patch粘貼到陰影區(qū)?;蛘哂?OpenCV 的cv2.inpaint配合cv2.INPAINT_NSNavier-Stokes算法在紋理區(qū)域效果比 Telea 好result cv2.inpaint(img_bgr, boundary, 5, cv2.INPAINT_NS)如果陰影面積超過(guò)圖像 10%建議直接上深度學(xué)習(xí)修復(fù)模型傳統(tǒng)方法很難做好。4.5 現(xiàn)象調(diào)好的參數(shù)換一段視頻就失效原因閾值法對(duì)光照條件敏感不同時(shí)段、不同天氣、不同相機(jī)的圖像亮度分布差異很大。解決做自適應(yīng)閾值。不要寫(xiě)死 0.6而是根據(jù)圖像整體亮度分布動(dòng)態(tài)計(jì)算。比如取圖像亮度直方圖的某個(gè)百分位數(shù)作為基準(zhǔn)def adaptive_brightness_ratio(img_bgr, percentile30): hsv cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV) v hsv[:, :, 2] # 用低百分位數(shù)估計(jì)陰影區(qū)亮度高百分位數(shù)估計(jì)背景亮度 v_low np.percentile(v, percentile) v_high np.percentile(v, 90) if v_high 0: return 0.6 return v_low / v_high這個(gè)比值作為brightness_ratio的初始值再根據(jù)實(shí)際效果微調(diào)。自適應(yīng)方法不能保證所有場(chǎng)景都完美但比固定閾值魯棒得多。5. 進(jìn)階用輕量 U-Net 做陰影分割以及怎么驗(yàn)證效果5.1 為什么傳統(tǒng)方法到頭了要轉(zhuǎn)深度學(xué)習(xí)前面講的閾值法加 Retinex在固定場(chǎng)景下能跑出不錯(cuò)的效果但一旦場(chǎng)景變復(fù)雜——多光源、半透明陰影、軟陰影、彩色陰影——規(guī)則方法就捉襟見(jiàn)肘。深度學(xué)習(xí)的優(yōu)勢(shì)在于它能從數(shù)據(jù)里學(xué)到「陰影長(zhǎng)什么樣」的高層語(yǔ)義而不是依賴人工設(shè)計(jì)的低層特征。但我不建議一上來(lái)就上大模型。陰影分割是個(gè)二分類密集預(yù)測(cè)任務(wù)輕量 U-Net 就夠了。輸入 RGB 三通道輸出單通道概率圖編碼器用 MobileNetV2 或自己搭 4 層下采樣解碼器對(duì)稱上采樣加跳連。參數(shù)量控制在 2M 以內(nèi)CPU 推理也能到 5-10 FPS。訓(xùn)練數(shù)據(jù)方面公開(kāi)數(shù)據(jù)集有 SBU、UCF 陰影數(shù)據(jù)集但場(chǎng)景和你的實(shí)際數(shù)據(jù)可能不匹配。我的經(jīng)驗(yàn)是先用公開(kāi)數(shù)據(jù)預(yù)訓(xùn)練再用自己標(biāo)注的 200-500 張圖微調(diào)效果比純用公開(kāi)數(shù)據(jù)好很多。標(biāo)注不用像素級(jí)精細(xì)用多邊形粗略框出陰影區(qū)域即可U-Net 對(duì)標(biāo)注噪聲有一定容忍度。5.2 陰影去除效果怎么量化驗(yàn)證做去除不能只看「看起來(lái)好了」得有量化指標(biāo)。常用的三個(gè)指標(biāo)含義適用場(chǎng)景RMSE去除后圖像與無(wú)陰影參考圖的均方根誤差有配對(duì)參考圖時(shí)PSNR峰值信噪比越高越好有配對(duì)參考圖時(shí)SSIM結(jié)構(gòu)相似度衡量紋理結(jié)構(gòu)保留程度無(wú)配對(duì)參考圖時(shí)可用實(shí)際項(xiàng)目中很難拿到「同一場(chǎng)景無(wú)陰影」的參考圖所以 SSIM 更常用。做法是在陰影區(qū)域和周圍非陰影區(qū)域各取一個(gè) patch算它們的 SSIM去除后這個(gè)值應(yīng)該升高。另外可以算陰影邊界的梯度一致性去除后邊界梯度應(yīng)該更平滑。from skimage.metrics import structural_similarity as ssim def evaluate_removal(original, removed, mask): # 在陰影區(qū)域取 patch ys, xs np.where(mask 0) if len(ys) 0: return None y1, y2 ys.min(), ys.max() x1, x2 xs.min(), xs.max() patch_orig original[y1:y2, x1:x2] patch_rem removed[y1:y2, x1:x2] if patch_orig.size 0: return None score ssim(patch_orig, patch_rem, channel_axis2, data_range255) return score這個(gè)分?jǐn)?shù)不是絕對(duì)標(biāo)準(zhǔn)但可以用來(lái)對(duì)比不同參數(shù)或不同方法的效果。我一般會(huì)跑一組對(duì)比原始圖、閾值法去除、Retinex 去除、U-Net 去除看哪個(gè) SSIM 最高。5.3 一個(gè)我踩過(guò)的坑別在 RGB 空間直接做 Retinex早期我圖省事直接在 RGB 三個(gè)通道上做 Retinex結(jié)果去除后陰影區(qū)域顏色偏紫。原因是 RGB 通道間有強(qiáng)相關(guān)性逐通道對(duì)數(shù)相減會(huì)破壞白平衡。后來(lái)改成在 Lab 空間只對(duì) L 通道做補(bǔ)償a、b 通道完全不動(dòng)顏色就正常了。這個(gè)改動(dòng)很小但效果差異巨大。def retinex_lab(img_bgr, mask, sigma30): lab cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) l l.astype(np.float32) 1.0 blur cv2.GaussianBlur(l, (0, 0), sigma) blur[blur 0] 1 l_retinex np.log10(l) - np.log10(blur) l_retinex cv2.normalize(l_retinex, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) mask_3ch cv2.merge([mask, mask, mask]) / 255.0 l_final (l_retinex * mask_3ch[:, :, 0] l * (1 - mask_3ch[:, :, 0])).astype(np.uint8) lab_result cv2.merge([l_final, a, b]) return cv2.cvtColor(lab_result, cv2.COLOR_LAB2BGR)這段代碼和前面 HSV 版的區(qū)別就一處只處理 L 通道a、b 原樣保留。如果你做的是彩色圖像陰影去除我強(qiáng)烈建議用這個(gè)版本。做這個(gè)方向這幾年我最大的習(xí)慣是每換一個(gè)數(shù)據(jù)集先跑一遍直方圖分析看亮度和色相分布再?zèng)Q定閾值范圍。不要迷信任何一組「萬(wàn)能參數(shù)」陰影檢測(cè)這件事場(chǎng)景適配比算法選型更重要。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取