目标
HSV色彩空间介绍
使用InRange操作阈值
基于HSV色彩空间的像素值范围检测对象
生成渐变色
HSV色彩空间
HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。HSV色系对用户来说是一种直观的颜色模型,对于颜色,人们直观的会问”什么颜色?深浅如何?明暗如何?“,而HSV色系则直观的表示了这些信息。
每一种颜色都是由色相(Hue,简H),饱和度(Saturation,简S)和色明度(Value,简V)所表示的。这个模型中颜色的参数分别是:色调(H),饱和度(S),亮度(V)。
色调H参数表示色彩信息,即所处的光谱颜色的位置。该参数用一角度量来表示,取值范围为0°~360°。若从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,紫色为300°;
饱和度S:取值范围为0.0~1.0;
亮度V:取值范围为0.0(黑色)~1.0(白色)。
H:(Hue色调、色相)、S:(Saturation饱和度)、V:(Value高度)。HSV对于需要按颜色分割对象的场合十分有用。饱和度(S)的范围从不饱和到代表灰色的阴影和完全饱和(没有白色成分)。亮度(V)通道描述的是亮度或强度。如图(图片来源官网)
相对于RGB色彩空间,通过颜色是很难分割图像中的对象的。
InRange范围判断
函数说明:检查数组元素是否位于其他两个数组的元素之间。
对单通道的每个元素
dst(I)=lowerb(I)0≤src(I)0≤upperb(I)0对于三通道
dst(I)=lowerb(I)0≤src(I)0≤upperb(I)0 ∧ lowerb(I)1≤src(I)1≤upperb(I)1 ∧ lowerb(I)2≤src(I)2≤upperb(I)2
//函数原型1
void InRange(InputArray src,
InputArray lowerb,
InputArray upperb,
OutputArray dst)
//函数原型2
void InRange(InputArray src,
Scalar lowerb,
Scalar upperb,
OutputArray dst)
参数 | 说明 |
---|---|
InputArray src | 输入图像 |
InputArray lowerb Scalar lowerb | 范围下限(包含) |
InputArray upperb Scalar upperb | 范围上限(包含) |
OutputArray dst | 输出图像(在lowerb与upperb范围内的值为255,否则为0) |
OpenCV中的HSV
OpenCV中使用的HSV的取值范围是H:[0,180],S:[0,255],V:[0,255],所以,各颜色的范围如下图所示:
将HSV中各颜色的边界值绘制成颜色块
1、将上表定义为矩阵
/// <summary>
/// HSV颜色范围
/// </summary>
/// <returns></returns>
private Mat GetHSVRange()
{
//使用HSV定义的各颜色范围 6个byte为一组,
//前三个是对应颜色的最小值,后三个是对应颜色的最大值
var HSVMinMaxValue = new byte[] {0,0,0, //黑min
180,255,46,//黑max
0,0,46,//灰
180,43,220,
0,0,221,//白
180,30,255,
0,43,46,//红1
10,255,255,
156,43,46,//红2
180,255,255,
11,43,46,//橙
25,255,255,
26,43,46,//黄
34,255,255,
35,43,46,//绿
77,255,255,
78,43,46,//青
99,255,255,
100,43,46,//蓝
124,255,255,
125,43,46,//紫
155,255,255};
var hsvRange = new Mat(HSVMinMaxValue.Length / 3, 1, MatType.CV_8UC3, HSVMinMaxValue);
return hsvRange;
}
2、将HSV转为BGR,然后绘制
var HSVRange = GetHSVRange();
var BGRRange = new Mat();
Cv2.CvtColor(HSVRange, BGRRange, ColorConversionCodes.HSV2BGR);
int cWidth = 100;
int perRow = width / cWidth - 2;
Mat canvasBGR = new Mat(height, width, MatType.CV_8UC3, Scalar.White);
//将各颜色的上下限画出来
for (int i = 0; i < BGRRange.Rows; i++)
{
var bgr = BGRRange.At<Vec3b>(i, 0);
var color = Scalar.FromVec3b(bgr);
var rect = new Rect(cWidth * ((i) % perRow + 1), cWidth * ((i) / perRow + 1), cWidth, cWidth);
Cv2.Rectangle(canvasBGR, rect, color, -1);
}
Utils.ShowWaitDestroy(canvasBGR, "HSV ColorSpace");//显示canvasBGR
生成的效果如下(对黑、灰、白、红1、红2、橙、黄、绿、青、蓝、紫的上下边界值):
按HSV各值生成渐变色:
HSV转BGR后,再生成渐变色:
两种方式生成的结果略有不同。
官网示例
将官网的代码用OpenCvSharp跑一边,效果如下:
如上图所示,简单的根据颜色边界范围来提取对象的效果一般都不能满足生产要求!
代码示例
const int maxValueH = 360 / 2;
const int maxValue = 255;
const string winNameSrc = "源图";
const string winNameDst = "HSV对象掩膜";
const string winNameResult = "根据掩膜提取的对象";
//int lowH = 0, lowS = 0, lowV = 0;
//int highH=maxValueH, highS = maxValue, highV = maxValue;
//红色区域
//int lowH = 156, lowS = 43, lowV = 46;
//int highH = 180, highS = 255, highV = 255;
//蓝色区域
int lowH = 100, lowS = 43, lowV = 46;
int highH = 124, highS = 255, highV = 255;
public void Run()
{
DrawingHSVRange();
HSVThreshold();
}
#region HSV Threshold Sample
private void HSVThreshold()
{
var fileName = ImagePath.WindowsLogo;
using var src = new Mat(fileName, ImreadModes.Color);
Cv2.NamedWindow(winNameSrc);
Cv2.NamedWindow(winNameDst);
Cv2.NamedWindow(winNameResult);
// Trackbars to set thresholds for HSV values
Cv2.CreateTrackbar("Low H", winNameDst, ref lowH, maxValueH, OnLowHThreshTrackbar);
Cv2.CreateTrackbar("High H", winNameDst, ref highH, maxValueH, OnHighHTreshTrackbar);
Cv2.CreateTrackbar("Low S", winNameDst, ref lowS, maxValue, OnLowSThreshTrackbar);
Cv2.CreateTrackbar("High S", winNameDst, ref highS, maxValue, OnHighSThreshTrackbar);
Cv2.CreateTrackbar("Low V", winNameDst, ref lowV, maxValue, OnLowVThreshTrackbar);
Cv2.CreateTrackbar("High V", winNameDst, ref highV, maxValue, OnHighVThreshTrackbar);
Cv2.ImShow(winNameSrc, src);
using var dstHSV = new Mat();
// 将BGR 转为 HSV
Cv2.CvtColor(src, dstHSV, ColorConversionCodes.BGR2HSV);
using var dstThreshold = new Mat();
while (true)
{
// 使用InRange根据HSV的最小、最大值检测对象
Cv2.InRange(dstHSV, new Scalar(lowH, lowS, lowV), new Scalar(highH, highS, highV), dstThreshold);
// 对象掩膜
Cv2.ImShow(winNameDst, dstThreshold);
var copyImg = new Mat();
src.CopyTo(copyImg, dstThreshold);
Cv2.ImShow(winNameResult, copyImg);
char key = (char)Cv2.WaitKey(50);
if (key == 'q' || key == 27)
{
break;
}
}
Cv2.DestroyAllWindows();
}
void OnLowHThreshTrackbar(int pos, IntPtr userData)
{
lowH = Math.Min(highH - 1, pos);
Cv2.SetTrackbarPos("Low H", winNameDst, lowH);
}
void OnHighHTreshTrackbar(int pos, IntPtr userData)
{
highH = Math.Max(pos, lowH + 1);
Cv2.SetTrackbarPos("High H", winNameDst, highH);
}
void OnLowSThreshTrackbar(int pos, IntPtr userData)
{
lowS = Math.Min(highS - 1, pos);
Cv2.SetTrackbarPos("Low S", winNameDst, lowS);
}
void OnHighSThreshTrackbar(int pos, IntPtr userData)
{
highS = Math.Max(pos, lowS + 1);
Cv2.SetTrackbarPos("High S", winNameDst, highS);
}
void OnLowVThreshTrackbar(int pos, IntPtr userData)
{
lowV = Math.Min(highV - 1, pos);
Cv2.SetTrackbarPos("Low V", winNameDst, lowV);
}
void OnHighVThreshTrackbar(int pos, IntPtr userData)
{
highV = Math.Max(pos, lowV + 1);
Cv2.SetTrackbarPos("High V", winNameDst, highV);
}
#endregion
/// <summary>
/// 按HSV绘制
/// </summary>
private void DrawingHSVRange()
{
var width = 1600;
var height = 800;
string ColorName = "黑、灰、白、红1、红2、橙、黄、绿、青、蓝、紫";
var HSVRange = GetHSVRange();
var BGRRange = new Mat();
Cv2.CvtColor(HSVRange, BGRRange, ColorConversionCodes.HSV2BGR);
int cWidth = 100;
int perRow = width / cWidth - 2;
Mat canvasBGR = new Mat(height, width, MatType.CV_8UC3, Scalar.White);
//将各颜色的上下限画出来
for (int i = 0; i < BGRRange.Rows; i++)
{
var bgr = BGRRange.At<Vec3b>(i, 0);
var color = Scalar.FromVec3b(bgr);
var rect = new Rect(cWidth * ((i) % perRow + 1), cWidth * ((i) / perRow + 1), cWidth, cWidth);
Cv2.Rectangle(canvasBGR, rect, color, -1);
}
Utils.ShowWaitDestroy(canvasBGR, "HSV ColorSpace");
//初始化一张HSV,白底画布,注意HSV中白色是(0,0,255)
Mat canvasHSV = new Mat(height, width, MatType.CV_8UC3, new Scalar(0, 0, 255));
//通过HSV的递增生成渐变色
GenerateGradientColor(canvasHSV, HSVRange, "HSV");
//将HSV转成BGR显示
Cv2.CvtColor(canvasHSV, canvasHSV, ColorConversionCodes.HSV2BGR);
Utils.ShowWaitDestroy(canvasHSV, "HSV:"+ ColorName);
//初始化一张BGR,白底画布
canvasBGR = new Mat(height, width, MatType.CV_8UC3, Scalar.White);
GenerateGradientColor(canvasBGR, BGRRange, "BGR");
Utils.ShowWaitDestroy(canvasBGR, "BGR:"+ ColorName);
Cv2.DestroyAllWindows();
}
/// <summary>
/// 根据颜色范围生成渐变色
/// </summary>
/// <param name="canvas">画布</param>
/// <param name="colorRange">颜色范围(最小值、最大值)</param>
/// <param name="colorSpace"></param>
private void GenerateGradientColor(Mat canvas,Mat colorRange,string colorSpace)
{
//两边的边距
int edgeDistance = 160;
//各渐变色的跨度
int stepWdith = canvas.Cols - edgeDistance * 2;
//各渐变色的高度
int rowHeight = 60;
//通过颜色递增生成渐变色
for (int i = 0; i < colorRange.Rows - 1; i += 2)
{
var minVal = colorRange.At<Vec3b>(i, 0);//颜色最小值
var maxVal = colorRange.At<Vec3b>(i + 1, 0);//颜色最大值 转BGR时,各通道的值大、小有变
var stepB = Math.Abs(1.0D * (maxVal.Item0 - minVal.Item0) / stepWdith);
var stepG = Math.Abs(1.0D * (maxVal.Item1 - minVal.Item1) / stepWdith);
var stepR = Math.Abs(1.0D * (maxVal.Item2 - minVal.Item2) / stepWdith);
int rowOffset = rowHeight * (i / 2 + 1);
for (int step = 0; step <= stepWdith; step++)
{
//生成渐变色
var colorVec = new Vec3b((byte)(Math.Min(minVal.Item0,maxVal.Item0) + stepB * step),
(byte)(Math.Min(minVal.Item1, maxVal.Item1) + stepG * step),
(byte)(Math.Min(minVal.Item2, maxVal.Item2) + stepR * step));
//生成rowHeigh行
for (int row = 0; row < rowHeight; row++)
{
canvas.At<Vec3b>(row + rowHeight * (i / 2 + 1), edgeDistance + step) = colorVec;
}
//两边加文字
if (step == 0)
{
PutText(canvas, new Point(5, rowOffset + rowHeight / 2), colorSpace, colorVec);
}
else if (step == stepWdith)
{
PutText(canvas, new Point((canvas.Cols - edgeDistance) + 5, rowOffset + rowHeight / 2), colorSpace, colorVec);
}
}
}
}
//绘制渐变色两字的文字
private void PutText(Mat src, Point org, string text, Vec3b colorVec)
{
Cv2.PutText(src, $"{text}({colorVec.Item0},{colorVec.Item1},{colorVec.Item2})", org, HersheyFonts.HersheyTriplex, 0.5, Scalar.Black);
}
/// <summary>
/// HSV颜色范围
/// </summary>
/// <returns></returns>
private Mat GetHSVRange()
{
//使用HSV定义的各颜色范围 6个byte为一组,
//前三个是对应颜色的最小值,后三个是对应颜色的最大值
var HSVMinMaxValue = new byte[] {0,0,0, //黑min
180,255,46,//黑max
0,0,46,//灰
180,43,220,
0,0,221,//白
180,30,255,
0,43,46,//红1
10,255,255,
156,43,46,//红2
180,255,255,
11,43,46,//橙
25,255,255,
26,43,46,//黄
34,255,255,
35,43,46,//绿
77,255,255,
78,43,46,//青
99,255,255,
100,43,46,//蓝
124,255,255,
125,43,46,//紫
155,255,255};
var hsvRange = new Mat(HSVMinMaxValue.Length / 3, 1, MatType.CV_8UC3, HSVMinMaxValue);
return hsvRange;
}
参考
https://docs.opencv.org/4.7.0/da/d97/tutorial_threshold_inRange.html