本文将介绍计算机视觉中常用到的色彩空间,并将其用于图像分割中。我们都知道在绘画时可以使用红色、黄色和蓝色这三种原色生成不同的颜色,这些颜色就定义了一个色彩空间。我们将品红色的量定义为X轴、青色的量定义为Y轴、黄色的量定义为Z轴,这样就得到一个三维空间,每种可能的颜色都在这个三维空间中对应于唯一的位置。
但是,这不是唯一的一个色彩空间。例如,当在计算机上显示颜色时,通常使用RGB(红色、绿色、蓝色)色彩空间,或使用色相、饱和度和明度表示的HSV色彩空间。首先来看本文将用到的样例图:
上图是不同光照下得到的物体成像,左图(室外)和右图(室内)。
在RGB色彩空间中,所有颜色都是分量R、G和B的线性组合,而某颜色的该三个分量值与物体表面的光照有关。现将三个通道分离:
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
// OpenCV读取的默认格式为BGR
Mat image = imread("figures/rub01.jpg");
// 存放各通道值
vector<Mat> listChannel;
// 使用split函数分离通道
split(image, listChannel);
for (int i = 0; i < listChannel.size(); ++i) {
imwrite("channel" + to_string(i) + "_.jpg", listChannel[i]);
}
return 0;
}
在将原彩色图像的通道分离后,由于此时的图像是单通道的,所以以灰度图的形式显示。从上述结果我们可以观察到,在原图为蓝色的地方,蓝色通道图中对应位置接近于白色,其他颜色亦如此。
在LAB色彩空间中含有三个分量:L(亮度)、A和B表示颜色对立度,A表示从绿色到洋红色的分量、B表示从蓝色到黄色的分量。现将三个通道分离:
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
// OpenCV读取的默认格式为BGR
Mat image = imread("figures/rub01.jpg");
// 存放LAB色彩空间的图像
Mat image_lab;
// 通道转换
cvtColor(image, image_lab, COLOR_BGR2Lab);
// 存放各通道值
vector<Mat> listChannel;
// 使用split函数分离通道
split(image_lab, listChannel);
for (int i = 0; i < listChannel.size(); ++i) {
imwrite("channel" + to_string(i) + "_.jpg", listChannel[i]);
}
return 0;
}
可以看到光照的变换主要影响了L分量的值,而对其他两个分量的值影响不大。
YCrCb色彩空间是从BGR色彩空间派生而来,其中Y表示RGB色彩空间下亮度经伽马矫正后的值,Cr分量表示红色离亮度值有多远,Cb表示蓝色离亮度值有多远。现将三个通道分离:
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
// OpenCV读取的默认格式为BGR
Mat image = imread("figures/rub01.jpg");
// 存放YCrCb色彩空间的图像
Mat image_lab;
// 通道转换
cvtColor(image, image_lab, COLOR_BGR2YCrCb);
// 存放各通道值
vector<Mat> listChannel;
// 使用split函数分离通道
split(image_lab, listChannel);
for (int i = 0; i < listChannel.size(); ++i) {
imwrite("channel" + to_string(i) + "_.jpg", listChannel[i]);
}
return 0;
}
如上图,Y通道的结果与LAB类似,我们以Cr为例说明,其表示红色离亮度值有多远,原红色位置在Cr通道的图像中的相应位置更白;Cb通道的值也具有类似性质。
HSV色彩空间由H(色相)、S(饱和度)和V(明度)组成。现将三个通道分离:
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
// OpenCV读取的默认格式为BGR
Mat image = imread("figures/rub01.jpg");
// 存放HSV色彩空间的图像
Mat image_lab;
// 通道转换
cvtColor(image, image_lab, COLOR_BGR2HSV);
// 存放各通道值
vector<Mat> listChannel;
// 使用split函数分离通道
split(image_lab, listChannel);
for (int i = 0; i < listChannel.size(); ++i) {
imwrite("channel" + to_string(i) + "_.jpg", listChannel[i]);
}
}
输入图像及输出结果:
两幅图像的H分量十分接近,表明光照色相的影响不大,S分量亦如此。而V表示明度,在不同光照条件下V分量的差别较大。
#include
#include
using namespace cv;
using namespace std;
// 全局变量
Mat image, placeholder;
// 鼠标响应事件
void onMouse(int event, int x, int y, int flags, void* userdata) {
// 移动
if (event == EVENT_MOUSEMOVE) {
// 存放bgr各通道的值,Vec3b表示8U类型的RGB彩色图像格式(0-255)
// 读取位置为(y,x)处的像素值,并将各通道值存放在向量bgrPixel中
// 然后将向量转换成Mat格式
Vec3b bgrPixel(image.at<Vec3b>(y, x));
Mat3b bgr(bgrPixel);
// 存放LAB、YCrCb、HSV各通道的值
Mat3b lab, ycrcb, hsv;
// 将颜色通道由BGR转换到其它色彩空间
cvtColor(bgr, lab, COLOR_BGR2Lab);
cvtColor(bgr, ycrcb, COLOR_BGR2YCrCb);
cvtColor(bgr, hsv, COLOR_BGR2HSV);
// 同上
Vec3b labPixel(lab.at<Vec3b>(0, 0));
Vec3b ycrcbPixel(ycrcb.at<Vec3b>(0, 0));
Vec3b hsvPixel(hsv.at<Vec3b>(0, 0));
// 定义占位符,根据原图大小定义高,宽为400,格式为三通道的8U图像格式
placeholder = Mat::zeros(image.rows, 400, CV_8UC3);
// 填充占位符,分别显示各通道的值
putText(placeholder, format("BGR [%d, %d, %d]", bgrPixel[0], bgrPixel[1], bgrPixel[2]), Point(20, 70), FONT_HERSHEY_COMPLEX, .9, Scalar(255, 255, 255), 1);
putText(placeholder, format("LAB [%d, %d, %d]", labPixel[0], labPixel[1], labPixel[2]), Point(20, 140), FONT_HERSHEY_COMPLEX, .9, Scalar(255, 255, 255), 1);
putText(placeholder, format("YCrCb [%d, %d, %d]", ycrcbPixel[0], ycrcbPixel[1], ycrcbPixel[2]), Point(20, 210), FONT_HERSHEY_COMPLEX, .9, Scalar(255, 255, 255), 1);
putText(placeholder, format("HSV [%d, %d, %d]", hsvPixel[0], hsvPixel[1], hsvPixel[2]), Point(20, 280), FONT_HERSHEY_COMPLEX, .9, Scalar(255, 255, 255), 1);
// size
Size size1 = image.size();
Size size2 = placeholder.size();
// 合并结果并使其显示在单幅图像上
Mat combinedResult(size1.height, size1.width + size2.width, CV_8UC3);
// 左边显示图像
Mat left(combinedResult, Rect(0, 0, size1.width, size1.height));
image.copyTo(left);
// 右边显示像素值信息
Mat right(combinedResult, Rect(size1.width, 0, size2.width, size2.height));
placeholder.copyTo(right);
// 显示
imshow("Press P for Previous, N for Next Image", combinedResult);
}
}
int main(int argc, char** argv) {
// 定义图像索引、图像数量、格式化输出
int image_ind = 0;
int num_images = 10;
char filename[20];
// 格式化输出
sprintf_s(filename, "figures/rub%02d.jpg", image_ind % num_images);
// 读取图像
image = imread(filename);
// resize
Size rsize(400, 400);
resize(image, image, rsize);
// 创建空窗体
namedWindow("Press P for Previous, N for Next Image", WINDOW_AUTOSIZE);
// 创建鼠标的回调函数
setMouseCallback("Press P for Previous, N for Next Image", onMouse);
// 显示图像
imshow("Press P for Previous, N for Next Image", image);
// 获取键盘操作
while (1)
{
// 键盘响应
char k = waitKey(1) & 0xFF;
// 退出
if (k == 27) {
break;
}
// 下一幅图像
if (k == 'n') {
// 索引加1
++image_ind;
// 格式化输出
sprintf_s(filename, "figures/rub%02d.jpg", image_ind % num_images);
// 重新读取图像
image = imread(filename);
// resize
resize(image, image, rsize);
}
// 上一幅图像
else if (k == 'p') {
// 索引减1
--image_ind;
// 格式化输出
sprintf_s(filename, "figures/rub%02d.jpg", image_ind % num_images);
// 重新读取图像
image = imread(filename);
// resize
resize(image, image, rsize);
}
}
return 0;
}
输出结果:
在结果中,左图显示输入图像,右图显示鼠标位置处像素各通道的值。注意:由于程序书写的缘故,必须移动鼠标后才能切换到上一幅或下一幅图像,否则会出错。