第 3 章 處理圖像的顏色


本章包括以下內(nèi)容:

  • 用策略設計模式比較顏色醒串;
  • 用GrabCut 算法分割圖像怀偷;
  • 轉換顏色表示法;
  • 用色調(diào)扔仓、飽和度和亮度表示顏色褐奥。


3.2 用策略設計模式比較顏色

假設我們要構建一個簡單的算法,用來識別圖像中具有某種顏色的所有像素翘簇。這個算法必須輸入一幅圖像和一個顏色撬码,并且返回一個二值圖像,顯示具有指定顏色的像素版保。在運行算法前耍群,還要指定一個參數(shù),即能接受的顏色的公差找筝。

這里先看一個部署和使用它的例子蹈垢。寫一個簡單的主函數(shù),調(diào)用顏色檢測算法:

int main()
{
    // 1.創(chuàng)建圖像處理器對象
    ColorDetector cdetect;
    // 2.讀取輸入的圖像
    cv::Mat image= cv::imread("boldt.jpg");
    if (image.empty()) return 0;
    // 3.設置輸入?yún)?shù)
    cdetect.setTargetColor(230,190,130); // 這里表示藍天
    // 4.處理圖像并顯示結果
    cv::namedWindow("result");
    cv::Mat result = cdetect.process(image);
    cv::imshow("result",result);
    cv::waitKey(0);

    return 0;
}
result.jpg

這個算法的核心過程非常簡單袖裕,只是對每個像素進行循環(huán)掃描曹抬,把它的顏色和目標顏色做比較〖宾可以這樣寫這個循環(huán):

#include "colordetector.h"
#include <vector>

cv::Mat ColorDetector::process(const cv::Mat &image) {

      // 必要時重新分配二值映像
      // 與輸入圖像的尺寸相同谤民,不過是單通道
      result.create(image.size(),CV_8U);

      // Converting to Lab color space 
      if (useLab)
          cv::cvtColor(image, converted, CV_BGR2Lab);

      // 取得迭代器
      cv::Mat_<cv::Vec3b>::const_iterator it= image.begin<cv::Vec3b>();
      cv::Mat_<cv::Vec3b>::const_iterator itend= image.end<cv::Vec3b>();
      cv::Mat_<uchar>::iterator itout= result.begin<uchar>();

      // get the iterators of the converted image 
      if (useLab) {
          it = converted.begin<cv::Vec3b>();
          itend = converted.end<cv::Vec3b>();
      }

      // 對于每個像素
      for ( ; it!= itend; ++it, ++itout) {

          // 比較與目標顏色的差距
          if (getDistanceToTargetColor(*it)<maxDist) {
              *itout= 255;
          } else {
              *itout= 0;
          }
      }

      return result;
}

我們已經(jīng)定義了核心的處理方法,下面就看一下為了部署該算法疾宏,還需要添加哪些額外方法张足。

#if !defined COLORDETECT
#define COLORDETECT

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

class ColorDetector {
private:

    int maxDist; // 允許的最小差距
    cv::Vec3b target; // 目標顏色
    cv::Mat result; // 存儲二值映像結果的圖像

public:
    // 空構造函數(shù)
    // 在此初始化默認參數(shù)
    ColorDetector() : maxDist(100), target(0, 0, 0) {}
    // 另一種構造函數(shù),使用目標顏色和顏色距離作為參數(shù)
    ColorDetector(uchar blue, uchar green, uchar red, int mxDist=100):maxDist(maxDist){
    // 目標顏色
    setTargetColor(blue, green, red);
    }
    cv::Mat process(const cv::Mat& image);
    
    // 設置顏色差距的閾值
    // 閾值必須是正數(shù)坎藐,否則就設為0
    void setColorDistanceThreshold(int distance) {
        if (distance < 0)
            distance = 0;
        maxDist = distance;
    }

    // 取得顏色差距的閾值
    int getColorDistanceThreshold() const {
        return maxDist;
    }
    
    // 設置需要檢測的顏色
    void setTargetColor(uchar blue, uchar green, uchar red) {
        target = cv::Vec3b(blue, green, red);
    }
    // 設置需要檢測的顏色
    void setTargetColor(cv::Vec3b color) {
        target = color;
    }
    // 取得需要檢測的顏色
    cv::Vec3b getTargetColor() const {
        return target;
    }

    // 計算兩個顏色之間的城區(qū)距離
    int getColorDistance(const cv::Vec3b& color1,
        const cv::Vec3b& color2) const {
        return abs(color1[0] - color2[0]) +
            abs(color1[1] - color2[1]) +
            abs(color1[2] - color2[2]);
    }
    // 計算與目標顏色的差距
    int getDistanceToTargetColor(const cv::Vec3b& color) const {
        return getColorDistance(color, target);
    }
};
#endif

計算兩個顏色向量間的距離

要計算兩個顏色向量間的距離为牍,可使用這個簡單的公式:

abs(color[0]-target[0])+
abs(color[1]-target[1])+
abs(color[2]-target[2]);

OpenCV 中也有計算向量的歐幾里得范數(shù)的函數(shù),因此也可以這樣計算距離:

return static_cast<int>(
    cv::norm<int, 3>(cv::Vec3i(color[0] - target[0],
        color[1] - target[1],
        color[2] - target[2])));

使用OpenCV 函數(shù)

本節(jié)采用了在循環(huán)中使用迭代器的方法來進行計算岩馍。還有一種做法是調(diào)用OpenCV 的系列函數(shù)碉咆,也能得到一樣的結果。因此蛀恩,檢測顏色的方法還可以這樣寫:

cv::Mat ColorDetector::process(const cv::Mat& image) {
    cv::Mat output;
    // 計算與目標顏色的距離的絕對值
    cv::absdiff(image, cv::Scalar(target), output);
    // 把通道分割進3 幅圖像
    std::vector<cv::Mat> images;
    cv::split(output, images);
    // 3 個通道相加(這里可能出現(xiàn)飽和的情況)
    output = images[0] + images[1] + images[2];
    // 應用閾值
    cv::threshold(output, // 相同的輸入/輸出圖像
        output,
        maxDist, // 閾值(必須<256)
        255, // 最大值
        cv::THRESH_BINARY_INV); // 閾值化模式
    return output;
}

該方法使用了absdiff 函數(shù)計算圖像的像素與標量值之間差距的絕對值疫铜。該函數(shù)的第二個參數(shù)也可以不用標量值,而是改用另一幅圖像双谆,這樣就可以逐個像素地計算差距壳咕。因此兩幅圖像的尺寸必須相同席揽。

然后,用split 函數(shù)提取出存放差距的圖像的單個通道以便求和谓厘。

最后一步是用cv::threshold 函數(shù)創(chuàng)建一個二值圖像幌羞。。這個函數(shù)通常用于將所有像素與某個閾值(第三個參數(shù))進行比較庞呕,并且在常規(guī)閾值化模式(cv::THRESH_BINARY)下新翎,將所有大于指定閾值的像素賦值為預定的最大值(第四個參數(shù))程帕,將其他像素賦值為0住练。

一般來說,最好直接使用OpenCV 函數(shù)愁拭。它可以快速建立復雜程序讲逛,減少潛在的錯誤,而且程序的運行效率通常也比較高(得益于OpenCV 項目參與者做的優(yōu)化工作)岭埠。不過這樣會執(zhí)行很多的中間步驟盏混,消耗更多內(nèi)存。

floodFill 函數(shù)

ColorDetector 類可以在一幅圖像中找出與指定顏色接近的像素惜论,它的判斷方法是對像素進行逐個檢查许赃。cv::floodFill 函數(shù)的做法與之類似,但有一個很大的區(qū)別馆类,那就是它在判斷一個像素時混聊,還要檢查附近像素的狀態(tài),這是為了識別某種顏色的相關區(qū)域乾巧。用戶只需指定一個起始位置和允許的誤差句喜,就可以找出顏色接近的連續(xù)區(qū)域。

首先根據(jù)亞像素確定搜尋的顏色沟于,并檢查它旁邊的像素罐呼,判斷它們是否為顏色接近的像素念逞;然后,繼續(xù)檢查它們旁邊的像素,并持續(xù)操作匙握。這樣就可以從圖像中提取出特定顏色的區(qū)域。例如要從圖中提取出藍天浩习,可以執(zhí)行以下語句:

cv::floodFill(image, // 輸入/輸出圖像
    cv::Point(100, 50), // 起始點
    cv::Scalar(255, 255, 255), // 填充顏色
    (cv::Rect*)0, // 填充區(qū)域的邊界矩形
    cv::Scalar(35, 35, 35), // 偏差的最小/最大閾值
    cv::Scalar(35, 35, 35), // 正差閾值朱沃,兩個閾值通常相等
    cv::FLOODFILL_FIXED_RANGE); // 與起始點像素比較

圖像中亞像素(100, 50)所處的位置是天空。函數(shù)會檢查所有的相鄰像素嗜傅,顏色接近的像素會被重繪成第三個參數(shù)指定的新顏色金句。為了判斷顏色是否接近,需要分別定義比參考色更高或更低的值作為閾值吕嘀。這里使用固定范圍模式违寞,即所有像素都與亞像素的顏色進行對比贞瞒,默認模式是將每個像素與和它鄰近的像素進行對比。得到的結果如下圖所示趁曼。

result.jpg


3.3 用GrabCut 算法分割圖像

如果要從靜態(tài)圖像中提取前景物體(例如從圖像中剪切一個物體军浆,并粘貼到另一幅圖像),最好采用GrabCut 算法挡闰。

cv::grabCut 函數(shù)的用法非常簡單乒融,只需要輸入一幅圖像,并對一些像素做上“屬于背景”或“屬于前景”的標記即可摄悯。根據(jù)這個局部標記赞季,算法將計算出整幅圖像的前景/背景分割線。

一種指定輸入圖像局部前景/背景標簽的方法是定義一個包含前景物體的矩形:

// 定義一個帶邊框的矩形
// 矩形外部的像素會被標記為背景
cv::Rect rectangle(5,70,260,120);
result.jpg

矩形之外的像素都會被標記為背景奢驯。調(diào)用cv::grabCut 時申钩,除了需要輸入圖像和分割后的圖像,還需要定義兩個矩陣瘪阁,用于存放算法構建的模型撒遣,代碼如下所示:

cv::Mat result; // 分割結果(四種可能的值)
cv::Mat bgModel,fgModel; // 模型(內(nèi)部使用)
// GrabCut 分割算法
cv::grabCut(image, // 輸入圖像
            result, // 分割結果
            rectangle, // 包含前景的矩形
            bgModel,fgModel, // 模型
            5, // 迭代次數(shù)
            cv::GC_INIT_WITH_RECT); // 使用矩形

注意,我們在函數(shù)的中用cv::GC_INIT_WITH_RECT 標志作為最后一個參數(shù)管跺,表示將使用帶邊框的矩形模型义黎。

輸入/輸出的分割圖像可以是以下四個值之一。

  • cv::GC_BGD:這個值表示明確屬于背景的像素(例如本例中矩形之外的像素)豁跑。
  • cv::GC_FGD:這個值表示明確屬于前景的像素(本例中沒有這種像素)廉涕。
  • cv::GC_PR_BGD:這個值表示可能屬于背景的像素。
  • cv::GC_PR_FGD:這個值表示可能屬于前景的像素(即本例中矩形之內(nèi)像素的初始值)贩绕。

通過提取值為cv::GC_PR_FGD 的像素火的,可得到包含分割信息的二值圖像,實現(xiàn)代碼為:

// 取得標記為“可能屬于前景”的像素
cv::compare(result,cv::GC_PR_FGD,result,cv::CMP_EQ);
// 生成輸出圖像
cv::Mat foreground(image.size(),CV_8UC3,cv::Scalar(255,255,255));
image.copyTo(foreground, result); // 不復制背景像素

要提取全部前景像素淑倾,即值為cv::GC_PR_FGD 或cv::GC_FGD 的像素馏鹤,可以檢查第一位的值,代碼如下所示:

// 用“按位與”運算檢查第一位
result= result&1; // 如果是前景像素娇哆,結果為1

這可能是因為這幾個常量被定義的值為1 和3湃累,而另外兩個(cv::GC_BGD 和cv::GC_PR_BGD)被定義為0 和2。本例因為分割圖像不含cv::GC_FGD 像素(只輸入了cv::GC_BGD 像素)碍讨,所以得到的結果是一樣的治力。

result.jpg

完整的代碼為:

    cv::Mat image = cv::imread("boldt.jpg", 1);
    if (image.empty())
        return 0;

    cv::Rect rectangle(50, 25, 210, 180);
    cv::Mat bgModel, fgModel;
    cv::Mat result;

    cv::grabCut(image,    
        result,   
        rectangle,
        bgModel, fgModel, 
        5,       
        cv::GC_INIT_WITH_RECT); 

    cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ);  // result = result & 1;
    cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255, 255, 255));
    image.copyTo(foreground, result);

    cv::namedWindow("result");
    cv::imshow("result", foreground);
    cv::waitKey(0);

利用輸入信息,GrabCut 算法通過以下步驟進行背景/前景分割勃黍。

首先宵统,把所有未標記的像素臨時標為前景(cv::GC_PR_FGD)「不瘢基于當前的分類情況马澈,算法把像素劃分為多個顏色相似的組(即K 個背景組和K 個前景組)瓢省。

下一步是通過引入前景和背景像素之間的邊緣,確定背景/前景的分割痊班,這將通過一個優(yōu)化過程來實現(xiàn)勤婚。

在此過程中,將試圖連接具有相似標記的像素涤伐,并且避免邊緣出現(xiàn)在強度相對均勻的區(qū)域馒胆。使用Graph Cuts 算法可以高效地解決這個優(yōu)化問題,它尋找最優(yōu)解決方案的方法是:把問題表示成一幅連通的圖形凝果,然后在圖形上進行切割祝迂,以形成最優(yōu)的形態(tài)。分割完成后豆村,像素會有新的標記液兽。然后重復這個分組過程骂删,找到新的最優(yōu)分割方案掌动,如此反復。

因此宁玫,GrabCut 算法是一個逐步改進分割結果的迭代過程粗恢。根據(jù)場景的復雜程度,找到最佳方案所需的迭代次數(shù)各不相同(如果情況簡單欧瘪,迭代一次就足夠了)眷射。


3.4 轉換顏色表示法

利用RGB 色彩空間計算顏色之間的差距并不是衡量兩個顏色相似度的最好方式。實際上佛掖,RGB 并不是感知均勻的色彩空間妖碉。也就是說,兩種具有一定差距的顏色可能看起來非常接近芥被,而另外兩種具有同樣差距的顏色看起來卻差別很大欧宜。

為解決這個問題,引入了一些具有感知均勻特性的顏色表示法拴魄。CIE L*a*b*就是一種這樣的顏色模型冗茸。把圖像轉換到這種表示法后,我們就可以真正地使用圖像像素與目標顏色之間的歐幾里得距離匹中,來度量顏色之間的視覺相似度夏漱。本節(jié)將介紹如何轉換顏色表示法,以便使用其他色彩空間顶捷。

使用OpenCV 的函數(shù)cv::cvtColor 可以輕松轉換圖像的色彩空間挂绰。

// 將目標顏色轉換成Lab 色彩空間
cv::cvtColor(tmp, tmp, cv::COLOR_BGR2Lab);

在將圖像從一個色彩空間轉換到另一個色彩空間時,會在每個輸入像素上做一個線性或非線性的轉換服赎,以得到輸出像素葵蒂。

實際的像素值范圍取決于指定的色彩空間和目標圖像的類型芳室。比如說CIE L*a*b*色彩空間中的L 通道表示每個像素的亮度,范圍是0~100刹勃;在使用8 位圖像時堪侯,它的范圍就會調(diào)整為0~255。a 通道和b 通道表示色度組件荔仁,這些通道包含了像素的顏色信息伍宦,與亮度無關。它們的值的范圍是 -127~127乏梁;對于8 位圖像次洼,為了適應0~255 的區(qū)間,每個值會加上128遇骑。但是要注意卖毁,進行8 位顏色轉換時會產(chǎn)生舍入誤差,因此轉換過程并不是完全可逆的落萎。

CIE L*u*v*是另一種感知均勻的色彩空間亥啦。若想從BGR 轉換成CIE L*u*v*,可使用代碼COLOR_BGR2Luv练链。L*a*b*和L*u*v*對亮度通道使用同樣的轉換公式翔脱,但對色度通道則使用不同的表示法。另外媒鼓,為了實現(xiàn)視覺感知上的均勻届吁,這兩種色彩空間都扭曲了RGB 的顏色范圍,所以這些轉換過程都是非線性的(因此計算量巨大)绿鸣。

此外還有CIE XYZ 色彩空間(用代碼CV_BGR2XYZ 表示)疚沐。它是一種標準色彩空間,用與設備無關的方式表示任何可見顏色潮模。在L*a*b*和L*u*v*色彩空間的計算中亮蛔,用XYZ 色彩空間作為一種中間表示法。RGB 與XYZ 之間的轉換是線性的再登。還有一點非常有趣尔邓,就是Y 通道對應著圖像的灰度版本。

HSV 和HLS 這兩種色彩空間很有意思锉矢,它們把顏色分解成加值的色調(diào)和飽和度組件或亮度組件梯嗽。人們用這種方式來描述的顏色會更加自然。下一節(jié)將介紹這種色彩空間沽损。


3.5 用色調(diào)灯节、飽和度和亮度表示顏色

為了能讓用戶用更直觀的屬性描述顏色,我們引入了基于色調(diào)、飽和度和亮度的色彩空間炎疆。本節(jié)將把色調(diào)卡骂、飽和度和亮度作為描述顏色的方法,并對這些概念加以探討形入。

cv::cvtColor 函數(shù)把BGR 圖像轉換成另一種色彩空間全跨。這里使用轉換代碼COLOR_BGR2HSV:

// 轉換成HSV 色彩空間
cv::Mat hsv;
cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);

我們可以用代碼COLOR_HSV2BGR 把圖像轉換回BGR 色彩空間。通過把圖像的通道分割到三個獨立的圖像中亿遂,我們可以直觀地看到每一種HSV 組件浓若,方法如下所示:

// 把3 個通道分割進3 幅圖像中
std::vector<cv::Mat> channels;
cv::split(hsv,channels);
// channels[0]是色調(diào)
// channels[1]是飽和度
// channels[2]是亮度

注意第三個通道表示顏色值,即顏色亮度的近似值蛇数。因為處理的是8 位圖像挪钓,所以OpenCV會把通道值的范圍重新調(diào)節(jié)為0255(色調(diào)除外,它的范圍被調(diào)節(jié)為0180)耳舅。

城堡圖的亮度通道顯示如下碌上。

value.jpg

飽和度通道顯示如下辑畦。

Saturation.jpg

色調(diào)通道如下暂筝。

Hue.jpg

之所以要引入色調(diào)/飽和度/亮度的色彩空間概念鸵赖,是因為人們喜歡憑直覺分辨各種顏色,而它與這種方式吻合。

色調(diào)(hue)表示主色轮洋,我們使用的顏色名稱(例如綠色块促、黃色和紅色)就對應了不同的色調(diào)值薇搁;

飽和度(saturation)表示顏色的鮮艷程度宏娄,柔和的顏色飽和度較低,而彩虹的顏色飽和度就很高巍杈;

最后刺洒,亮度(brightness)是一個主觀的屬性纸泡,表示某種顏色的光亮程度栏饮。

OpenCV 建議的兩種直覺色彩空間的實現(xiàn)是HSV 和HLS 色彩空間,它們的轉換公式略有不同磷仰,但是結果非常相似袍嬉。

亮度成分可能是最容易解釋的。在OpenCV 對HSV 的實現(xiàn)中灶平,它被定義為三個BGR 成分中的最大值伺通。

OpenCV 用一個公式來計算飽和度,該公式基于BGR 組件的最小值和最大值:
s=\frac{max(R,G,B)-min(R,G,B)}{max(R,G,B)}

灰度顏色包含的R逢享、G罐监、B 的成分是相等的,相當于一種極不飽和的顏色瞒爬,因此它的飽和度是0(飽和度是一個0~1.0 的值)弓柱。對于8 位圖像,飽和度被調(diào)節(jié)成一個0~255 的值侧但,并且作為灰度圖像顯示的時候矢空,較亮區(qū)域對應的顏色具有較高的飽和度。

在黑色區(qū)域中計算得到的飽和度是不可靠的禀横,沒有參考價值屁药。

顏色的色調(diào)通常用0~360 的角度來表示,其中紅色是0 度柏锄。對于8 位圖像酿箭,OpenCV 把角度除以2,以適合單字節(jié)的存儲范圍绢彤。因此七问,每個色調(diào)值對應指定顏色的色彩,與亮度和飽和度無關茫舶。有一點要特別注意,如果顏色的飽和度很低刹淌,它計算出來的色調(diào)就不可靠饶氏。

HSB 色彩空間通常用一個圓錐體來表示,圓錐體內(nèi)部的每個點代表一種特定的顏色有勾,角度位置表示顏色的色調(diào)疹启,到中軸線的距離表示飽和度,高度表示亮度蔼卡。圓錐體的頂點表示黑色喊崖,它的色調(diào)和飽和度是沒有意義的。

我們還可以人為生成一幅圖像,用來說明各種色調(diào)/飽和度組合荤懂。

    cv::Mat hs(128, 360, CV_8UC3);
    for (int h = 0; h < 360; h++) {
        for (int s = 0; s < 128; s++) {
            hs.at<cv::Vec3b>(s, h)[0] = h / 2; // 所有色調(diào)角度
            // 飽和度從高到低
            hs.at<cv::Vec3b>(s, h)[1] = 255 - s * 2;
            hs.at<cv::Vec3b>(s, h)[2] = 255; // 常數(shù)
        }
    }
result.jpg

從左到右表示不同的色調(diào)(0~180)茁裙,從上到下表示不同的飽和度。圖像頂端為飽和度最高的顏色节仿,底部為飽和度最低的顏色晤锥。圖中所有顏色的亮度都為255。

使用HSV 的值可以生成一些非常有趣的效果廊宪。一些用照片編輯軟件生成的色彩特效就是用這個色彩空間實現(xiàn)的矾瘾。你可以修改一幅圖像,把它的所有像素都設置為一個固定的亮度箭启,但不改變色調(diào)和飽和度壕翩。可以這樣實現(xiàn):

// 轉換成HSV 色彩空間
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV);
// 將3 個通道分割到3 幅圖像中
std::vector<cv::Mat> channels;
cv::split(hsv,channels);
// 所有像素的顏色亮度通道將變成255
channels[2]= 255;
// 重新合并通道
cv::merge(channels,hsv);
// 轉換回BGR
cv::Mat newImage;
cv::cvtColor(hsv,newImage,CV_HSV2BGR);
result.jpg

顏色用于檢測:膚色檢測

在搜尋特定顏色的物體時傅寡,HSV 色彩空間也是非常實用的戈泼。一個例子是膚色檢測,檢測到的皮膚區(qū)域可作為圖像中有人存在的標志赏僧。手勢識別就經(jīng)常使用膚色檢測確定手的位置大猛。

膚色檢測領域的大量研究已經(jīng)表明,來自不同人種的人群的皮膚顏色淀零,可以在色調(diào)-飽和度色彩空間中很好地歸類挽绩。這里,我們將只使用色調(diào)和飽和度值來識別膚色驾中。

girl.jpg

我們定義了一個基于數(shù)值區(qū)間(最小和最大色調(diào)唉堪、最小和最大飽和度)的函數(shù),把圖像中的像素分為皮膚和非皮膚兩類:

void detectHScolor(const cv::Mat& image, // 輸入圖像
    double minHue, double maxHue, // 色調(diào)區(qū)間
    double minSat, double maxSat, // 飽和度區(qū)間
    cv::Mat& mask) { // 輸出掩碼

    // 轉換到HSV 空間
    cv::Mat hsv;
    cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);

    // 將3 個通道分割到3 幅圖像
    std::vector<cv::Mat> channels;
    cv::split(hsv, channels);

    // 色調(diào)掩碼
    cv::Mat mask1; // 小于maxHue
    cv::threshold(channels[0], mask1, maxHue, 255, cv::THRESH_BINARY_INV);
    cv::Mat mask2; // 大于minHue
    cv::threshold(channels[0], mask2, minHue, 255, cv::THRESH_BINARY);

    cv::Mat hueMask; // 色調(diào)掩碼
    if (minHue < maxHue)
        hueMask = mask1 & mask2;
    else // 如果區(qū)間穿越0 度中軸線
        hueMask = mask1 | mask2;

    // 飽和度掩碼
    // 從minSat 到maxSat
    cv::Mat satMask; // 飽和度掩碼
    cv::inRange(channels[1], minSat, maxSat, satMask);

    // 組合掩碼
    mask = hueMask & satMask;
}

如果在處理時有了大量的皮膚(以及非皮膚)樣本肩民,我們就可以使用概率方法估算在皮膚樣本中和非皮膚樣本中發(fā)現(xiàn)指定顏色的可能性唠亚。此處,我們依據(jù)經(jīng)驗定義了一個合理的色調(diào)-0飽和度區(qū)間(記住持痰,8 位版本的色調(diào)在0180灶搜,飽和度在0255):

// 檢測膚色
    cv::Mat mask;
    detectHScolor(image, 160, 10, // 色調(diào)為320 度~20 度
        25, 166, // 飽和度為~0.1~0.65
        mask);
    // 顯示使用掩碼后的圖像
    cv::Mat detected(image.size(), CV_8UC3, cv::Scalar(0, 0, 0));
    image.copyTo(detected, mask);
result.jpg

注意,為了簡化工窍,我們在檢測時沒有考慮顏色的亮度割卖。在實際應用中,排除較高亮度的顏色可以降低把明亮的淡紅色誤認為皮膚的可能性患雏。顯然鹏溯,要想對皮膚顏色進行可靠和準確的檢測,還需要更加精確的分析淹仑。對不同的圖像進行檢測丙挽,也很難保證效果都好肺孵,因為攝影時影響彩色再現(xiàn)的因素有很多,如白平衡和光照條件等颜阐。盡管如此平窘,用這種只使用色調(diào)/飽和度信息做初步檢測的方法也能得到一個比較令人滿意的結果。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞬浓,一起剝皮案震驚了整個濱河市初婆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猿棉,老刑警劉巖磅叛,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異萨赁,居然都是意外死亡弊琴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門杖爽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敲董,“玉大人,你說我怎么就攤上這事慰安∫刚” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵化焕,是天一觀的道長萄窜。 經(jīng)常有香客問我,道長撒桨,這世上最難降的妖魔是什么查刻? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮凤类,結果婚禮上穗泵,老公的妹妹穿的比我還像新娘。我一直安慰自己谜疤,他們只是感情好佃延,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茎截,像睡著了一般苇侵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上企锌,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音于未,去河邊找鬼撕攒。 笑死陡鹃,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的抖坪。 我是一名探鬼主播萍鲸,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼擦俐!你這毒婦竟也來了脊阴?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚯瞧,失蹤者是張志新(化名)和其女友劉穎嘿期,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埋合,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡备徐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了甚颂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜜猾。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖振诬,靈堂內(nèi)的尸體忽然破棺而出蹭睡,到底是詐尸還是另有隱情,我是刑警寧澤赶么,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布肩豁,位于F島的核電站,受9級特大地震影響禽绪,放射性物質發(fā)生泄漏蓖救。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一印屁、第九天 我趴在偏房一處隱蔽的房頂上張望循捺。 院中可真熱鬧,春花似錦雄人、人聲如沸从橘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恰力。三九已至,卻和暖如春旗吁,著一層夾襖步出監(jiān)牢的瞬間踩萎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工很钓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留香府,地道東北人董栽。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像企孩,于是被迫代替她去往敵國和親锭碳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容