DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

画像のカラーバランスを補正して表示する

注意:画像の表示方法が分からないという方は、まず「コントロールやフォームに画像を表示する」をご覧ください。

ColorMatrixクラスを使用する方法

画像のRGB(およびA)を補正するには、ColorMatrixクラスが便利です。ColorMatrixクラスは、5x5の行列を表します。色を表すRGBAW(赤、緑、青、アルファ、w(常に1))とColorMatrixの行列を掛けた結果が、変換後の新しい色になります。

               ┌                     ┐
               │ M00 M01 M02 M03 M04 │
┌           ┐│ M10 M11 M12 M13 M14 │ ┌                          ┐
│ R G B A 1 ││ M20 M21 M22 M23 M24 │=│ NewR NewG NewB NewA NewW │
└           ┘│ M30 M31 M32 M33 M34 │ └                          ┘
               │ M40 M41 M42 M43 M44 │
               └                     ┘

つまり新しい色(NewR、NewG、NewB)は、次のような式で計算できます。

NewR = M00*R + M10*G + M20*B + M30*A + M40
NewG = M01*R + M11*G + M21*B + M31*A + M41
NewB = M02*R + M12*G + M22*B + M32*A + M42

具体例を示しましょう。例えば下のような5x5の行列を用意すれば、赤を2倍、緑を1.5倍、青を0.5倍して0.1(255を1とした時)を足した色に変換することができます。

                 ┌                     ┐
                 │ 2.0 0.0 0.0 0.0 0.0 │
┌             ┐│ 0.0 1.5 0.0 0.0 0.0 │ ┌                             ┐
│ R G B A 1.0 ││ 0.0 0.0 0.5 0.0 0.0 │=│ 2.0*R 1.5*G 0.5*B+0.1 A 1.0 │
└             ┘│ 0.0 0.0 0.0 1.0 0.0 │ └                             ┘
                 │ 0.0 0.0 0.1 0.0 1.0 │
                 └                     ┘

このように、単位行列(n行n列が1で、それ以外が0)を基本として、赤の倍率を0行0列に、緑の倍率を1行1列に、青の倍率を2行2列に指定すればよいということが分かります。また、赤にプラスする値を4行0列に、緑にプラスする値を4行1列に、青にプラスする値を4行2列に指定すればよいということも分かります。

なお計算の結果、値が1(255を1とした時)を超えた時は1に、0に満たなかった時は0になるようです。

以下に、カラーバランスを調整するメソッドと、それを使用する例を示します。このメソッドは、指定した画像のRGBに、指定した倍率を掛けて画像を作成するものです。PictureBoxコントロール(PictureBox1)をクリックすると、画像(C:\test\1.png)の赤を2倍にした画像を作成して、PictureBox1に表示しています。

VB.NET
コードを隠すコードを選択
'Imports System.Drawing

''' <summary>
''' 指定した画像の色を補正した画像を取得する
''' </summary>
''' <param name="img">色の補正をする画像</param>
''' <param name="rScale">赤に掛ける倍率</param>
''' <param name="gScale">緑に掛ける倍率</param>
''' <param name="bScale">青に掛ける倍率</param>
''' <returns></returns>
Public Shared Function CreateColorCorrectedImage( _
        ByVal img As Image, ByVal rScale As Single, _
        ByVal gScale As Single, ByVal bScale As Single) As Image
    '補正された画像の描画先となるImageオブジェクトを作成
    Dim newImg As New Bitmap(img.Width, img.Height)
    'newImgのGraphicsオブジェクトを取得
    Dim g As Graphics = Graphics.FromImage(newImg)

    'ColorMatrixオブジェクトの作成
    '指定された倍率を掛けるための行列を指定する
    Dim cm As New System.Drawing.Imaging.ColorMatrix(New Single()() _
        {New Single() {rScale, 0, 0, 0, 0}, _
         New Single() {0, gScale, 0, 0, 0}, _
         New Single() {0, 0, bScale, 0, 0}, _
         New Single() {0, 0, 0, 1, 0}, _
         New Single() {0, 0, 0, 0, 1}})
    '次のようにしても同じ
    'Dim cm As New System.Drawing.Imaging.ColorMatrix()
    'cm.Matrix00 = rScale
    'cm.Matrix11 = gScale
    'cm.Matrix22 = bScale
    'cm.Matrix33 = 1
    'cm.Matrix44 = 1

    'ImageAttributesオブジェクトの作成
    Dim ia As New System.Drawing.Imaging.ImageAttributes()
    'ColorMatrixを設定する
    ia.SetColorMatrix(cm)

    'ImageAttributesを使用して描画
    g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height), _
                0, 0, img.Width, img.Height, GraphicsUnit.Pixel, ia)

    'リソースを解放する
    g.Dispose()

    Return newImg
End Function

'PictureBox1のClickイベントハンドラ
Private Sub PictureBox1_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles PictureBox1.Click
    '色補正をする画像
    Dim img As New Bitmap("C:\test\1.png")
    '赤を倍にする
    Dim newImg As Image = CreateColorCorrectedImage(img, 2, 1, 1)
    img.Dispose()
    'PictureBox1に表示
    If Not PictureBox1.Image Is Nothing Then
        PictureBox1.Image.Dispose()
    End If
    PictureBox1.Image = newImg
End Sub
C#
コードを隠すコードを選択
//using System.Drawing;

/// <summary>
/// 指定した画像の色を補正した画像を取得する
/// </summary>
/// <param name="img">色の補正をする画像</param>
/// <param name="rScale">赤に掛ける倍率</param>
/// <param name="gScale">緑に掛ける倍率</param>
/// <param name="bScale">青に掛ける倍率</param>
/// <returns></returns>
public static Image CreateColorCorrectedImage(Image img,
    float rScale, float gScale, float bScale)
{
    //補正された画像の描画先となるImageオブジェクトを作成
    Bitmap newImg = new Bitmap(img.Width, img.Height);
    //newImgのGraphicsオブジェクトを取得
    Graphics g = Graphics.FromImage(newImg);

    //ColorMatrixオブジェクトの作成
    //指定された倍率を掛けるための行列を指定する
    System.Drawing.Imaging.ColorMatrix cm =
        new System.Drawing.Imaging.ColorMatrix(
            new float[][] {
                new float[] {rScale, 0, 0, 0, 0},
                new float[] {0, gScale, 0, 0, 0},
                new float[] {0, 0, bScale, 0, 0}, 
                new float[] {0, 0, 0, 1, 0},
                new float[] {0, 0, 0, 0, 1}
            });
    //次のようにしても同じ
    //System.Drawing.Imaging.ColorMatrix cm =
    //    new System.Drawing.Imaging.ColorMatrix();
    //cm.Matrix00 = rScale;
    //cm.Matrix11 = gScale;
    //cm.Matrix22 = bScale;
    //cm.Matrix33 = 1;
    //cm.Matrix44 = 1;

    //ImageAttributesオブジェクトの作成
    System.Drawing.Imaging.ImageAttributes ia =
        new System.Drawing.Imaging.ImageAttributes();
    //ColorMatrixを設定する
    ia.SetColorMatrix(cm);

    //ImageAttributesを使用して描画
    g.DrawImage(img,
        new Rectangle(0, 0, img.Width, img.Height),
        0, 0, img.Width, img.Height, GraphicsUnit.Pixel, ia);

    //リソースを解放する
    g.Dispose();

    return newImg;
}

//PictureBox1のClickイベントハンドラ
private void PictureBox1_Click(object sender, EventArgs e)
{
    //色補正をする画像
    Bitmap img = new Bitmap(@"C:\test\1.png");
    //赤を倍にする
    Image newImg = CreateColorCorrectedImage(img, 2, 1, 1);
    img.Dispose();
    //PictureBox1に表示
    if (PictureBox1.Image != null)
    {
        PictureBox1.Image.Dispose();
    }
    PictureBox1.Image = newImg;
}

上が元の画像で、下が赤を2倍にした画像です。

赤を2倍にした画像

GetPixelとSetPixelを使用する方法

Bitmap.GetPixelメソッドで指定したピクセルの色を取得できます。また、Bitmap.SetPixelメソッドで指定したピクセルの色を変更することができます。この2つのメソッドを使えば、画像の色を変更することができます。

以下の例では、指定された値を赤、緑、青に加えるメソッドを作成しています。PictureBox1をクリックすると、赤に128を加えた画像を表示します。

VB.NET
コードを隠すコードを選択
'Imports System.Drawing

''' <summary>
''' 指定した画像の色を補正する
''' </summary>
''' <param name="img">補正する画像</param>
''' <param name="rValue">赤の増加値(-255〜255)</param>
''' <param name="gValue">緑の増加値(-255〜255)</param>
''' <param name="bValue">青の増加値(-255〜255)</param>
Public Shared Sub CorrectColorImage( _
        ByVal img As Bitmap, ByVal rValue As Integer, _
        ByVal gValue As Integer, ByVal bValue As Integer)
    For x As Integer = 0 To img.Width - 1
        For y As Integer = 0 To img.Height - 1
            '指定したピクセルの色を取得する
            Dim c As Color = img.GetPixel(x, y)
            '新しい色を計算する
            Dim newR As Integer = Math.Max(0, Math.Min(255, c.R + rValue))
            Dim newG As Integer = Math.Max(0, Math.Min(255, c.G + gValue))
            Dim newB As Integer = Math.Max(0, Math.Min(255, c.B + bValue))
            '新しい色を設定する
            img.SetPixel(x, y, Color.FromArgb(c.A, newR, newG, newB))
        Next
    Next
End Sub

'PictureBox1のClickイベントハンドラ
Private Sub PictureBox1_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles PictureBox1.Click
    '色補正をする画像
    Dim img As New Bitmap("C:\test\1.png")
    '赤を128増加させる
    CorrectColorImage(img, 128, 0, 0)
    'PictureBox1に表示
    If Not PictureBox1.Image Is Nothing Then
        PictureBox1.Image.Dispose()
    End If
    PictureBox1.Image = img
End Sub
C#
コードを隠すコードを選択
//using System.Drawing;

/// <summary>
/// 指定した画像の色を補正する
/// </summary>
/// <param name="img">補正する画像</param>
/// <param name="rValue">赤の増加値(-255〜255)</param>
/// <param name="gValue">緑の増加値(-255〜255)</param>
/// <param name="bValue">青の増加値(-255〜255)</param>
public static void CorrectColorImage(Bitmap img,
    int rValue, int gValue, int bValue)
{
    for (int x = 0; x < img.Width; x++)
    {
        for (int y = 0; y < img.Height; y++)
        {
            //指定したピクセルの色を取得する
            Color c = img.GetPixel(x, y);
            //新しい色を計算する
            int newR = Math.Max(0, Math.Min(255, c.R + rValue));
            int newG = Math.Max(0, Math.Min(255, c.G + gValue));
            int newB = Math.Max(0, Math.Min(255, c.B + bValue));
            //新しい色を設定する
            img.SetPixel(x, y, Color.FromArgb(c.A, newR, newG, newB));
        }
    }
}

//PictureBox1のClickイベントハンドラ
private void PictureBox1_Click(object sender, EventArgs e)
{
    //色補正をする画像
    Bitmap img = new Bitmap(@"C:\test\1.png");
    //赤を128増加させる
    CorrectColorImage(img, 128, 0, 0);
    //PictureBox1に表示
    if (PictureBox1.Image != null)
    {
        PictureBox1.Image.Dispose();
    }
    PictureBox1.Image = img;
}

結果は、以下のようになります。

赤を128増加させた画像

LockBitsメソッドを使用する方法

GetPixelとSetPixelメソッドはあまりパフォーマンスがよくありませんので、画像のすべてのピクセルを書き換える場合は、Bitmap.LockBitsメソッドを使った方がよいでしょう。

以下に、Bitmap.LockBitsメソッドを使って上記のコードを書き直した例を示します。

VB.NET
コードを隠すコードを選択
'Imports System.Drawing
'Imports System.Drawing.Imaging

''' <summary>
''' 指定した画像の色を補正する
''' </summary>
''' <param name="img">補正する画像</param>
''' <param name="rValue">赤の増加値(-255〜255)</param>
''' <param name="gValue">緑の増加値(-255〜255)</param>
''' <param name="bValue">青の増加値(-255〜255)</param>
Public Shared Sub AdjustColorImage( _
        ByVal img As Bitmap, ByVal rValue As Integer, _
        ByVal gValue As Integer, ByVal bValue As Integer)
    '1ピクセルあたりのバイト数を取得する
    Dim pixelFormat As PixelFormat = img.PixelFormat
    Dim pixelSize As Integer = Image.GetPixelFormatSize(pixelFormat) / 8
    If pixelSize < 3 OrElse 4 < pixelSize Then
        Throw New ArgumentException( _
            "1ピクセルあたり24または32ビットの形式のイメージのみ有効です。", _
            "img")
    End If

    'または次のように元の画像とは異なるPixelFormatでLockBitsすることも可能
    'この場合、UnlockBitsで元のPixelFormatに戻る
    'ただし、元のPixelFormatとLockBits時のPixelFormatが異なる場合は、
    '変更した色とは異なる色になる可能性がある
    'pixelFormat = PixelFormat.Format32bppArgb
    'pixelSize = 4

    'Bitmapをロックする
    Dim bmpDate As BitmapData = _
        img.LockBits(New Rectangle(0, 0, img.Width, img.Height), _
                     ImageLockMode.ReadWrite, img.PixelFormat)

    If bmpDate.Stride < 0 Then
        img.UnlockBits(bmpDate)
        Throw New ArgumentException( _
            "ボトムアップ形式のイメージには対応していません。", _
            "img")
    End If

    'ピクセルデータをバイト型配列で取得する
    Dim ptr As IntPtr = bmpDate.Scan0
    Dim pixels As Byte() = New Byte(bmpDate.Stride * img.Height - 1) {}
    System.Runtime.InteropServices.Marshal.Copy(ptr, pixels, 0, pixels.Length)

    'すべてのピクセルの色を補正する
    For y As Integer = 0 To bmpDate.Height - 1
        For x As Integer = 0 To bmpDate.Width - 1
            'ピクセルデータでのピクセル(x,y)の開始位置を計算する
            Dim pos As Integer = y * bmpDate.Stride + x * pixelSize
            '新しい色を計算する
            Dim newR As Integer = _
                Math.Max(0, Math.Min(255, pixels(pos + 2) + rValue))
            Dim newG As Integer = _
                Math.Max(0, Math.Min(255, pixels(pos + 1) + gValue))
            Dim newB As Integer = _
                Math.Max(0, Math.Min(255, pixels(pos) + bValue))
            '色を変更する
            pixels(pos + 2) = CByte(newR)
            pixels(pos + 1) = CByte(newG)
            pixels(pos) = CByte(newB)
        Next
    Next

    'ピクセルデータを元に戻す
    System.Runtime.InteropServices.Marshal.Copy(pixels, 0, ptr, pixels.Length)

    'ロックを解除する
    img.UnlockBits(bmpDate)
End Sub

'PictureBox1のClickイベントハンドラ
Private Sub PictureBox1_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles PictureBox1.Click
    '色補正をする画像
    Dim img As New Bitmap("C:\test\1.png")
    '赤を128増加させる
    AdjustColorImage(img, 128, 0, 0)
    'PictureBox1に表示
    If Not PictureBox1.Image Is Nothing Then
        PictureBox1.Image.Dispose()
    End If
    PictureBox1.Image = img
End Sub
C#
コードを隠すコードを選択
//using System.Drawing;
//using System.Drawing.Imaging;

/// <summary>
/// 指定した画像の色を補正する
/// </summary>
/// <param name="img">補正する画像</param>
/// <param name="rValue">赤の増加値(-255〜255)</param>
/// <param name="gValue">緑の増加値(-255〜255)</param>
/// <param name="bValue">青の増加値(-255〜255)</param>
public static void AdjustColorImage(Bitmap img,
    int rValue, int gValue, int bValue)
{
    //1ピクセルあたりのバイト数を取得する
    PixelFormat pixelFormat = img.PixelFormat;
    int pixelSize = Image.GetPixelFormatSize(pixelFormat) / 8;
    if (pixelSize < 3 || 4 < pixelSize)
    {
        throw new ArgumentException(
            "1ピクセルあたり24または32ビットの形式のイメージのみ有効です。",
            "img");
    }

    //または次のように元の画像とは異なるPixelFormatでLockBitsすることも可能
    //この場合、UnlockBitsで元のPixelFormatに戻る
    //ただし、元のPixelFormatとLockBits時のPixelFormatが異なる場合は、
    //変更した色とは異なる色になる可能性がある
    //pixelFormat = PixelFormat.Format32bppArgb;
    //pixelSize = 4;

    //Bitmapをロックする
    BitmapData bmpDate = img.LockBits(
        new Rectangle(0, 0, img.Width, img.Height),
        ImageLockMode.ReadWrite, img.PixelFormat);

    if (bmpDate.Stride < 0)
    {
        img.UnlockBits(bmpDate);
        throw new ArgumentException(
            "ボトムアップ形式のイメージには対応していません。",
            "img");
    }

    //ピクセルデータをバイト型配列で取得する
    IntPtr ptr = bmpDate.Scan0;
    byte[] pixels = new byte[bmpDate.Stride * img.Height];
    System.Runtime.InteropServices.Marshal.Copy(ptr, pixels, 0, pixels.Length);

    //すべてのピクセルの色を補正する
    for (int y = 0; y < bmpDate.Height; y++)
    {
        for (int x = 0; x < bmpDate.Width; x++)
        {
            //ピクセルデータでのピクセル(x,y)の開始位置を計算する
            int pos = y * bmpDate.Stride + x * pixelSize;
            //新しい色を計算する
            int newR = Math.Max(0, Math.Min(255, pixels[pos + 2] + rValue));
            int newG = Math.Max(0, Math.Min(255, pixels[pos + 1] + gValue));
            int newB = Math.Max(0, Math.Min(255, pixels[pos] + bValue));
            //色を変更する
            pixels[pos + 2] = (byte)newR;
            pixels[pos + 1] = (byte)newG;
            pixels[pos] = (byte)newB;
        }
    }

    //ピクセルデータを元に戻す
    System.Runtime.InteropServices.Marshal.Copy(pixels, 0, ptr, pixels.Length);

    //アンセーフコードを使うと、以下のようにもできる
    //unsafe
    //{
    //    byte* pixelPtr = (byte*)bmpDate.Scan0;
    //    for (int y = 0; y < bmpDate.Height; y++)
    //    {
    //        for (int x = 0; x < bmpDate.Width; x++)
    //        {
    //            //ピクセルデータでのピクセル(x,y)の開始位置を計算する
    //            int pos = y * bmpDate.Stride + x * pixelSize;
    //            //新しい色を計算する
    //            int newR = Math.Max(0, Math.Min(255, pixels[pos + 2] + rValue));
    //            int newG = Math.Max(0, Math.Min(255, pixels[pos + 1] + gValue));
    //            int newB = Math.Max(0, Math.Min(255, pixels[pos] + bValue));
    //            //色を変更する
    //            pixelPtr[pos + 2] = (byte)newR;
    //            pixelPtr[pos + 1] = (byte)newG;
    //            pixelPtr[pos] = (byte)newB;
    //        }
    //    }
    //}

    //ロックを解除する
    img.UnlockBits(bmpDate);
}

//PictureBox1のClickイベントハンドラ
private void PictureBox1_Click(object sender, EventArgs e)
{
    //色補正をする画像
    Bitmap img = new Bitmap(@"C:\test\1.png");
    //赤を128増加させる
    AdjustColorImage(img, 128, 0, 0);
    //PictureBox1に表示
    if (PictureBox1.Image != null)
    {
        PictureBox1.Image.Dispose();
    }
    PictureBox1.Image = img;
}
  • 履歴:
  • 2015/6/30 「LockBitsメソッドを使用する方法」のサンプルで、元のPixelFormatとは異なるPixelFormatでLockBitsできる説明を追加。ボトムアップ形式のチェックを追加など。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。