HSV實(shí)戰(zhàn):OpenCV顏色分割與值域陷阱解析)
1. 從一次調(diào)色翻車說起為什么RGB轉(zhuǎn)HSV不是調(diào)個(gè)函數(shù)那么簡單去年幫一個(gè)做工業(yè)質(zhì)檢的朋友處理一批金屬表面的圖像目標(biāo)是把氧化發(fā)黑的區(qū)域從正常銀灰色里分離出來。我第一反應(yīng)是拿RGB三通道做閾值分割結(jié)果在產(chǎn)線燈光偏暖的那批圖上全線崩盤——同一塊氧化斑在冷光下R和G的差值很小在暖光下差值被拉得很大閾值根本沒法統(tǒng)一。后來換成HSV空間只盯H和S兩個(gè)維度問題一下就收斂了。這件事讓我意識(shí)到RGB轉(zhuǎn)HSV這個(gè)看起來一行代碼就能搞定的操作背后其實(shí)藏著色彩空間建模的一整套邏輯用不對(duì)地方代碼跑通了結(jié)果也是廢的。這篇內(nèi)容就是圍繞python--RGB轉(zhuǎn)HSV這個(gè)主題把我在圖像處理、計(jì)算機(jī)視覺項(xiàng)目里反復(fù)用到的轉(zhuǎn)換方法、踩過的坑、以及不同庫之間的差異完整地梳理一遍。核心關(guān)鍵詞包括RGB色彩空間、HSV色彩空間、OpenCV顏色轉(zhuǎn)換、色相飽和度明度、圖像閾值分割、顏色特征提取。不管你是剛接觸圖像處理、還在糾結(jié)cv2.cvtColor參數(shù)怎么填的新手還是已經(jīng)寫過不少視覺代碼、但總在顏色分割上翻車的老手這篇都能給你一些能直接抄作業(yè)的東西。先說清楚這篇適合誰如果你做的是顏色識(shí)別、目標(biāo)跟蹤、膚色檢測(cè)、工業(yè)分揀、圖像增強(qiáng)這類活兒RGB轉(zhuǎn)HSV幾乎是你繞不開的一步。但很多人只知道HSV比RGB好做顏色分割卻說不清好在哪、什么時(shí)候不該用、轉(zhuǎn)換過程中哪些信息被丟掉了。我會(huì)把這些講透并且給出可以直接運(yùn)行的Python代碼和參數(shù)對(duì)照表。2. RGB和HSV到底在描述什么兩種坐標(biāo)系的本質(zhì)差異2.1 RGB是設(shè)備視角HSV是人眼視角RGB色彩空間的本質(zhì)是加色混合模型。它把顏色拆成紅、綠、藍(lán)三個(gè)通道的強(qiáng)度值每個(gè)通道通常取0到255。你可以把它理解成三盞燈分別打紅、綠、藍(lán)三種光疊加出各種顏色。這個(gè)模型非常貼合顯示器和攝像頭的物理實(shí)現(xiàn)——屏幕上的每個(gè)像素就是三個(gè)子像素發(fā)光攝像頭傳感器也是分別感應(yīng)三種波長的光。所以RGB是設(shè)備怎么產(chǎn)生顏色的描述方式。但問題在于RGB的三個(gè)通道是高度耦合的。你想表達(dá)這是一個(gè)偏紅的顏色在RGB里得同時(shí)約束R高、G中、B低三個(gè)值互相牽扯。光照一變?nèi)齻€(gè)值一起漂移你很難用一個(gè)簡單的規(guī)則把紅色框出來。HSV換了一套坐標(biāo)。H是色相Hue表示顏色在色環(huán)上的角度位置比如紅色在0度附近綠色在120度附近藍(lán)色在240度附近。S是飽和度Saturation表示顏色的純度越低越接近灰白。V是明度Value表示顏色的亮暗。這套描述方式更接近人腦感知顏色的方式——我們平時(shí)說這個(gè)紅很正高飽和、這個(gè)紅發(fā)暗低明度用的就是HSV的語言。2.2 轉(zhuǎn)換過程中到底發(fā)生了什么從RGB到HSV的轉(zhuǎn)換不是簡單的線性變換而是一組分段函數(shù)。核心思路是先把RGB歸一化到0到1找出三個(gè)通道里的最大值max和最小值min然后V max明度直接取最大通道值S (max - min) / max當(dāng)max不為0時(shí)飽和度取決于通道的極差相對(duì)于最大值H則根據(jù)max是哪個(gè)通道分三種情況計(jì)算本質(zhì)是判斷顏色偏向哪個(gè)色相區(qū)間這里有個(gè)關(guān)鍵點(diǎn)很多人忽略當(dāng)S為0時(shí)H是沒有定義的。因?yàn)镾為0意味著三個(gè)通道相等也就是灰色灰色在色環(huán)上沒有對(duì)應(yīng)的角度。OpenCV在這種情況下會(huì)把H設(shè)為0。如果你在做顏色分割時(shí)沒注意這一點(diǎn)灰色區(qū)域可能會(huì)被誤判成紅色因?yàn)镠0對(duì)應(yīng)紅色這是個(gè)非常隱蔽的坑。另一個(gè)關(guān)鍵點(diǎn)是值域范圍。不同庫對(duì)H、S、V的取值范圍定義不一樣這是新手最容易栽跟頭的地方我在第4節(jié)會(huì)專門用表格對(duì)比。2.3 為什么HSV更適合顏色分割舉個(gè)具體例子。假設(shè)你要從一張圖里提取所有紅色的像素。在RGB里你得寫類似R 150 and G 100 and B 100這樣的條件而且這個(gè)閾值對(duì)光照極其敏感換個(gè)亮度就得重調(diào)。在HSV里你只需要約束H在紅色區(qū)間比如0到10和170到180因?yàn)榧t色跨了色環(huán)的0度邊界S和V給一個(gè)寬松的下限過濾掉灰白和過暗的像素。光照變化主要影響V而V你本來就給了寬松范圍所以魯棒性大幅提升。這就是HSV在顏色分割任務(wù)里幾乎成為默認(rèn)選擇的原因。但HSV也不是萬能的。它的色相在低飽和度、低明度區(qū)域非常不穩(wěn)定。一個(gè)接近黑色的像素RGB三個(gè)值都很小微小的噪聲就能讓max對(duì)應(yīng)的通道跳變導(dǎo)致H劇烈抖動(dòng)。所以在暗部或灰部做色相分析結(jié)果往往不可靠。這一點(diǎn)在做夜間圖像或陰影區(qū)域處理時(shí)要特別小心。3. Python里做RGB轉(zhuǎn)HSV的幾條路選錯(cuò)庫等于白干3.1 OpenCV的cvtColor工業(yè)級(jí)首選在Python生態(tài)里做RGB轉(zhuǎn)HSV最常用的就是OpenCV的cv2.cvtColor。它的調(diào)用方式很直接import cv2 import numpy as np # 假設(shè)img是BGR格式OpenCV默認(rèn)讀入格式 img_bgr cv2.imread(test.jpg) img_hsv cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)這里第一個(gè)大坑就來了OpenCV讀入圖像默認(rèn)是BGR順序不是RGB。如果你拿PIL或matplotlib讀的圖直接丟給cv2.cvtColor并指定COLOR_RGB2HSV顏色通道就錯(cuò)位了轉(zhuǎn)換出來的H值全是錯(cuò)的。我見過不止一個(gè)項(xiàng)目因?yàn)檫@個(gè)問題導(dǎo)致紅色和藍(lán)色識(shí)別對(duì)調(diào)排查了半天。正確的做法是明確你的輸入是什么格式。如果是OpenCV自己讀的用COLOR_BGR2HSV如果是其他庫讀的RGB數(shù)組要么先轉(zhuǎn)成BGR要么用COLOR_RGB2HSV。我個(gè)人的習(xí)慣是統(tǒng)一在OpenCV體系里操作讀入就用imread避免混用。OpenCV的HSV值域是H取0到179S取0到255V取0到255。注意H只有180個(gè)值不是360。這是因?yàn)镺penCV為了把H塞進(jìn)8位無符號(hào)整數(shù)0到255里把角度除以了2。所以如果你從別的庫拿到H是0到360的轉(zhuǎn)到OpenCV前要除以2。3.2 PIL/Pillow的convert方便但值域不同Pillow也能做色彩空間轉(zhuǎn)換from PIL import Image img Image.open(test.jpg).convert(HSV) hsv_array np.array(img)Pillow的HSV值域是H、S、V都是0到255。H的計(jì)算方式是角度乘以255除以360所以和OpenCV的H值不一樣。如果你在Pillow里調(diào)好了閾值直接搬到OpenCV里用結(jié)果會(huì)完全不對(duì)。這個(gè)差異我在第4節(jié)的表格里會(huì)詳細(xì)列出來。Pillow的好處是API簡單適合做批量圖像格式轉(zhuǎn)換或簡單的顏色統(tǒng)計(jì)。但它在做像素級(jí)操作時(shí)性能不如OpenCV而且功能相對(duì)有限。3.3 matplotlib和skimage科研場(chǎng)景常用skimagescikit-image提供了rgb2hsv函數(shù)from skimage.color import rgb2hsv hsv rgb2hsv(rgb_image)skimage的特點(diǎn)是輸入輸出都是浮點(diǎn)數(shù)值域歸一化到0到1。H是0到1的角度比例S和V也是0到1。這種歸一化在科研和算法驗(yàn)證時(shí)很方便因?yàn)椴挥貌傩恼麛?shù)溢出和值域問題。但如果你要部署到嵌入式設(shè)備或做實(shí)時(shí)處理浮點(diǎn)運(yùn)算的開銷和OpenCV的定點(diǎn)優(yōu)化比起來還是有差距。matplotlib的colors.rgb_to_hsv也能用但主要是給繪圖配色服務(wù)的處理大圖像性能一般不建議在圖像處理流水線里用。3.4 手寫轉(zhuǎn)換理解原理的最好方式但別用在生產(chǎn)想真正搞懂轉(zhuǎn)換過程我建議你手寫一遍。下面是一個(gè)基于NumPy的向量化實(shí)現(xiàn)import numpy as np def rgb_to_hsv_numpy(rgb): # rgb: (H, W, 3), 值域0-255 rgb rgb.astype(np.float32) / 255.0 r, g, b rgb[..., 0], rgb[..., 1], rgb[..., 2] maxc np.max(rgb, axis-1) minc np.min(rgb, axis-1) delta maxc - minc # 計(jì)算H h np.zeros_like(maxc) mask delta ! 0 rmask mask (maxc r) gmask mask (maxc g) bmask mask (maxc b) h[rmask] (60 * ((g[rmask] - b[rmask]) / delta[rmask]) 360) % 360 h[gmask] 60 * ((b[gmask] - r[gmask]) / delta[gmask]) 120 h[bmask] 60 * ((r[bmask] - g[bmask]) / delta[bmask]) 240 # 計(jì)算S s np.zeros_like(maxc) s[maxc ! 0] delta[maxc ! 0] / maxc[maxc ! 0] # V就是maxc v maxc return np.stack([h, s, v], axis-1)手寫的好處是你能清楚看到每個(gè)像素的計(jì)算路徑調(diào)試時(shí)能定位到具體哪一步出了問題。但生產(chǎn)環(huán)境還是用OpenCV因?yàn)樗牡讓邮荂優(yōu)化過的速度差幾十倍。4. 值域?qū)φ毡聿煌瑤熘g轉(zhuǎn)換的隱形陷阱前面反復(fù)提到值域問題這里集中列一張表。這張表是我在實(shí)際項(xiàng)目里踩坑后整理的建議收藏。庫/函數(shù)H范圍S范圍V范圍輸入格式備注OpenCVCOLOR_BGR2HSV0-1790-2550-255BGRH是角度除以2OpenCVCOLOR_RGB2HSV0-1790-2550-255RGB同上注意輸入順序Pillowconvert(HSV)0-2550-2550-255RGBH是角度乘255除360skimagergb2hsv0-10-10-1RGB浮點(diǎn)歸一化適合科研matplotlibrgb_to_hsv0-10-10-1RGB浮點(diǎn)同skimage這張表最實(shí)用的地方在于跨庫遷移閾值。比如你在skimage里調(diào)好了紅色的H范圍是0到0.05和0.95到1.0搬到OpenCV里就要乘以180變成0到9和171到180。S和V從0到1乘以255變成0到255。這個(gè)換算不做閾值全廢。還有一個(gè)隱蔽的坑OpenCV的H在紅色區(qū)域是跨0的。紅色對(duì)應(yīng)H接近0和接近179兩個(gè)區(qū)間。如果你只寫H 10會(huì)漏掉H在170到179的那部分紅色。正確寫法是用cv2.inRange配合兩個(gè)區(qū)間做或運(yùn)算或者用位運(yùn)算合并掩碼。# 提取紅色OpenCV HSV lower_red1 np.array([0, 100, 100]) upper_red1 np.array([10, 255, 255]) lower_red2 np.array([170, 100, 100]) upper_red2 np.array([179, 255, 255]) mask1 cv2.inRange(hsv, lower_red1, upper_red1) mask2 cv2.inRange(hsv, lower_red2, upper_red2) mask cv2.bitwise_or(mask1, mask2)這段代碼我用了很多次S和V的下限設(shè)100是為了過濾掉灰白和過暗的像素。具體數(shù)值要根據(jù)你的圖像調(diào)整但思路是通用的。5. 實(shí)戰(zhàn)用HSV做顏色分割的完整流程與參數(shù)調(diào)優(yōu)5.1 從一張圖到掩碼的完整鏈路假設(shè)你有一張產(chǎn)品圖要從傳送帶上分離出藍(lán)色的零件。完整流程是這樣的import cv2 import numpy as np # 1. 讀入并轉(zhuǎn)換 img cv2.imread(product.jpg) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 2. 定義藍(lán)色范圍OpenCV值域 lower_blue np.array([100, 80, 50]) upper_blue np.array([130, 255, 255]) # 3. 生成掩碼 mask cv2.inRange(hsv, lower_blue, upper_blue) # 4. 形態(tài)學(xué)去噪 kernel np.ones((5, 5), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 5. 提取結(jié)果 result cv2.bitwise_and(img, img, maskmask)這個(gè)流程里第4步的形態(tài)學(xué)操作經(jīng)常被省略但實(shí)際項(xiàng)目里幾乎必做。因?yàn)镠SV閾值分割出來的掩碼往往有大量噪點(diǎn)尤其是圖像邊緣和反光區(qū)域。開運(yùn)算去掉小噪點(diǎn)閉運(yùn)算填補(bǔ)內(nèi)部空洞這兩個(gè)操作能讓掩碼質(zhì)量提升一個(gè)檔次。5.2 參數(shù)怎么調(diào)一個(gè)可復(fù)用的調(diào)試方法調(diào)HSV閾值最笨的辦法是反復(fù)改數(shù)字看結(jié)果效率極低。我常用的方法是先做交互式取色。用OpenCV的鼠標(biāo)回調(diào)點(diǎn)一下圖像就打印出該點(diǎn)的HSV值def pick_hsv(event, x, y, flags, param): if event cv2.EVENT_LBUTTONDOWN: hsv_img param[hsv] print(f位置({x},{y}) HSV: {hsv_img[y, x]}) img cv2.imread(product.jpg) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) cv2.imshow(image, img) cv2.setMouseCallback(image, pick_hsv, {hsv: hsv}) cv2.waitKey(0)在目標(biāo)區(qū)域多點(diǎn)幾個(gè)點(diǎn)記錄H、S、V的分布然后取一個(gè)能覆蓋大部分目標(biāo)點(diǎn)、又不包含背景的范圍。這個(gè)方法比盲調(diào)快十倍。調(diào)參時(shí)有個(gè)經(jīng)驗(yàn)H的范圍盡量窄S和V的范圍盡量寬。因?yàn)镠決定顏色類別窄一點(diǎn)能避免把相近顏色混進(jìn)來S和V受光照影響大寬一點(diǎn)能適應(yīng)不同亮度。但S的下限不能太低否則灰色會(huì)被誤判V的下限也不能太低否則暗部噪聲會(huì)進(jìn)來。5.3 光照變化的應(yīng)對(duì)策略如果你的應(yīng)用場(chǎng)景光照會(huì)變固定閾值遲早出問題。幾個(gè)實(shí)用的應(yīng)對(duì)手段自適應(yīng)白平衡預(yù)處理在轉(zhuǎn)HSV之前先做一次灰度世界或白平衡校正把光照色偏拉回來用S和V做歸一化有些場(chǎng)景下只靠H做分割S和V只用來過濾極端值分區(qū)域閾值如果圖像不同區(qū)域光照差異大可以分塊處理每塊用不同的閾值直方圖均衡化對(duì)V通道做均衡化提升暗部細(xì)節(jié)但要注意這會(huì)影響S和H我在一個(gè)戶外分揀項(xiàng)目里用過組合方案先做白平衡再轉(zhuǎn)HSVH范圍放寬S和V用動(dòng)態(tài)閾值根據(jù)圖像整體亮度調(diào)整。這套方案在陰天和晴天都能跑比固定閾值穩(wěn)定得多。6. 那些年我在RGB轉(zhuǎn)HSV上踩過的坑6.1 通道順序搞反最經(jīng)典也最致命前面提過OpenCV讀圖是BGR。我見過一個(gè)項(xiàng)目開發(fā)者用cv2.imread讀圖然后用COLOR_RGB2HSV轉(zhuǎn)換結(jié)果紅色和藍(lán)色完全對(duì)調(diào)。更麻煩的是這種錯(cuò)誤在灰度圖上不明顯只有彩色圖才暴露如果測(cè)試用例都是灰度圖上線后才炸。避坑方法在代碼里明確注釋輸入格式或者封裝一個(gè)統(tǒng)一的讀圖函數(shù)所有圖像都經(jīng)過同一個(gè)入口避免混用。6.2 灰色區(qū)域H值跳變隱蔽的誤判源前面講過S接近0時(shí)H不穩(wěn)定。我做過一個(gè)缺陷檢測(cè)背景是灰色金屬結(jié)果部分灰色像素的H偶爾跳到紅色區(qū)間被誤判成缺陷。排查了很久才發(fā)現(xiàn)是S太低導(dǎo)致的H抖動(dòng)。避坑方法在閾值里給S設(shè)一個(gè)下限比如S 30把灰色區(qū)域直接排除。如果你的任務(wù)必須處理灰色那就不要用H改用S和V做判斷。6.3 值域換算遺漏跨庫遷移的定時(shí)炸彈從skimage換到OpenCV或者從Pillow換到OpenCV值域不換算閾值全錯(cuò)。這個(gè)坑我在第4節(jié)專門做了表格但實(shí)際項(xiàng)目中還是容易忘。避坑方法寫一個(gè)轉(zhuǎn)換函數(shù)把不同庫的HSV統(tǒng)一到同一個(gè)值域再處理。或者干脆全項(xiàng)目只用一種庫減少切換。6.4 性能問題大圖逐像素轉(zhuǎn)換有人用Python循環(huán)逐像素做RGB轉(zhuǎn)HSV處理一張4K圖要幾十秒。OpenCV的cvtColor是向量化底層優(yōu)化的同樣的圖毫秒級(jí)完成。避坑方法永遠(yuǎn)用庫函數(shù)不要手寫循環(huán)。如果非要手寫用NumPy向量化別用for循環(huán)。6.5 浮點(diǎn)精度問題skimage返回浮點(diǎn)HSV如果你把它轉(zhuǎn)成uint8存儲(chǔ)會(huì)丟失精度。反過來從uint8轉(zhuǎn)浮點(diǎn)做運(yùn)算再轉(zhuǎn)回uint8也可能因?yàn)樯崛氘a(chǎn)生偏差。避坑方法在流水線里保持?jǐn)?shù)據(jù)類型一致中間計(jì)算用浮點(diǎn)最終輸出再轉(zhuǎn)整數(shù)。不要反復(fù)在整數(shù)和浮點(diǎn)之間來回轉(zhuǎn)。7. 進(jìn)階HSV之外什么時(shí)候該考慮其他色彩空間HSV不是終點(diǎn)。在某些場(chǎng)景下其他色彩空間可能更合適Lab色彩空間感知均勻適合做顏色差異度量。如果你要判斷兩個(gè)顏色看起來差多少Lab比HSV更準(zhǔn)YCrCb亮度和色度分離得更徹底膚色檢測(cè)常用HSL和HSV類似但L的定義不同某些圖像編輯軟件用這個(gè)但HSV的優(yōu)勢(shì)在于直觀、計(jì)算簡單、OpenCV支持好。對(duì)于大多數(shù)顏色分割任務(wù)HSV夠用了。只有在需要精確顏色差異度量或特定領(lǐng)域應(yīng)用時(shí)才需要考慮換空間。我個(gè)人的選擇邏輯是顏色分類和分割用HSV顏色差異度量用Lab膚色檢測(cè)用YCrCb。這個(gè)組合覆蓋了我遇到的大部分場(chǎng)景。8. 幾個(gè)能直接抄的代碼片段最后放幾個(gè)我反復(fù)用的代碼片段都是經(jīng)過項(xiàng)目驗(yàn)證的。批量轉(zhuǎn)換并保存HSV圖像import cv2 import os def batch_convert(input_dir, output_dir): os.makedirs(output_dir, exist_okTrue) for fname in os.listdir(input_dir): if not fname.lower().endswith((.jpg, .png, .jpeg)): continue img cv2.imread(os.path.join(input_dir, fname)) if img is None: continue hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 為了可視化把HSV轉(zhuǎn)回BGR顯示注意這不是原始顏色 vis cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) cv2.imwrite(os.path.join(output_dir, fname), vis)注意這里轉(zhuǎn)回BGR只是為了可視化HSV圖像本身不能直接保存為普通圖片格式因?yàn)閳D片格式存的是RGB/BGR。顏色范圍可視化工具import cv2 import numpy as np def create_hsv_trackbars(): cv2.namedWindow(trackbars) cv2.createTrackbar(H_min, trackbars, 0, 179, lambda x: None) cv2.createTrackbar(H_max, trackbars, 179, 179, lambda x: None) cv2.createTrackbar(S_min, trackbars, 0, 255, lambda x: None) cv2.createTrackbar(S_max, trackbars, 255, 255, lambda x: None) cv2.createTrackbar(V_min, trackbars, 0, 255, lambda x: None) cv2.createTrackbar(V_max, trackbars, 255, 255, lambda x: None) img cv2.imread(test.jpg) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) while True: h_min cv2.getTrackbarPos(H_min, trackbars) h_max cv2.getTrackbarPos(H_max, trackbars) s_min cv2.getTrackbarPos(S_min, trackbars) s_max cv2.getTrackbarPos(S_max, trackbars) v_min cv2.getTrackbarPos(V_min, trackbars) v_max cv2.getTrackbarPos(V_max, trackbars) lower np.array([h_min, s_min, v_min]) upper np.array([h_max, s_max, v_max]) mask cv2.inRange(hsv, lower, upper) result cv2.bitwise_and(img, img, maskmask) cv2.imshow(result, result) if cv2.waitKey(1) 0xFF 27: break cv2.destroyAllWindows()這個(gè)工具用滑動(dòng)條實(shí)時(shí)調(diào)閾值比盲調(diào)快得多。我每個(gè)新項(xiàng)目都會(huì)先跑一遍這個(gè)找到大致范圍再寫死到代碼里。HSV值域轉(zhuǎn)換函數(shù)def convert_hsv_range(h, s, v, from_libskimage, to_libopencv): 在不同庫的HSV值域之間轉(zhuǎn)換 if from_lib skimage and to_lib opencv: return int(h * 180), int(s * 255), int(v * 255) elif from_lib opencv and to_lib skimage: return h / 180.0, s / 255.0, v / 255.0 elif from_lib pillow and to_lib opencv: return int(h * 180 / 255), s, v else: raise ValueError(f不支持的轉(zhuǎn)換: {from_lib} - {to_lib})這個(gè)函數(shù)雖然簡單但在跨庫項(xiàng)目里能省很多事。9. 關(guān)于HSV轉(zhuǎn)換我最后想說的幾點(diǎn)體會(huì)做了這么多年圖像處理RGB轉(zhuǎn)HSV這個(gè)操作我用了無數(shù)次但每次遇到新場(chǎng)景還是會(huì)有新的坑。最大的體會(huì)是不要迷信HSV也不要輕視它。它在顏色分割上的優(yōu)勢(shì)是實(shí)打?qū)嵉牡木窒扌浴惋柡投葏^(qū)域H不穩(wěn)定、值域因庫而異、光照敏感——也是實(shí)打?qū)嵉?。我的建議是拿到一個(gè)新任務(wù)先用交互式工具快速看一下目標(biāo)顏色的HSV分布心里有個(gè)數(shù)。然后寫代碼時(shí)把值域轉(zhuǎn)換、通道順序、灰色過濾這幾個(gè)點(diǎn)檢查一遍。最后一定要用多張不同光照、不同場(chǎng)景的圖測(cè)試別只用一張圖調(diào)通了就上線。還有一個(gè)實(shí)用技巧如果你的應(yīng)用允許在轉(zhuǎn)HSV之前先做一次高斯模糊。這能平滑掉傳感器噪聲讓H值更穩(wěn)定。模糊核大小根據(jù)圖像分辨率定一般3x3或5x5就夠。這個(gè)操作對(duì)后續(xù)的閾值分割幫助很大尤其是低質(zhì)量圖像。至于HSV和其他色彩空間的配合我的經(jīng)驗(yàn)是不要一開始就上復(fù)雜的方案。先把HSV用熟遇到它解決不了的問題再考慮Lab或YCrCb。大多數(shù)顏色分割任務(wù)HSV加上合理的預(yù)處理和后處理已經(jīng)能覆蓋八九成的需求了。