SKProgramLab

Let's Enjoy Programming! ~画像処理/IoT/機械学習など~

C#/VB.NETで画像処理⑩<画像のヒストグラム作成>

こんにちは、SKです。
f:id:SKProgramLab:20200310203357p:plain:w100

C#/VB.NETで画像処理シリーズの第10弾。
カメラ画像のヒストグラムを作成します。

画像のヒストグラムとは、各ピクセルの輝度値の分布を表すグラフのこと。
画像同士の照合等に使われるこのヒストグラムOpenCVでは簡単に計算できます。

OpenCVSharpとは?

skprogramlab.hatenablog.com

(前回)画像のラベリング処理

skprogramlab.hatenablog.com

動画手順

youtu.be

ヒストグラムを作成する

OpenCVSharpのCalcHist関数でヒストグラムを作成します。

CalcHist関数

ヒストグラム — opencv 2.2 documentation
ヒストグラム その1: 計算して,プロットして,解析する !!! — OpenCV-Python Tutorials 1 documentation
・第1引数:入力画像の配列:Mat配列
・第2引数:画像チャンネルのインデックス:int配列
・第3引数:マスク画像:Mat
・第4引数:出力ヒストグラム:Mat
・第5引数:ヒストグラム次元数:int
・第6引数:ヒストグラムサイズ:int配列
・第7引数:計測する画素値の範囲:Rangef配列

引数が多く、入力画像を配列で指定する必要があるなど、やや複雑です。
B,G,Rそれぞれについて、下記のようにヒストグラムを計算します。

//ヒストグラム計算
Mat b_hist = new Mat();
Mat g_hist = new Mat();
Mat r_hist = new Mat();
Cv2.CalcHist(new Mat[] { img }, new int[] { 0 }, null, b_hist, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) });
Cv2.CalcHist(new Mat[] { img }, new int[] { 1 }, null, g_hist, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) });
Cv2.CalcHist(new Mat[] { img }, new int[] { 2 }, null, r_hist, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) });

f:id:SKProgramLab:20200412223516p:plain

得られたヒストグラムはMat型ですが、中身は32bitの小数値が格納された1次元配列(サイズ=256)です。
f:id:SKProgramLab:20200412224120p:plain

ヒストグラムを正規化する

ヒストグラムを線グラフで描画する用にMat画像histImage(512×400)を定義します。
そして、OpenCVSharpのNormalize関数でヒストグラムを正規化します。
今回は、0~400(histImageの高さ)の範囲で正規化することにします。
配列操作 — opencv 2.2 documentation
・第1引数:入力画像:Mat
・第2引数:出力画像:Mat
・第3引数:正規化範囲の下界:Mat
・第4引数:正規化範囲の上界:Mat
・第5引数:正規化の種類:NormTypes

//ヒストグラム画像
Mat histImage = new Mat(400, 512, MatType.CV_8UC3, Scalar.Black);

//正規化
Cv2.Normalize(b_hist, b_hist, 0, histImage.Height, NormTypes.MinMax);
Cv2.Normalize(g_hist, g_hist, 0, histImage.Height, NormTypes.MinMax);
Cv2.Normalize(r_hist, r_hist, 0, histImage.Height, NormTypes.MinMax);

f:id:SKProgramLab:20200412225209p:plain

ヒストグラム描画関数を定義する

B,G,Rそれぞれのヒストグラムを線グラフとして描画するための関数DrawHistを定義します。
forループでヒストグラム内の値とその次の値を取り出し、画像上の座標を計算し、Cv2.Line関数を使い線で繋いでいます。

private void DrawHist(Mat histImage, Mat hist, Scalar color)
{
    int bin = histImage.Width / 256;
    for(int i = 0; i < 255; i++)
    {
        float value1 = hist.At<float>(i, 0);
        float value2 = hist.At<float>(i+1, 0);
        Point pt1 = new Point(i * bin, histImage.Height - value1);
        Point pt2 = new Point((i + 1) * bin, histImage.Height - value2);
        Cv2.Line(histImage, pt1, pt2, color, 2);
    }
}

f:id:SKProgramLab:20200412225804p:plain

ヒストグラム描画関数を呼び出す

③で定義したDrawHist関数をB、G、Rそれぞれのヒストグラムについて呼び出して描画した後、Cv2.ImShow関数で表示します。

//ヒストグラム表示
DrawHist(histImage, b_hist, Scalar.Blue);
DrawHist(histImage, g_hist, Scalar.Lime);
DrawHist(histImage, r_hist, Scalar.Red);
Cv2.ImShow("Histogram", histImage);
Cv2.WaitKey(1);

f:id:SKProgramLab:20200412230321p:plain

実行します。
f:id:SKProgramLab:20200412230444p:plain
カメラ画像と、ヒストグラムのグラフが表示されました。


最後に、コード全文を載せておきます。
C#

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                using (VideoCapture v = new VideoCapture(0))
                using (Mat img = new Mat())
                {
                    while (true)
                    {
                        v.Read(img);
                        Process(img);
                        pictureBox1.Image = BitmapConverter.ToBitmap(img);
                    }
                }
            });
        }

        private void Process(Mat img)
        {
            //ヒストグラム計算
            Mat b_hist = new Mat();
            Mat g_hist = new Mat();
            Mat r_hist = new Mat();
            Cv2.CalcHist(new Mat[] { img }, new int[] { 0 }, null, b_hist, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) });
            Cv2.CalcHist(new Mat[] { img }, new int[] { 1 }, null, g_hist, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) });
            Cv2.CalcHist(new Mat[] { img }, new int[] { 2 }, null, r_hist, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256) });

            //ヒストグラム画像
            Mat histImage = new Mat(400, 512, MatType.CV_8UC3, Scalar.Black);

            //正規化
            Cv2.Normalize(b_hist, b_hist, 0, histImage.Height, NormTypes.MinMax);
            Cv2.Normalize(g_hist, g_hist, 0, histImage.Height, NormTypes.MinMax);
            Cv2.Normalize(r_hist, r_hist, 0, histImage.Height, NormTypes.MinMax);

            //ヒストグラム表示
            DrawHist(histImage, b_hist, Scalar.Blue);
            DrawHist(histImage, g_hist, Scalar.Lime);
            DrawHist(histImage, r_hist, Scalar.Red);
            Cv2.ImShow("Histogram", histImage);
            Cv2.WaitKey(1);
        }

        private void DrawHist(Mat histImage, Mat hist, Scalar color)
        {
            int bin = histImage.Width / 256;
            for(int i = 0; i < 255; i++)
            {
                float value1 = hist.At<float>(i, 0);
                float value2 = hist.At<float>(i+1, 0);
                Point pt1 = new Point(i * bin, histImage.Height - value1);
                Point pt2 = new Point((i + 1) * bin, histImage.Height - value2);
                Cv2.Line(histImage, pt1, pt2, color, 2);
            }
        }

    }
}


VB.NET

Imports OpenCvSharp
Imports OpenCvSharp.Extensions

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Task.Run(Sub()
                     Using v As New VideoCapture(0)
                         Using img As New Mat
                             Do
                                 v.Read(img)
                                 Process(img)
                                 PictureBox1.Image = img.ToBitmap
                             Loop
                         End Using
                     End Using
                 End Sub)
    End Sub

    Private Sub Process(ByVal img As Mat)

        'ヒストグラム計算
        Dim b_hist As New Mat()
        Dim g_hist As New Mat()
        Dim r_hist As New Mat()
        Cv2.CalcHist(New Mat() {img}, New Integer() {0}, Nothing, b_hist, 1, New Integer() {256}, New Rangef() {New Rangef(0, 256)})
        Cv2.CalcHist(New Mat() {img}, New Integer() {1}, Nothing, g_hist, 1, New Integer() {256}, New Rangef() {New Rangef(0, 256)})
        Cv2.CalcHist(New Mat() {img}, New Integer() {2}, Nothing, r_hist, 1, New Integer() {256}, New Rangef() {New Rangef(0, 256)})

        'ヒストグラム画像
        Dim histImage As Mat = New Mat(400, 512, MatType.CV_8UC3, Scalar.Black)

        '正規化
        Cv2.Normalize(b_hist, b_hist, 0, histImage.Height, NormTypes.MinMax)
        Cv2.Normalize(g_hist, g_hist, 0, histImage.Height, NormTypes.MinMax)
        Cv2.Normalize(r_hist, r_hist, 0, histImage.Height, NormTypes.MinMax)

        'ヒストグラム表示
        DrawHist(histImage, b_hist, Scalar.Blue)
        DrawHist(histImage, g_hist, Scalar.Lime)
        DrawHist(histImage, r_hist, Scalar.Red)
        Cv2.ImShow("Histogram", histImage)
        Cv2.WaitKey(1)
    End Sub

    Private Sub DrawHist(ByVal histImage As Mat, ByVal hist As Mat, ByVal color As Scalar)
        Dim bin As Integer = histImage.Width / 256
        For i = 0 To 254
            Dim value1 As Single = hist.At(Of Single)(i, 0)
            Dim value2 As Single = hist.At(Of Single)(i + 1, 0)
            Dim pt1 As New Point(i * bin, histImage.Height - value1)
            Dim pt2 As New Point((i + 1) * bin, histImage.Height - value2)
            Cv2.Line(histImage, pt1, pt2, color, 2)
        Next
    End Sub

End Class