第 3 章 處理圖像的顏色


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

  • 用策略設(shè)計模式比較顏色;
  • 用GrabCut 算法分割圖像;
  • 轉(zhuǎn)換顏色表示法;
  • 用色調(diào)、飽和度和亮度表示顏色。


3.2 用策略設(shè)計模式比較顏色

假設(shè)我們要構(gòu)建一個簡單的算法,用來識別圖像中具有某種顏色的所有像素。這個算法必須輸入一幅圖像和一個顏色,并且返回一個二值圖像,顯示具有指定顏色的像素。在運(yùn)行算法前,還要指定一個參數(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.設(shè)置輸入?yún)?shù)
    cdetect.setTargetColor(230,190,130); // 這里表示藍(lán)天
    // 4.處理圖像并顯示結(jié)果
    cv::namedWindow("result");
    cv::Mat result = cdetect.process(image);
    cv::imshow("result",result);
    cv::waitKey(0);

    return 0;
}
result.jpg

這個算法的核心過程非常簡單,只是對每個像素進(jìn)行循環(huán)掃描,把它的顏色和目標(biāo)顏色做比較。可以這樣寫這個循環(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) {

          // 比較與目標(biāo)顏色的差距
          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; // 目標(biāo)顏色
    cv::Mat result; // 存儲二值映像結(jié)果的圖像

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

    // 取得顏色差距的閾值
    int getColorDistanceThreshold() const {
        return maxDist;
    }
    
    // 設(shè)置需要檢測的顏色
    void setTargetColor(uchar blue, uchar green, uchar red) {
        target = cv::Vec3b(blue, green, red);
    }
    // 設(shè)置需要檢測的顏色
    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]);
    }
    // 計算與目標(biāo)顏色的差距
    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)中使用迭代器的方法來進(jìn)行計算。還有一種做法是調(diào)用OpenCV 的系列函數(shù),也能得到一樣的結(jié)果。因此,檢測顏色的方法還可以這樣寫:

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

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

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

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

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

floodFill 函數(shù)

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

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

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

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

result.jpg


3.3 用GrabCut 算法分割圖像

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

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

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

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

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

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

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

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

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

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

// 取得標(biāo)記為“可能屬于前景”的像素
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); // 不復(fù)制背景像素

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

// 用“按位與”運(yùn)算檢查第一位
result= result&1; // 如果是前景像素,結(jié)果為1

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

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 算法通過以下步驟進(jìn)行背景/前景分割。

首先,把所有未標(biāo)記的像素臨時標(biāo)為前景(cv::GC_PR_FGD)。基于當(dāng)前的分類情況,算法把像素劃分為多個顏色相似的組(即K 個背景組和K 個前景組)。

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

在此過程中,將試圖連接具有相似標(biāo)記的像素,并且避免邊緣出現(xiàn)在強(qiáng)度相對均勻的區(qū)域。使用Graph Cuts 算法可以高效地解決這個優(yōu)化問題,它尋找最優(yōu)解決方案的方法是:把問題表示成一幅連通的圖形,然后在圖形上進(jìn)行切割,以形成最優(yōu)的形態(tài)。分割完成后,像素會有新的標(biāo)記。然后重復(fù)這個分組過程,找到新的最優(yōu)分割方案,如此反復(fù)。

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


3.4 轉(zhuǎn)換顏色表示法

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

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

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

// 將目標(biāo)顏色轉(zhuǎn)換成Lab 色彩空間
cv::cvtColor(tmp, tmp, cv::COLOR_BGR2Lab);

在將圖像從一個色彩空間轉(zhuǎn)換到另一個色彩空間時,會在每個輸入像素上做一個線性或非線性的轉(zhuǎn)換,以得到輸出像素。

實(shí)際的像素值范圍取決于指定的色彩空間和目標(biāo)圖像的類型。比如說CIE L*a*b*色彩空間中的L 通道表示每個像素的亮度,范圍是0~100;在使用8 位圖像時,它的范圍就會調(diào)整為0~255。a 通道和b 通道表示色度組件,這些通道包含了像素的顏色信息,與亮度無關(guān)。它們的值的范圍是 -127~127;對于8 位圖像,為了適應(yīng)0~255 的區(qū)間,每個值會加上128。但是要注意,進(jìn)行8 位顏色轉(zhuǎn)換時會產(chǎn)生舍入誤差,因此轉(zhuǎn)換過程并不是完全可逆的。

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

此外還有CIE XYZ 色彩空間(用代碼CV_BGR2XYZ 表示)。它是一種標(biāo)準(zhǔn)色彩空間,用與設(shè)備無關(guān)的方式表示任何可見顏色。在L*a*b*和L*u*v*色彩空間的計算中,用XYZ 色彩空間作為一種中間表示法。RGB 與XYZ 之間的轉(zhuǎn)換是線性的。還有一點(diǎn)非常有趣,就是Y 通道對應(yīng)著圖像的灰度版本。

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


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

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

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

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

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

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

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

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

value.jpg

飽和度通道顯示如下。

Saturation.jpg

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

Hue.jpg

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

色調(diào)(hue)表示主色,我們使用的顏色名稱(例如綠色、黃色和紅色)就對應(yīng)了不同的色調(diào)值;

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

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

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

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

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

灰度顏色包含的R、G、B 的成分是相等的,相當(dāng)于一種極不飽和的顏色,因此它的飽和度是0(飽和度是一個0~1.0 的值)。對于8 位圖像,飽和度被調(diào)節(jié)成一個0~255 的值,并且作為灰度圖像顯示的時候,較亮區(qū)域?qū)?yīng)的顏色具有較高的飽和度。

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

顏色的色調(diào)通常用0~360 的角度來表示,其中紅色是0 度。對于8 位圖像,OpenCV 把角度除以2,以適合單字節(jié)的存儲范圍。因此,每個色調(diào)值對應(yīng)指定顏色的色彩,與亮度和飽和度無關(guān)。有一點(diǎn)要特別注意,如果顏色的飽和度很低,它計算出來的色調(diào)就不可靠。

HSB 色彩空間通常用一個圓錐體來表示,圓錐體內(nèi)部的每個點(diǎn)代表一種特定的顏色,角度位置表示顏色的色調(diào),到中軸線的距離表示飽和度,高度表示亮度。圓錐體的頂點(diǎn)表示黑色,它的色調(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 的值可以生成一些非常有趣的效果。一些用照片編輯軟件生成的色彩特效就是用這個色彩空間實(shí)現(xiàn)的。你可以修改一幅圖像,把它的所有像素都設(shè)置為一個固定的亮度,但不改變色調(diào)和飽和度。可以這樣實(shí)現(xiàn):

// 轉(zhuǎ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);
// 轉(zhuǎn)換回BGR
cv::Mat newImage;
cv::cvtColor(hsv,newImage,CV_HSV2BGR);
result.jpg

顏色用于檢測:膚色檢測

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

膚色檢測領(lǐ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) { // 輸出掩碼

    // 轉(zhuǎn)換到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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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