ベクトルスコープ

```CS
using System;
using System.Linq;
using OpenCvSharp;

public static class VectorScope
{
    const double RY_R = +0.701;
    const double RY_G = -0.587;
    const double RY_B = -0.114;

    const double BY_R = -0.299;
    const double BY_G = -0.587;
    const double BY_B = +0.886;

    public static Point Rotation(int cx, int cy, double theta, double radius)
    {
        var x = cx + radius * Math.Cos(theta);
        var y = cy - radius * Math.Sin(theta);
        return new Point(Math.Round(x, MidpointRounding.AwayFromZero), Math.Round(y, MidpointRounding.AwayFromZero));
    }

    public static (double ry, double by, double ec, double maxval) Calc(Scalar bgr)
    {
        var ry = RY_R * bgr[2] + RY_G * bgr[1] + RY_B * bgr[0];
        var by = BY_R * bgr[2] + BY_G * bgr[1] + BY_B * bgr[0];
        var ec = Math.Sqrt(Math.Pow(ry / 1.14, 2) + Math.Pow(by / 2.03, 2));
        var maxval = Math.Sqrt(Math.Pow(RY_R / 1.14, 2) + Math.Pow(BY_B / 2.03, 2));
        return (ry, by, ec, maxval);
    }

    public static Point Rgb2Point(int cx, int cy, double radius, Scalar bgr)
    {
        var (ry, by, ec, maxval) = Calc(bgr);
        var theta = Math.Atan2(ry / 1.14, by / 2.03);
        return Rotation(cx, cy, theta, ec / maxval * radius);
    }

    public static void DrawTarget(Mat img, int cx, int cy, int radius, Scalar bgr, double angle, string element)
    {
        var (_, _, ec, maxval) = Calc(bgr);
        var percent = ec / maxval;

        {
            var coeff1 = 0.40;
            var radius1 = radius * percent * (1 - coeff1 / 2);
            var radius2 = radius * percent * (1 + coeff1 / 2);

            var angle1 = Math.Round(angle - 10.0, MidpointRounding.AwayFromZero);
            var angle2 = angle1 + (10.0 * 2);

            var coeff2 = 0.28;
            var radius3 = radius * percent * (1 - coeff2 / 2);
            var radius4 = radius * percent * (1 + coeff2 / 2);

            var p1 = Rotation(cx, cy, angle1 * Math.PI / 180, radius1);
            var p2 = Rotation(cx, cy, angle1 * Math.PI / 180, radius2);

            var p3 = Rotation(cx, cy, angle1 * Math.PI / 180, radius3);
            var p4 = Rotation(cx, cy, angle1 * Math.PI / 180, radius4);
            Cv2.Line(img, p1, p3, Scalar.LightGray, 1, LineTypes.AntiAlias);
            Cv2.Line(img, p4, p2, Scalar.LightGray, 1, LineTypes.AntiAlias);

            var p5 = Rotation(cx, cy, angle2 * Math.PI / 180, radius1);
            var p6 = Rotation(cx, cy, angle2 * Math.PI / 180, radius2);

            var p7 = Rotation(cx, cy, angle2 * Math.PI / 180, radius3);
            var p8 = Rotation(cx, cy, angle2 * Math.PI / 180, radius4);
            Cv2.Line(img, p5, p7, Scalar.LightGray, 1, LineTypes.AntiAlias);
            Cv2.Line(img, p8, p6, Scalar.LightGray, 1, LineTypes.AntiAlias);

            Cv2.Ellipse(img, new Point(cx, cy), new Size(radius1, radius1), -angle2, 0, 5.0, Scalar.LightGray, 1, LineTypes.AntiAlias);
            Cv2.Ellipse(img, new Point(cx, cy), new Size(radius2, radius2), -angle2, 0, 5.0, Scalar.LightGray, 1, LineTypes.AntiAlias);

            Cv2.Ellipse(img, new Point(cx, cy), new Size(radius1, radius1), -angle2, 15, 20, Scalar.LightGray, 1, LineTypes.AntiAlias);
            Cv2.Ellipse(img, new Point(cx, cy), new Size(radius2, radius2), -angle2, 15, 20, Scalar.LightGray, 1, LineTypes.AntiAlias);
        }

        {
            var coeff1 = (0.714 * 5 / 100) / maxval; // 5IRE
            var radius1 = radius * percent * (1 - coeff1 / 2);
            var radius2 = radius * percent * (1 + coeff1 / 2);

            var angle1 = Math.Round(angle - 2.5, MidpointRounding.AwayFromZero);
            var angle2 = angle1 + (2.5 * 2);

            var p1 = Rotation(cx, cy, angle1 * Math.PI / 180, radius1);
            var p2 = Rotation(cx, cy, angle1 * Math.PI / 180, radius2);
            Cv2.Line(img, p1, p2, Scalar.LightGray, 1, LineTypes.AntiAlias);

            var p3 = Rotation(cx, cy, angle2 * Math.PI / 180, radius1);
            var p4 = Rotation(cx, cy, angle2 * Math.PI / 180, radius2);
            Cv2.Line(img, p3, p4, Scalar.LightGray, 1, LineTypes.AntiAlias);

            Cv2.Ellipse(img, new Point(cx, cy), new Size(radius1, radius1), -angle2, 0, 5, Scalar.LightGray, 1, LineTypes.AntiAlias);
            Cv2.Ellipse(img, new Point(cx, cy), new Size(radius2, radius2), -angle2, 0, 5, Scalar.LightGray, 1, LineTypes.AntiAlias);
        }

        {
            var radius1 = radius * percent;
            var p1 = Rotation(cx, cy, angle * Math.PI / 180, radius1);

            Cv2.PutText(img, element, new Point(p1.X, p1.Y - 18), HersheyFonts.HersheySimplex, 0.8, Scalar.LightGray, 1, LineTypes.AntiAlias);
        }
    }

    public static void DrawBackgroud(Mat img, int cx, int cy, int radius)
    {
        foreach (var i in Enumerable.Range(1, 5).Select(n => (1 / 5.0 * n)))
        {
            Cv2.Circle(img, cx, cy, (int)(radius * i), Scalar.DimGray, (i == 1) ? 2 : 1, LineTypes.AntiAlias);
        }

        foreach (var i in Enumerable.Range(0, 4).Select(n => n * 360 / 4))
        {
            Point p2;
            p2 = Rotation(cx, cy, i * Math.PI / 180, radius);
            Cv2.Line(img, cx, cy, p2.X, p2.Y, Scalar.DimGray, 1, LineTypes.AntiAlias);

            // +Q(33)/-I(303)
            p2 = Rotation(cx, cy, (i + 33.0) * Math.PI / 180, radius * 0.88);
            Cv2.Line(img, cx, cy, p2.X, p2.Y, Scalar.DimGray, 1, LineTypes.AntiAlias);
        }

        foreach (var i in Enumerable.Range(0, 180).Select(n => n * 2))
        {
            var p1 = Rotation(cx, cy, i * Math.PI / 180, radius * ((i % 10) == 0 ? 0.940 : 0.960));
            var p2 = Rotation(cx, cy, i * Math.PI / 180, radius);
            Cv2.Line(img, p1, p2, Scalar.DimGray, (i % 10 == 0) ? 2 : 1, LineTypes.AntiAlias);
        }

        // M(60.8)/R(103.4)/Y(167.1)/G(240.8)/C(283.4)/B(347.1)
        Scalar[] BGR = {
            new Scalar(1.0, 0.0, 1.0),
            new Scalar(0.0, 0.0, 1.0),
            new Scalar(0.0, 1.0, 1.0),
            new Scalar(0.0, 1.0, 0.0),
            new Scalar(1.0, 1.0, 0.0),
            new Scalar(1.0, 0.0, 0.0)
        };

        double[] ANGLE = { 60.8, 103.4, 167.1, 240.8, 283.4, 347.1 };

        string[] ELEMENT = { "MG", "R", "YL", "G", "CY", "B" };

        foreach (var ((bgr, angle), element) in BGR.Zip(ANGLE, Tuple.Create).Zip(ELEMENT, Tuple.Create))
        {
            DrawTarget(img, cx, cy, radius, bgr, angle, element);
        }
    }

    public static void DrawPixels(Mat img, int cx, int cy, int radius, Scalar bgr)
    {
        var point = Rgb2Point(cx, cy, radius, bgr);
        var bgr_ = new Scalar(bgr[0] * 255, bgr[1] * 255, bgr[2] * 255);
        Cv2.Circle(img, point, 1, bgr_, -1, LineTypes.AntiAlias);
    }

    public static void Run()
    {
        const int COLS = 640;
        const int ROWS = 640;
        const int MARGIN = 10;

        var image = Cv2.ImRead("Mandrill.bmp");

        var cx = COLS / 2;
        var cy = ROWS / 2;
        var radius = cy - MARGIN;
        var image_bg = new Mat<Vec3b>(ROWS, COLS, Scalar.Black);

        var data = new Vec3d[image.Height, image.Width];
        image.ConvertTo(InputArray.Create(data).GetMat(), MatType.CV_64FC3, 1 / 255.0, 0);

        DrawBackgroud(image_bg, cx, cy, radius);

        var image_dst = image_bg.Clone();

        foreach (var bgr in data.Cast<Vec3d>())
        {
            DrawPixels(image_dst, cx, cy, radius, (Scalar)bgr);
        }

        Cv2.AddWeighted(image_dst, 1.0, image_bg, 0.4, 0.0, image_dst);

        Cv2.ImShow("input", image);
        Cv2.ImShow("output", image_dst);
        Cv2.WaitKey(0);
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662099/0fbe699c-3137-a6b5-7e59-422da56b5335.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/662099/293b6273-ce7e-cd37-1c5b-d1f16eacab86.png)

・以下のサイトを参考にさせていただきました。
[ベクトルスコープを描いてみる @linear_pcm0153](https://ameblo.jp/holycater/entry-12467335971.html)