OpenCvSharp 研究ノート 13 -- InRange を使用した HSV しきい値フィルタリングとグラデーション カラー生成

目標

HSV 色空間の概要
InRange を使用してしきい値を操作する
HSV 色空間のピクセル値範囲に基づいてオブジェクトを検出する
グラデーション カラーを生成する

HSV色空間

HSV (色相、彩度、値) は、色の直感的な特性に基づいて 1978 年に AR Smith によって作成された色空間であり、ヘクスコーン モデルとしても知られています。HSV 表色系はユーザーにとって直感的なカラーモデルで、色に関して人は「何色? どれくらい暗い? 明暗は?」という疑問を直感的に表現し、その情報を直感的に表現します。
各色は、色相 (Hue、略して H)、彩度 (Saturation、略して)、明度 (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: (色相、色相)、S: (彩度)、V: (値の高さ)。HSV は、オブジェクトを色でセグメント化する必要がある場合に役立ちます。彩度 (S) は、グレーの色合いを表す不飽和から完全に飽和した (白成分がない) までの範囲になります。輝度 (V) チャネルは明るさまたは強度を表します。写真の通り(画像出典公式サイト)

HSV色空間

RGB 色空間と比較すると、画像内のオブジェクトを色でセグメント化するのは困難です。

RGB色空間

InRange 範囲判定

関数の説明: 配列要素が他の 2 つの配列の要素の間にあるかどうかを確認します。
単一チャネルの各要素について
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)
パラメータ 説明する                            
入力配列ソース 入力画像
入力配列の下位 b
スカラーの下位 b
範囲の下限値(両端を含む)
入力配列の上限b
スカラーの上限b
範囲の上限(両端を含む)
出力配列 dst 出力画像( lowerb と upperb の範囲の値は 255、それ以外の場合は 0)

OpenCV の HSV

OpenCVで使用するHSVの値の範囲はH:[0,180]、S:[0,255]、V:[0,255]なので、各色の範囲は下図のようになります。HSVにおける各色の値の範囲

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 に変換した後、再度グラデーション カラー
各色の境界値
を生成
HSV グラデーションカラー
:
BGR グラデーションカラー
2 つの方法はわずかに異なる結果を生成します。

公式サイトの例

OpenCvSharp を使用して公式 Web サイトのコードを実行すると、次のような効果が得られます。
HSV に基づいてオブジェクトを抽出する
上の図に示すように、色の境界範囲に基づいてオブジェクトを単純に抽出する効果は、通常、制作要件を満たすことができません。

コード例

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

おすすめ

転載: blog.csdn.net/TyroneKing/article/details/129871720