文章目錄
- 介紹
- 使用策略設計模式比較顏色
- 實現方案
- 計算兩個顏色向量之間的距離
- 1. 簡單方法:曼哈頓距離計算(Manhattan Distance)
- 2.使用 OpenCV 的 cv::norm 函數
- 3.使用 OpenCV 的 cv::absdiff 函數
- 錯誤示例
- 使用 OpenCV 函數實現顏色檢測
- 實現方案
- 閾值處理:cv::threshold
- cv::THRESH_BINARY_INV 模式:
- 其他有用的模式
- 使用 cv::floodFill 函數
- cv::floodFill 的工作原理
- 實現方案
- 應用場景
- 使用函數對象
- 實現方案
- 適用場景
- OpenCV 的算法基類:cv::Algorithm
- cv::Algorithm 的核心功能
- cv::Algorithm 的多態性
- 優點
- 適用場景
- 使用GrabCut算法分割圖像
- 圖像分割實現步驟
- 實現方案
- 轉換顏色
- RGB 顏色空間
- RGB 的局限性
- CIE Lab* 顏色空間
- 顏色轉換的步驟
- 其他常用顏色空間轉換
- 工作原理
- 用色調、飽和度和亮度表示顏色
- 顏色空間轉換及可視化HSV各分量
- HSV顏色空間簡介
- 色調 (Hue)
- 飽和度 (Saturation)
- 亮度 (Value)
- OpenCV 中的 HSV 實現
- 生成 HSV 色彩表
- 修改圖像亮度
- 將BGR圖像轉換為HSV顏色空間
- 從HSV轉回BGR顏色空間
- 分離并可視化HSV各通道
- 實現案例
- 使用顏色進行膚色檢測
- 案例實現
介紹
- 使用策略設計模式比較顏色
- 使用GrabCut算法分割圖像
- 轉換顏色表示法
- 使用色調、飽和度和亮度表示顏色
使用策略設計模式比較顏色
假設需要構建一個簡單的算法,用于識別圖像中所有具有指定顏色的像素。為實現這一目標,該算法需要接受一張圖像和一種顏色作為輸入,并返回一張二值圖像,顯示哪些像素具有指定的顏色。此外,還需要在運行算法之前指定一個容差值(tolerance),以決定接受某種顏色的寬松程度。
為了實現這個目標,將使用策略設計模式(Strategy Design Pattern)。這是一種面向對象的設計模式,非常適合將算法封裝到類中。通過這種方式,可以輕松替換某個算法,或將多個算法串聯在一起以構建更復雜的過程。此外,這種模式通過隱藏盡可能多的復雜性,為算法提供了一個直觀的編程接口,從而簡化了算法的部署和使用。
實現方案
#include <opencv2/opencv.hpp>
#include <iostream>class ColorDetector
{
public:void setTargetColor(int r, int g, int b); // 設置目標顏色void setColorDistanceThreshold(int distance); // 設置顏色距離閾值cv::Mat process(const cv::Mat &image); // 處理圖像并返回結果private:bool matchColor(const cv::Vec3b& pixel) const; // 比較顏色private:cv::Vec3b targetColor; // 目標顏色int tolerance{30}; // 容差,默認值
};void ColorDetector::setTargetColor(int r, int g, int b)
{targetColor = cv::Vec3b(b, g, r); // 注意OpenCV中的顏色順序是BGR
}void setColorDistanceThreshold(int distance) {if (distance < 0) distance = 0;tolerance= distance;}bool ColorDetector::matchColor(const cv::Vec3b& pixel) const
{return std::abs(pixel[0] - targetColor[0]) <= tolerance &&std::abs(pixel[1] - targetColor[1]) <= tolerance &&std::abs(pixel[2] - targetColor[2]) <= tolerance;
}cv::Mat ColorDetector::process(const cv::Mat &image)
{cv::Mat result(image.size(), CV_8UC1); // 輸出的二值圖像for (int i = 0; i < image.rows; ++i) {for (int j = 0; j < image.cols; ++j) {if (matchColor(image.at<cv::Vec3b>(i, j))) {result.at<uchar>(i, j) = 255; // 白色表示匹配} else {result.at<uchar>(i, j) = 0; // 黑色表示不匹配}}}return result;
}
#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main()
{// 1. 創建圖像處理器對象ColorDetector cdetect;// 2. 讀取輸入圖像cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) return 0;// 3. 設置輸入參數cdetect.setTargetColor(230, 190, 130); // 這里假設要檢測藍色天空// 4. 處理圖像并顯示結果cv::namedWindow("result");cv::Mat result = cdetect.process(image);cv::imshow("result", result);cv::imshow("image", image);cv::waitKey();return 0;
}
OpenCV提供了一些內置函數可以完成類似的任務,例如提取具有特定顏色的連通區域。此外,策略設計模式的實現還可以通過函數對象進一步完善。最后,OpenCV定義了一個基類 cv::Algorithm,它實現了策略設計模式的核心概念。
計算兩個顏色向量之間的距離
1. 簡單方法:曼哈頓距離計算(Manhattan Distance)
計算方式是將每個顏色通道的絕對差值相加。
優點是實現簡單且計算效率高,但在某些情況下可能無法準確反映顏色之間的感知差異。
return abs(color[0] - target[0]) +abs(color[1] - target[1]) +abs(color[2] - target[2]);
2.使用 OpenCV 的 cv::norm 函數
OpenCV 提供了一個名為 cv::norm 的函數,可以用來計算向量的歐幾里得范數(Euclidean Norm)。我們可以使用它來計算顏色向量之間的距離:
return static_cast<int>(cv::norm(cv::Vec3i(color[0] - target[0],color[1] - target[1],color[2] - target[2])));
這里使用了 cv::Vec3i(一個包含三個整數值的向量),因為顏色通道相減的結果可能是負數,而 cv::Vec3b 是無符號類型,無法正確表示負數
通過這種方式,可以獲得更精確的結果。需要注意的是,cv::norm 默認計算的是歐幾里得距離。
3.使用 OpenCV 的 cv::absdiff 函數
錯誤示例
return static_cast<int>(cv::norm<uchar, 3>(color - target)); // 錯誤!
OpenCV 的算術運算符會調用 saturate_cast 函數來確保結果始終在輸入類型的范圍內(在這里是 uchar 類型)。如果目標值大于對應的顏色值,結果會被截斷為 0,而不是預期的負值。這會導致距離計算不準確。
為了避免上述問題,可以使用 OpenCV 提供的 cv::absdiff 函數來計算兩個向量之間的絕對差值:
// cv::absdiff 會逐元素地計算兩個向量之間的絕對差值。
// cv::sum 函數會對結果向量的所有元素求和,返回一個標量值。
cv::Vec3b dist;
cv::absdiff(color, target, dist);
return cv::sum(dist)[0];
這種方法可以正確計算距離,但它需要兩次函數調用(cv::absdiff 和 cv::sum),因此效率較低。
盡管 cv::absdiff 和 cv::sum 提供了一種正確且通用的解決方案,但對于大規模圖像處理任務來說,效率可能是一個問題。在這種情況下,手動實現距離計算通常是更好的選擇(使用方法1)
使用 OpenCV 函數實現顏色檢測
上文展示了一種使用 OpenCV 函數實現顏色檢測的方法。與手動編寫循環相比,這種方法可以更快速地構建復雜的應用程序,并且通常具有更高的效率(得益于 OpenCV 的優化)。需要注意的是,當涉及多個中間步驟時,可能會消耗更多的內存。
實現方案
cv::Mat ColorDetector::process(const cv::Mat &image)
{cv::Mat output;// 1. 計算圖像與目標顏色之間的絕對差值cv::absdiff(image, cv::Scalar(target), output);// 2. 將通道分離為三個獨立的圖像// 將 RGB 圖像的三個通道分離出來,以便后續對每個通道單獨處理std::vector<cv::Mat> images;cv::split(output, images);// 3. 將三個通道相加(可能發生飽和)// OpenCV 默認會對結果應用飽和操作(saturate_cast),因此如果某個像素的總和超過 255,它會被截斷為 255。output = images[0] + images[1] + images[2];// 4. 應用閾值處理生成二值圖像cv::threshold(output, // 輸入/輸出圖像output, // 輸出圖像maxDist, // 閾值(必須小于 256)255, // 最大值cv::THRESH_BINARY_INV // 閾值模式);return output;
}
閾值處理:cv::threshold
cv::THRESH_BINARY_INV 模式:
- 像素值 ≤ 閾值時,設置為 255(白色)。
- 像素值 > 閾值時,設置為 0(黑色)。
其他有用的模式
- cv::THRESH_TOZERO:保留像素值大于閾值的部分,其余部分設為 0。
- cv::THRESH_TOZERO_INV:保留像素值小于或等于閾值的部分,其余部分設為 0。
使用 cv::floodFill 函數
上述案例中,使用了逐像素比較的方式來識別圖像中顏色接近目標顏色的像素。
OpenCV 提供了一個功能類似的函數 cv::floodFill。cv::floodFill 的核心思想是基于連通區域進行顏色提取,而不僅僅是逐像素判斷。
cv::floodFill 的工作原理
1.種子點(Seed Point)
- 需要指定一個起始像素位置(稱為種子點),該點的顏色將作為參考顏色。
- 從種子點開始,算法會檢查其鄰居像素,并根據容差參數決定是否接受這些鄰居像素。
2.連通性(Connectivity)
- 如果某個鄰居像素被接受,則繼續檢查該像素的鄰居,依此類推。
- 這樣,算法可以提取出一個連通的顏色區域。
3.顏色容差(Tolerance Parameters)
- 容差參數決定了顏色相似度的標準。用戶可以為高于和低于參考顏色的值分別設置不同的閾值。
- 在固定范圍模式(cv::FLOODFILL_FIXED_RANGE)下,所有測試的像素都會與種子點的顏色進行比較。
- 在默認模式下,每個測試像素會與其鄰居的顏色進行比較。
4.重新著色(Repainting)
- 被接受的像素會被重新著色為指定的顏色(通過第三個參數指定)。
實現方案
cv::Mat image = cv::imread("image.jpg");// 使用 floodFill 提取天空區域
cv::floodFill(image, // 輸入/輸出圖像cv::Point(100, 50), // 種子點位置cv::Scalar(255, 255, 255), // 重新著色的顏色(白色)(cv::Rect*)0, // 返回的邊界矩形(可選) 如果需要獲取被填充區域的邊界,可以傳遞一個非空指針cv::Scalar(35, 35, 35), // 顏色下限容差cv::Scalar(35, 35, 35), // 顏色上限容差cv::FLOODFILL_FIXED_RANGE); // 固定范圍模式// cv::FLOODFILL_FIXED_RANGE:表示所有測試的像素都與種子點的顏色進行比較。// 默認模式(未指定 cv::FLOODFILL_FIXED_RANGE):表示每個測試像素與鄰居的顏色進行比較
#include <opencv2/opencv.hpp>
#include <iostream>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main()
{cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) return 0;cv::floodFill(image, // 輸入/輸出圖像cv::Point(100, 50), // 種子點位置cv::Scalar(255, 255, 255), // 重新著色的顏色(白色)(cv::Rect*)0, // 返回的邊界矩形(可選) cv::Scalar(35, 35, 35), // 顏色下限容差cv::Scalar(35, 35, 35), // 顏色上限容差cv::FLOODFILL_FIXED_RANGE); // 固定范圍模式cv::imshow("image", image);cv::waitKey();return 0;
}
算法會從種子點開始,提取出一個連通的藍色區域,并將其重新著色為白色。即使圖像中其他地方存在顏色相似的像素,只要它們沒有區域連通,就不會被識別。
應用場景
- 連通區域提取: 提取圖像中特定顏色的連通區域(如天空、水面等)。
- 對象分割:將圖像中的某個對象(如前景或背景)分割出來。
- 圖像修復:填充圖像中的小區域或孔洞。
- 交互式圖像編輯:用戶可以通過點擊圖像中的某個點來選擇并操作特定區域。
使用函數對象
在 C++ 中,通過重載 operator() 運算符,可以創建一個類,使其實例像函數一樣被調用。這種技術被稱為函數對象(Functor) 。
優點是它們結合了函數的簡潔性和類的狀態管理能力,因此非常適合用于需要保存狀態的算法。
實現方案
class ColorDetector
{
public:// 構造函數ColorDetector(uchar blue, uchar green, uchar red, int maxDist = 100): maxDist(maxDist) {setTargetColor(blue, green, red);}// 設置目標顏色void setTargetColor(uchar blue, uchar green, uchar red) {targetColor = cv::Vec3b(blue, green, red);}// 獲取最大距離int getMaxDist() const {return maxDist;}// 設置最大距離void setMaxDist(int dist) {maxDist = dist;}// 重載 operator(),實現函數對象行為cv::Mat operator()(const cv::Mat &image) {cv::Mat output;// 計算絕對差值cv::absdiff(image, cv::Scalar(targetColor), output);// 分離通道并相加std::vector<cv::Mat> channels;cv::split(output, channels);output = channels[0] + channels[1] + channels[2];// 應用閾值處理cv::threshold(output, output, maxDist, 255, cv::THRESH_BINARY_INV);return output;}private:cv::Vec3b targetColor; // 目標顏色int maxDist; // 最大距離(容差)
};
// 創建 ColorDetector 實例,并初始化目標顏色和容差
ColorDetector colordetector(230, 190, 130, 100);// 使用函數對象檢測顏色
cv::Mat image = cv::imread("image.jpg");
cv::Mat result = colordetector(image); // 像調用函數一樣調用對象
適用場景
- 需要保存狀態的算法。
- 動態配置參數的場景。
- 與 STL 算法結合使用的場景。
OpenCV 的算法基類:cv::Algorithm
cv::Algorithm 的核心功能
1. 動態創建算法實例
所有繼承自 cv::Algorithm 的算法都可以通過靜態方法動態創建。
確保算法在創建時總是處于有效狀態(即未指定的參數會被賦予默認值)。
例如,對于 cv::ORB(一種用于檢測興趣點的算法),可以通過以下方式創建實例:
cv::Ptr<cv::ORB> ptrORB = cv::ORB::create(); // 默認狀態
2. 讀取和寫入算法狀態
cv::Algorithm 提供了通用的 read 和 write 方法,用于加載或存儲算法的狀態。
這使得算法可以輕松地保存到文件中,并在需要時重新加載。
cv::FileStorage fs("orb_state.yml", cv::FileStorage::WRITE);
ptrORB->write(fs); // 將算法狀態寫入文件
fs.release();cv::Ptr<cv::ORB> newORB = cv::ORB::create();
cv::FileStorage fs2("orb_state.yml", cv::FileStorage::READ);
newORB->read(fs2.root()); // 從文件中讀取算法狀態
fs2.release();
3.專用方法
每個算法都有其特定的功能方法。例如,cv::ORB 提供了 detect 和 compute 方法,用于檢測特征點并計算描述符。
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
ptrORB->detectAndCompute(image, cv::Mat(), keypoints, descriptors);
這些方法是算法的核心計算單元,用戶可以直接調用它們來執行特定任務。
4.參數設置與獲取
算法通常包含多個內部參數,用戶可以通過專用的 setter 和 getter 方法來調整這些參數。
ptrORB->setMaxFeatures(500); // 設置最大特征點數量
int maxFeatures = ptrORB->getMaxFeatures(); // 獲取當前設置
cv::Algorithm 的多態性
如果將算法實例聲明為 cv::Ptrcv::Algorithm 類型,則無法直接調用其專用方法(如 detect 或 compute)。這是因為 cv::Algorithm 是一個通用基類,它并不知道子類的具體方法。
cv::Ptr<cv::Algorithm> algo = cv::ORB::create();
// algo->detect(...) // 錯誤!detect 方法不可用
使用具體的子類類型(如 cv::Ptrcv::ORB)來聲明指針,以便訪問其專用方法。
cv::Ptr<cv::ORB> ptrORB = cv::ORB::create();
ptrORB->detectAndCompute(image, cv::Mat(), keypoints, descriptors); // 正確!
優點
- 統一的接口,便于管理和使用。
- 動態創建機制確保算法始終處于有效狀態。
- 支持靈活的參數調整和狀態管理。
- 易于擴展,適合開發新的算法。
適用場景
- 需要動態加載或保存算法狀態的場景。
- 需要靈活調整算法參數的任務。
- 開發新的算法并希望與 OpenCV 現有框架無縫集成。
使用GrabCut算法分割圖像
GrabCut 是一種流行的圖像分割算法,特別適用于從靜態圖像中提取前景對象(例如,從一張圖片中剪切并粘貼一個對象到另一張圖片)。盡管它是一種復雜且計算成本較高的算法,但它通常能產生非常精確的結果。
圖像分割實現步驟
1.定義輸入圖像和矩形區域
定義一個矩形區域來包含前景對象。所有在這個矩形之外的像素將被標記為背景。
// 定義包圍矩形
cv::Rect rectangle(5, 70, 260, 120);
2.初始化 GrabCut 所需的數據結構
創建幾個矩陣來存儲分割結果和算法內部使用的模型。
cv::Mat result; // 分割結果(4種可能值) 每個像素可以有四種狀態之一
cv::Mat bgModel, fgModel; // 算法內部使用的模型 用于存儲算法生成的背景和前景模型
3.調用 cv::grabCut 函數
// GrabCut 分割
cv::grabCut(image, // 輸入圖像result, // 分割結果rectangle, // 包含前景對象的矩形bgModel, // 背景模型fgModel, // 前景模型5, // 迭代次數cv::GC_INIT_WITH_RECT); // 使用矩形模式 使用定義的矩形來指定前景區域
4.解釋分割結果
分割結果圖像中的每個像素可以有以下四種狀態之一:
- cv::GC_BGD:背景像素(例如,在我們的例子中,矩形外的所有像素)。
- cv::GC_FGD:前景像素(在我們的例子中沒有)。
- cv::GC_PR_BGD:可能是背景的像素。
- cv::GC_PR_FGD:可能是前景的像素(即,初始狀態下矩形內的所有像素)。
為了生成二值分割圖像,我們需要提取那些具有 cv::GC_PR_FGD 值的像素:
// 獲取可能屬于前景的像素
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);
5.提取所有前景像素
如果要提取所有前景像素(包括 cv::GC_PR_FGD 和 cv::GC_FGD),可以通過檢查第一個比特位來實現:
// 檢查第一個比特位
result = result & 1; // 如果是前景,則值為1
因為 cv::GC_FGD 和 cv::GC_PR_FGD 的值分別為 1 和 3,而其他兩個常量 cv::GC_BGD 和 cv::GC_PR_BGD 的值分別為 0 和 2。因此,通過與操作可以有效地提取前景像素。
實現方案
#include <opencv2/opencv.hpp>
#include <iostream>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main()
{cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) {std::cerr << "Error: Could not load image!" << std::endl;return -1;}// 定義包含前景對象的矩形區域cv::Rect rectangle(50, 50, 300, 200); // 根據實際情況調整矩形位置和大小// 創建用于存儲分割結果和模型的矩陣cv::Mat result; // 分割結果(4種可能值)cv::Mat bgModel, fgModel; // 背景和前景模型// 調用 GrabCut 算法cv::grabCut(image, // 輸入圖像result, // 分割結果rectangle, // 包含前景的矩形bgModel, // 背景模型fgModel, // 前景模型5, // 迭代次數cv::GC_INIT_WITH_RECT); // 使用矩形模式// 提取可能屬于前景的像素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::imshow("Original Image", image);cv::imshow("Foreground", foreground);// 保存結果cv::imwrite("foreground.jpg", foreground);// 等待用戶按鍵退出cv::waitKey(0);return 0;
}
轉換顏色
RGB 顏色空間
RGB 顏色空間基于紅(Red)、綠(Green)、藍(Blue)三種加色原理。
在數字圖像中,RGB 是默認的顏色空間,因為彩色圖像是通過紅、綠、藍濾鏡捕捉的。
RGB 的每個通道(紅、綠、藍)都經過歸一化處理:當三個通道值相等時,會生成灰度顏色,從黑色(0,0,0)到白色(255,255,255)。然而,它并不適合用來直接衡量兩種顏色之間的相似性。
RGB 的局限性
RGB 不是一個感知均勻的顏色空間。
在 RGB 空間中,兩個顏色之間的距離可能無法準確反映它們在人眼中的視覺差異。例如:
- 兩個顏色之間的距離相同,但它們看起來可能非常相似。
- 另外兩個顏色之間的距離相同,但它們看起來卻可能截然不同。
這種不一致性使得 RGB 顏色空間不適合用于需要精確比較顏色相似性的任務。
CIE Lab* 顏色空間
1.感知均勻性
- 在 CIE Lab* 中,兩個顏色之間的歐幾里得距離可以很好地反映它們在人眼中的視覺相似性。
- 距離越小,顏色看起來越相似;距離越大,顏色看起來差異越明顯。
2.分量解釋
- L* 表示亮度(Lightness),從黑到白的變化。
- a* 表示從綠色到紅色的變化。
- b* 表示從藍色到黃色的變化。
3.適用性
- 將圖像從 RGB 轉換到 CIE Lab* 后,可以直接用歐幾里得距離來衡量顏色的相似性,而無需擔心 RGB 空間的非均勻性問題。
顏色轉換的步驟
1. 將圖像從 BGR 轉換到 CIE Lab 顏色空間
// 輸入圖像(假設為BGR格式)
cv::Mat inputImage = cv::imread("input.jpg");// 創建一個矩陣存儲轉換后的圖像
cv::Mat labImage;// 使用 cv::cvtColor 進行顏色空間轉換
cv::cvtColor(inputImage, labImage, cv::COLOR_BGR2Lab);
cv::COLOR_BGR2Lab 是 BGR 到 CIE Lab* 的轉換代碼。
轉換后,labImage 包含了 Lab* 表示的圖像數據。
2. 將目標顏色從 RGB 轉換到 CIE Lab
void setTargetColor(unsigned char red, unsigned char green, unsigned char blue)
{// 創建一個單像素的臨時圖像cv::Mat tmp(1, 1, CV_8UC3);tmp.at<cv::Vec3b>(0, 0) = cv::Vec3b(blue, green, red); // 注意順序:BGR// 轉換到 CIE L*a*b*cv::cvtColor(tmp, tmp, cv::COLOR_BGR2Lab);// 提取轉換后的顏色值cv::Vec3b labColor = tmp.at<cv::Vec3b>(0, 0);
}
tmp 是一個單像素圖像,用于存儲目標顏色。
轉換后,labColor 包含了目標顏色的 CIE Lab* 值。
3. 在 CIE Lab 空間中比較顏色
在 CIE Lab* 空間中,顏色的距離可以直接用歐幾里得距離計算:
// 計算兩個顏色之間的歐幾里得距離
double colorDistance(const cv::Vec3b &color1, const cv::Vec3b &color2)
{double deltaL = color1[0] - color2[0];double deltaA = color1[1] - color2[1];double deltaB = color1[2] - color2[2];return std::sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);
}
color1 和 color2 是兩個 CIE Lab* 顏色。
返回值是兩個顏色之間的感知距離。
其他常用顏色空間轉換
轉換類型 | 代碼 | 描述 |
---|---|---|
BGR → 灰度 | cv::COLOR_BGR2GRAY | 將彩色圖像轉換為灰度圖像 |
BGR → HSV | cv::COLOR_BGR2HSV | 轉換到 HSV 顏色空間 |
BGR → YCrCb | cv::COLOR_BGR2YCrCb | 轉換到 JPEG 壓縮使用的 YCrCb 空間 |
BGR → CIE XYZ | cv::COLOR_BGR2XYZ | 轉換到設備無關的 CIE XYZ 空間 |
BGR → CIE Luv* | cv::COLOR_BGR2Luv | 轉換到另一種感知均勻顏色空間 |
// 將彩色圖像轉換為灰度圖像
cv::Mat grayImage;
cv::cvtColor(colorImage, grayImage, cv::COLOR_BGR2GRAY);
工作原理
線性與非線性變換:
- RGB ? XYZ 是線性變換。
- RGB ? CIE Lab* 或 CIE Luv* 是非線性變換(為了實現感知均勻性)。
通道范圍:
- CIE Lab*:
1. L 通道:亮度,范圍 [0, 100],在 8 位圖像中映射為 [0, 255]。
2. a 和 b 通道:色度,范圍 [-127, 127],在 8 位圖像中偏移為 [0, 255]。 - 灰度圖像:單通道,范圍 [0, 255]。
精度損失:
8 位圖像在轉換時會引入舍入誤差,因此某些轉換不可完全逆向。
用色調、飽和度和亮度表示顏色
最初考慮的是 RGB 顏色空間,盡管它在電子成像系統中是一種有效的顏色捕獲和顯示方式,但并不直觀。
人們通常用色調、亮度或色彩濃度(即顏色是鮮艷還是柔和)來描述顏色。
因此,基于色調、飽和度和亮度的顏色空間被引入,以幫助用戶通過更直觀的屬性來指定顏色。
顏色空間轉換及可視化HSV各分量
HSV顏色空間簡介
HSB 顏色空間通常用一個錐形來表示,其中內部的每個點對應一種特定的顏色。角度位置對應于顏色的色調,飽和度是與中心軸的距離,而亮度則由高度表示。錐形的頂點對應黑色,此時色調和飽和度是未定義的。
HSV(色調、飽和度、亮度)顏色空間是基于人類對顏色的自然感知而設計的。它將顏色分為三個直觀屬性:
色調 (Hue)
表示顏色的類型,例如紅色、綠色、藍色等。
在 HSV 中,色調用角度表示,范圍為 [0, 360] 度。在 OpenCV 中,為了適應 8 位圖像,范圍被壓縮到 [0, 180]。
飽和度 (Saturation)
表示顏色的純度,即顏色中灰色的比例。
高飽和度對應鮮艷的顏色,低飽和度對應接近灰色的顏色。
范圍為 [0, 1] 或 [0, 255](對于 8 位圖像)。
亮度 (Value)
表示顏色的明暗程度。
在 OpenCV 中,亮度定義為 BGR 通道的最大值。
OpenCV 中的 HSV 實現
OpenCV 提供了兩種主要的感知顏色空間:HSV 和 HLS。以下是它們的計算方式和實現細節:
- 亮度 (Value):
定義為 BGR 通道的最大值。 - 飽和度 (Saturation):
如果顏色為灰度(R=G=B),則飽和度為 0。
計算公式基于 BGR 的最大值和最小值:
S = (max(R, G, B) - min(R, G, B))/ max(R, G, B)
- 色調 (Hue):
基于 BGR 的最大值和最小值,通過三角函數計算角度。
紅色對應 0 度,綠色對應 120 度,藍色對應 240 度。
生成 HSV 色彩表
#include <opencv2/opencv.hpp>
#include <iostream>int main()
{// 創建一個 128x360 的三通道圖像cv::Mat hs(128, 360, CV_8UC3);for (int h = 0; h < 360; h++) { // 遍歷所有色調值for (int s = 0; s < 128; s++) { // 遍歷所有飽和度值// 設置每個像素的 HSV 值hs.at<cv::Vec3b>(s, h)[0] = h / 2; // 色調 (0-180)hs.at<cv::Vec3b>(s, h)[1] = 255 - s * 2; // 飽和度 (從高到低)hs.at<cv::Vec3b>(s, h)[2] = 255; // 恒定亮度}}// 將 HSV 圖像轉換為 BGR 格式以顯示cv::Mat hsvToBgr;cv::cvtColor(hs, hsvToBgr, cv::COLOR_HSV2BGR);// 顯示結果cv::imshow("HSV Color Table", hsvToBgr);cv::waitKey(0);return 0;
}
修改圖像亮度
#include <opencv2/opencv.hpp>
#include <iostream>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main()
{// 讀取輸入圖像cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) {std::cerr << "無法加載圖像,請檢查路徑!" << std::endl;return -1;}// 轉換為 HSV 顏色空間cv::Mat hsvImage;cv::cvtColor(image, hsvImage, cv::COLOR_BGR2HSV);// 分離 HSV 通道std::vector<cv::Mat> channels;cv::split(hsvImage, channels);// 將亮度通道設置為 255channels[2] = 255;// 合并通道cv::merge(channels, hsvImage);// 轉換回 BGR 顏色空間cv::Mat newImage;cv::cvtColor(hsvImage, newImage, cv::COLOR_HSV2BGR);// 顯示結果cv::imshow("Original Image", image);cv::imshow("Modified Image", newImage);cv::waitKey(0);return 0;
}
將BGR圖像轉換為HSV顏色空間
要將一個BGR格式的圖像轉換到HSV(色調Hue、飽和度Saturation、亮度Value)顏色空間,可以使用如下方法:
// 假設'image'是你的輸入BGR圖像
cv::Mat hsv;
cv::cvtColor(image, hsv, CV_BGR2HSV);
這里的CV_BGR2HSV是轉換代碼,它將BGR圖像轉換為HSV圖像。
hsv變量現在存儲了轉換后的圖像數據。
從HSV轉回BGR顏色空間
cv::Mat bgr;
cv::cvtColor(hsv, bgr, CV_HSV2BGR);
分離并可視化HSV各通道
為了更好地理解HSV顏色空間中的每個組成部分,我們可以將HSV圖像的三個通道拆分成獨立的圖像:
// 創建一個容器來保存三個通道
std::vector<cv::Mat> channels;
// 拆分HSV圖像的三個通道
cv::split(hsv, channels);// 'channels[0]' 是色調(Hue)
// 'channels[1]' 是飽和度(Saturation)
// 'channels[2]' 是亮度(Value)
由于我們處理的是8位圖像,OpenCV將這些通道的值調整到了0到255的范圍(除了色調外,它的范圍是0到180)。這使得我們可以像顯示灰度圖像一樣顯示這些通道。
實現案例
#include <opencv2/opencv.hpp>
#include <iostream>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"int main()
{// 讀取輸入圖像cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) {std::cerr << "無法加載圖像,請檢查路徑!" << std::endl;return -1;}// 轉換為HSV顏色空間cv::Mat hsvImage;cv::cvtColor(image, hsvImage, cv::COLOR_BGR2HSV);// 分離HSV的三個通道std::vector<cv::Mat> channels;cv::split(hsvImage, channels);// 提取各通道cv::Mat hue = channels[0]; // 色調 (Hue)cv::Mat saturation = channels[1]; // 飽和度 (Saturation)cv::Mat value = channels[2]; // 亮度 (Value)// 顯示原始圖像cv::imshow("Original Image", image);// 顯示色調通道 (Hue)cv::imshow("Hue Channel", hue);// 顯示飽和度通道 (Saturation)cv::imshow("Saturation Channel", saturation);// 顯示亮度通道 (Value)cv::imshow("Value Channel", value);// 等待用戶按鍵退出cv::waitKey(0);return 0;
}
使用顏色進行膚色檢測
顏色信息可以用于特定對象的初步檢測。
駕駛員輔助系統中,可以通過標準路標的顏色快速識別潛在的路標候選區域。
膚色檢測中,可以用來判斷圖像中是否存在人類,并且常用于手勢識別中通過膚色檢測來定位手的位置。
使用顏色檢測對象,通常需要以下步驟:
收集樣本數據:
- 收集大量包含目標對象的圖像樣本,這些樣本應從不同的視角和光照條件下捕獲。
- 這些樣本將用于定義分類器的參數。
選擇顏色表示方式:
- 對于膚色檢測,研究表明不同種族的膚色在色調(Hue)和飽和度(Saturation)空間中具有良好的聚類特性。因此,我們將使用色調和飽和度值來識別膚色
案例實現
#include <opencv2/opencv.hpp>
#include <iostream>#define IMG_WOMAN "princess.jpeg"
#define IMG_OLD_MAN "old_man.jpeg"// 定義膚色檢測函數
void detectHScolor(const cv::Mat& image, // 輸入圖像double minHue, double maxHue, // 色調區間double minSat, double maxSat, // 飽和度區間cv::Mat& mask) // 輸出掩碼
{// 將圖像轉換為 HSV 空間cv::Mat hsv;cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);// 分離 HSV 通道std::vector<cv::Mat> channels;cv::split(hsv, channels);// channels[0] 是色調 (Hue)// channels[1] 是飽和度 (Saturation)// channels[2] 是亮度 (Value)// 色調掩碼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; // 色調掩碼if (minHue < maxHue)hueMask = mask1 & mask2; // 如果區間未跨越零度軸elsehueMask = mask1 | mask2; // 如果區間跨越零度軸// 飽和度掩碼cv::Mat satMask; // 飽和度掩碼cv::inRange(channels[1], minSat, maxSat, satMask);// 組合掩碼mask = hueMask & satMask;
}int main()
{// 讀取輸入圖像cv::Mat image = cv::imread(IMG_OLD_MAN);if (image.empty()) {std::cerr << "無法加載圖像,請檢查路徑!" << std::endl;return -1;}// 定義膚色檢測的色調和飽和度區間cv::Mat mask;detectHScolor(image, 160, 10, // 色調范圍:320 度到 20 度(OpenCV 中縮放為 0-180)25, 166, // 飽和度范圍:~0.1 到 ~0.65mask);// 顯示檢測結果cv::Mat detected(image.size(), CV_8UC3, cv::Scalar(0, 0, 0)); // 創建黑色背景image.copyTo(detected, mask); // 將檢測到的區域復制到黑色背景上// 顯示原始圖像和檢測結果cv::imshow("Original Image", image);cv::imshow("Detected Skin", detected);cv::waitKey(0);return 0;
}