OpenCvSharp study notes 13--Using InRange for HSV threshold filtering and gradient color generation

Target

Introduction to HSV color space
Using InRange to operate thresholds
Detect objects based on the pixel value range of HSV color space
Generate gradient colors

HSV color space

HSV (Hue, Saturation, Value) is a color space created by AR Smith in 1978 based on the intuitive characteristics of color, also known as the Hexcone Model. The HSV color system is an intuitive color model for users. Regarding color, people intuitively ask "What color? How dark? How light and dark?", and the HSV color system intuitively represents this information.
Each color is represented by hue (Hue, H for short), saturation (Saturation, for short) and lightness (Value, V for short). The parameters of color in this model are: hue (H), saturation (S), and brightness (V).
The hue H parameter represents color information, that is, the position of the spectral color. This parameter is represented by an angle, with a value ranging from 0° to 360°. If calculated counterclockwise starting from red, red is 0°, green is 120°, and blue is 240°. Their complementary colors are: yellow is 60°, cyan is 180°, and purple is 300°;
saturation S: the value range is 0.0 ~ 1.0;
brightness V: the value range is 0.0 (black) ~ 1.0 (white).
H: (Hue hue, hue), S: (Saturation), V: (Value height). HSV is useful where objects need to be segmented by color. Saturation (S) ranges from unsaturated to represent shades of gray and fully saturated (no white component). The luminance (V) channel describes brightness or intensity. As shown in the picture (picture source official website)

HSV color space

Compared to the RGB color space, it is difficult to segment objects in images by color.

RGB color space

InRange range judgment

Function Description: Checks whether an array element is between elements of two other arrays.
For each element of a single channel
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)
parameter illustrate                            
InputArray src input image
InputArray lowerb
Scalar lowerb
Lower limit of range (inclusive)
InputArray upperb
Scalar upperb
Range upper limit (inclusive)
OutputArray dst Output image (the value in the range of lowerb and upperb is 255, otherwise it is 0)

HSV in OpenCV

The value range of HSV used in OpenCV is H: [0,180], S: [0,255], V: [0,255], so the range of each color is as shown in the figure below:The value range of each color in HSV

Draw the boundary values ​​of each color in HSV into color blocks

1. Define the above table as a matrix

/// <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. Convert HSV to BGR and then draw

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

The generated effect is as follows (for the upper and lower boundary values ​​​​of black, gray, white, red 1, red 2, orange, yellow, green, cyan, blue, and purple): Generate gradient colors according to each value of HSV: After converting HSV to BGR,
Boundary value of each color
generate
HSV gradient color
again Gradient color:
BGR gradient color
The two methods produce slightly different results.

Official website example

Run the code on the official website with OpenCvSharp and the effect is as follows:
Extract objects based on HSV
As shown in the figure above, the effect of simply extracting objects based on the color boundary range generally cannot meet production requirements!

code example

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;
}

Reference
https://docs.opencv.org/4.7.0/da/d97/tutorial_threshold_inRange.html

Guess you like

Origin blog.csdn.net/TyroneKing/article/details/129871720