【C#】画像ファイルを開く

C#で「画像ファイルを開く」というと、画像を表示したり、画像処理をするために、Imageクラス(名前空間:System.Drawing)か、Bitmapクラス(名前空間:System.Drawing)を作成することになります。

 

BitmapクラスはImageクラスを継承しているので、Imageクラスで出来ることはBitmapクラスでも、ほとんど出来るのですが、違いは何か?というと、Bitmapクラスでは画像データ(画像の輝度値)を参照・設定できる点が大きく異なります。

 

そのため画像処理を行う場合は、ほぼBitmapクラスを使うことになります。

 

そこで、画像ファイルからBitmapクラスを生成するメソッドを作成しました。


/// <summary> 
/// ファイルを開くダイアログボックスを表示して画像ファイルを開く 
/// </summary> 
/// <returns>生成したBitmapクラスオブジェクト</returns>
private Bitmap ImageFileOpen()
{
    //ファイルを開くダイアログボックスの作成  
    var ofd = new OpenFileDialog();
    //ファイルフィルタ  
    ofd.Filter = "Image File(*.bmp,*.jpg,*.png,*.tif)|*.bmp;*.jpg;*.png;*.tif|Bitmap(*.bmp)|*.bmp|Jpeg(*.jpg)|*.jpg|PNG(*.png)|*.png";
    //ダイアログの表示 (Cancelボタンがクリックされた場合は何もしない)
    if (ofd.ShowDialog() == DialogResult.Cancel) return null;

    return ImageFileOpen(ofd.FileName);
}

/// <summary>
/// ファイルパスを指定して画像ファイルを開く
/// </summary>
/// <param name="fileName">画像ファイルのファイルパスを指定します。</param>
/// <returns>生成したBitmapクラスオブジェクト</returns>
private Bitmap ImageFileOpen(string fileName)
{
    // 指定したファイルが存在するか?確認
    if (System.IO.File.Exists(fileName) == false) return null;

    // 拡張子の確認
    var ext = System.IO.Path.GetExtension(fileName).ToLower();

    // ファイルの拡張子が対応しているファイルかどうか調べる
    if (
        (ext != ".bmp") &&
        (ext != ".jpg") &&
        (ext != ".png") &&
        (ext != ".tif")
        ) {
        return null;
    }

    Bitmap bmp;

    // ファイルストリームでファイルを開く
    using (var fs = new System.IO.FileStream(
        fileName,
        System.IO.FileMode.Open,
        System.IO.FileAccess.Read))
    {
        bmp = new Bitmap(fs);
    }
    return bmp;
}

このImageFileOpenメソッドの使用例はこんな感じ↓です。

// 画像ファイルを開く
var bmp = ImageFileOpen();
pictureBox1.Image = bmp;

今回はファイルを開くのにFileStreamを使って開いていますが、単に

 

var bmp = new Bitmap(fileName);

 

のようにしてもBitmapクラスオブジェクトが生成されるのですが、画像ファイルを使っている間は、指定したファイルをエクスプローラで削除しようとすると

 

vshost32.exeによってファイルは開かれているため、操作を完了できません。

 

というメッセージが出て、ファイルを削除することができなくなるため、FileStreamを用いました。

 

画像処理のためのC#へ戻る

【C#】Bitmap画像データの拡大縮小

Bitmap画像を拡大縮小するには、Bitmapクラスのコンストラクタで、

Bitmap bmpOrijinal = new Bitmap("sample.bmp");

int scale = 50;
Bitmap bmpResize = new Bitmap(
    bmpOrijinal,
    bmpOrijinal.Width * scale,
    bmpOrijinal.Height * scale
    );

pictureBox1.Image = bmpResize;

(処理結果)

 

のようにすると簡単に画像を拡大縮小をすることができますが、このやり方では、いくつか不都合があります。

 

●モノクロ8bit画像に対応していない。

●補間モードを指定できない。

●画像を拡大すると、0.5画素分、位置がズレている。

●リサイズ後のBitmapは32bitになってしまう。

 

ということで、これらに対応したメソッドを作ってみました。

/// <summary>
/// Bitmap画像データのリサイズ
/// </summary>
/// <param name="original">元のBitmapクラスオブジェクト</param>
/// <param name="width">リサイズ後の幅</param>
/// <param name="height">リサイズ後の高さ</param>
/// <param name="interpolationMode">補間モード</param>
/// <returns>リサイズされたBitmap</returns>
private Bitmap ResizeBitmap(Bitmap original, int width, int height, System.Drawing.Drawing2D.InterpolationMode interpolationMode)
{
    Bitmap bmpResize;
    Bitmap bmpResizeColor;
    Graphics graphics = null;

    try
    {
        System.Drawing.Imaging.PixelFormat pf = original.PixelFormat;

        if (original.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
        {
            // モノクロの時は仮に24bitとする
            pf = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
        }

        bmpResizeColor = new Bitmap(width, height, pf);
        var dstRect = new RectangleF(-0.5f, -0.5f, width, height);
        var srcRect = new RectangleF(-0.5f, -0.5f, original.Width, original.Height);
        graphics = Graphics.FromImage(bmpResizeColor);
        graphics.Clear(Color.Transparent);
        graphics.InterpolationMode = interpolationMode;
        graphics.DrawImage(original, dstRect, srcRect, GraphicsUnit.Pixel);

    }
    finally
    {
        if (graphics != null)
        {
            graphics.Dispose();
        }
    }

    if (original.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
    {
        // モノクロ画像のとき、24bit→8bitへ変換

        // モノクロBitmapを確保
        bmpResize = new Bitmap(
            bmpResizeColor.Width, 
            bmpResizeColor.Height, 
            System.Drawing.Imaging.PixelFormat.Format8bppIndexed
            );

        var pal = bmpResize.Palette;
        for (int i = 0; i < bmpResize.Palette.Entries.Length; i++)
        {
            pal.Entries[i] = original.Palette.Entries[i];
        }
        bmpResize.Palette = pal;

        // カラー画像のポインタへアクセス
        var bmpDataColor = bmpResizeColor.LockBits(
                new Rectangle(0, 0, bmpResizeColor.Width, bmpResizeColor.Height),
                System.Drawing.Imaging.ImageLockMode.ReadWrite,
                bmpResizeColor.PixelFormat
                );

        // モノクロ画像のポインタへアクセス
        var bmpDataMono = bmpResize.LockBits(
                new Rectangle(0, 0, bmpResize.Width, bmpResize.Height),
                System.Drawing.Imaging.ImageLockMode.ReadWrite,
                bmpResize.PixelFormat
                );

        int colorStride = bmpDataColor.Stride;
        int monoStride = bmpDataMono.Stride;

        unsafe
        {
            var pColor = (byte*)bmpDataColor.Scan0;
            var pMono = (byte*)bmpDataMono.Scan0;
            for (int y = 0; y < bmpDataColor.Height; y++)
            {
                for (int x = 0; x < bmpDataColor.Width; x++)
                {
                    // R,G,B同じ値のため、Bの値を代表してモノクロデータへ代入
                    pMono[x + y * monoStride] = pColor[x * 3 + y * colorStride];    
                }
            }
        }

        bmpResize.UnlockBits(bmpDataMono);
        bmpResizeColor.UnlockBits(bmpDataColor);

        // 解放
        bmpResizeColor.Dispose();
    }
    else
    {
        // カラー画像のとき
        bmpResize = bmpResizeColor;
    }

    return bmpResize;
}

このResizeBitmapメソッドを使って、このようなプログラムを書くと

Bitmap bmpOrijinal = new Bitmap("sample.bmp");

int scale = 50;
Bitmap bmpResize = ResizeBitmap(
    bmpOrijinal,
    bmpOrijinal.Width * scale,
    bmpOrijinal.Height * scale,
    System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor
    );

pictureBox1.Image = bmpResize;

(処理結果)

 

となります。

モノクロの処理は少しまどろっこしい感じもしますが、画像データのリサイズや補間の部分を自前で書くよりは簡単!?

 

画像処理のためのC#へ戻る

【C#】画像の座標系

画像を描画するにはDrawImageメソッドを用いますが、DrawImageメソッドはいくつものオバーロードが定義されていますが、画像の拡大縮小を考慮すると、個人的には以下の定義をよく用います。

public void DrawImage(
     Image image,
     Rectangle destRect,
     int srcX,
     int srcY,
     int srcWidth,
     int srcHeight,
     GraphicsUnit srcUnit
)
public void DrawImage(
     Image image,
     Rectangle destRect,
     float srcX,
     float srcY,
     float srcWidth,
     float srcHeight,
     GraphicsUnit srcUnit
)

この時の座標系は下図の用になり、元の画像に対して、画像の描画先(destRect)を大きくすると画像の拡大となり、描画先を小さくすると画像の縮小となります。

 

 

しかしながら、以下のように単純に画像の拡大のプログラムを実行すると、

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
     var srcImage = new Bitmap("sample.bmp");
     Graphics g = e.Graphics;
     g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;

     g.DrawImage(
          srcImage,
          new Rectangle(0, 0, srcImage.Width * 50, srcImage.Height * 50),
          0,
          0,
          srcImage.Width,
          srcImage.Height,
          GraphicsUnit.Pixel
     );
}

 

このように画素の半分だけ画像が左上にずれて表示されてしまいます。

 

これは、元の画像の座標系の原点が画素の中心部分にあるためで、このようになります。

 

このズレを無くすには、やり方は2つ

 

●PixelOffsetModeを指定する方法

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
     var srcImage = new Bitmap("sample.bmp");
     Graphics g = e.Graphics;
     g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
     g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;

     g.DrawImage(
          srcImage,
          new Rectangle(0, 0, srcImage.Width * 50, srcImage.Height * 50),
          0,
          0,
          srcImage.Width,
          srcImage.Height,
          GraphicsUnit.Pixel
     );
}

●元の画像の座標を0.5画素ズラす方法

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    var srcImage = new Bitmap("sample.bmp");

    Graphics g = e.Graphics;
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;


    g.DrawImage(
        srcImage,
        new Rectangle(0, 0, srcImage.Width * 50, srcImage.Height * 50),
        -0.5f,
        -0.5f,
        srcImage.Width,
        srcImage.Height,
        GraphicsUnit.Pixel
        );
}

このようにすると、表示のズレが無くなります。

 

単に画像を拡大/縮小表示するだけなら、PixelOffsetModeを指定した方が簡単だと思いますが、アフィン変換などの座標変換を伴う場合は、元の画像の座標を-0.5分だけ図ずらしたやり方の方が良いかと思います。

 

画像処理のためのC#へ戻る

Canny edge detectionの処理アルゴリズム

Canny edge detectionは画像の輪郭部分を抽出するのに、よく用いられるのですが、詳細なアルゴリズムを理解しないまま使われている事も多いのではないでしょうか?(それ、私)

特に、ヒステリシスしきい値の部分などは、詳細な説明も少ないので、今回、まとめてみたいと思います。

 

まずは処理結果から。

 

処理前画像 Canny edge detection処理後

 

大まかな流れとしては

1.ノイズ除去

ガウシアンフィルタなどでノイズを除去します

 

2.輪郭抽出

ソーベルフィルタで輪郭を抽出します

 

3.非極大抑制

エッジの強さが極大となる部分以外を除外します。

詳細は後述

 

4.ヒステリシスしきい値処理

詳細は後述

 

完成!!

 

以上の4ステップで成り立っていますが、非極大抑制とヒステリシスしきい値処理がちょっとわかりづらいので、詳細は以下の通りです。

 

非極大抑制

ソーベルフィルタ後のエッジの強さを3D表示すると、こんな感じ↓になります。

 

この山の尾根のみを抽出するのが、非極大抑制となります。

 

具体的には、まず、ソーベルフィルタでエッジの強さを求めて、エッジの角度を求めます。

横方向

縦方向

エッジの強さ

エッジの角度(実際にはエッジの線に直行する向きが求まります)

 

エッジの向き(エッジの法線方向)が求まったら、

0°(-22.5°~22.5°)

45°(22.5°~47.5°)

90°(47.5°~112.5°)

135°(112.5°~157.5°)

の4つに分類します。

 

エッジの法線方向の3画素のエッジの強さを用いて、中央のエッジの強さが、残す2つの良さよりも大きければ、その部分が極大点ということになり、中央のエッジの強さが最大とならない部分を除外する処理が非極大抑制となります。

エッジの強さ 非極大抑制

 

この処理によりエッジの部分の細線化が行われます。

 

ヒステリシスしきい値処理

ヒステリシスしきい値の処理については、Webで探してもあまりわかりやすいのが無いような気がしますが、以下の通りです。

 

まず、非極大抑制処理で細線化された画像に対して、2つのしきい値を用いて二値化します。

2つのしきい値は、なんとなくエッジっぽい部分(しきい値「小」)と、確実にエッジな部分(しきい値「大」)となるしきい値を設定します。

 

非極大抑制処理画像

 

しきい値「小」 しきい値「大」

 

 

この2つの画像を用いて、しきい値「小」の中から、しきい値「大」につながっている部分のみを残します。

分かりやすいように、しきい値「小」の画像にしきい値「大」のエッジを赤くして重ね合わせて表示すると↓

 

 

この赤い線につながっていない部分を除外します。

 

この処理がまさにCanny edge detectionとなります。

 

(参考)

OpenCV 3.0.0-dev documentation Canny Edge Detection

 

関連記事

【OpenCV-Python】Canny(Canny edge detection)

 

画像処理アルゴリズムへ戻る

【C#】ImageDataクラスライブラリ公開

画像処理プログラムの基本は画像の輝度値(画素値)を取得して、様々な処理をすることとなりますが、C#では輝度値を取得するメソッドにSetPixel/GetPixelのメソッドが用意されていますが、これは処理が遅いことで有名。

 

そこで、OpenCVのIplImage構造体やMatクラスのエッセンスを取り入れつつ、.NETのBitmapクラスっぽく、さらに画像データを簡単に扱えるようにすることを目指したImageDataクラスなるライブラリを作成してみました。

 

基本コンセプトは

●画像の輝度値の取得/設定が簡単

●SetPixel/GetPixelよりは高速

●シンプルなプログラムが書けること

 

といったところです。

 

画像処理をこれから学ぼうとする人、とりあえず簡単にアルゴリズムの評価プログラムを作って見たい人向けを想定しています。パフォーマンス重視の方はポインタ(Scan0)を参照してください。

 

というスタンスです。

今のところフリーですので、ご興味があれば使ってみてください。

ただし、本ライブラリを使用した事による不具合等の責任を負えませんので、ご了承願います。

 

ファイル:ImagingSolution.Imaging.ImageData.dll

.NET Frameworkバージョン:4.5.2

Visual Studio 2015 C#を使用

 

ダウンロード

公開日 バージョン サンプルプログラム
2016.3.19 Ver.0.1.1 ImageDataSample011.zip Bitmapファイル読込部分のバグ修正

Reaginプロパティに影響

2016.3.17 Ver.0.1.0 ImageDataSample010.zip 初版

バグなどが含まれる可能性があります。

今後、仕様が変更される可能性もあります。

 

コンストラクター

名前 説明
ImageData(int, int, int, int) 画像の幅、高さ、画像のビット数、メモリのビット数を指定して、ImageDataクラスの新しいインスタンスを初期化します。
ImageData(int, int, int, System.Drawing.Imaging.PixelFormat, System.IntPtr) 画像の幅、高さ、メモリの幅のバイト数、ピクセルフォーマット、外部メモリのポインタを指定して、ImageDataクラスの新しいインスタンスを初期化します。
ImageData(int, int, System.Drawing.Imaging.PixelFormat) 画像の幅、高さ、ピクセルフォーマットを指定して、ImageDataクラスの新しいインスタンスを初期化します。
ImageData(string) 画像ファイル名を指定して、ImageDataクラスの新しいインスタンスを初期化します。

 

プロパティ

名前 説明
BiCubicVal biCubiによる補間の計算時に用いる変数を取得/設定します。

初期値:-0.75

BorderType 画像の外側の輝度値の参照方法をBorderTypeEnumにより取得/設定します。

初期値:BorderTypeEnum.Mirror

BufferBit 画像を格納するメモリのビット数を取得します。

8、16、24、32のいづれを指定します。

ByteAlignment 画像を格納するメモリのアライメントのバイト数を取得します。
Channel 画像のチャンネル数(色数)を取得します。

1, 3, 4のいづれか

Height 画像の高さ(画素数)を取得します。
ImageBit 画像のビット数を取得します。

8~16、24、30、32のいづれを指定します。

InterpolationMode 画素間の輝度値の取得時の補間モードをInterpolationModeEnumにより取得/設定します。

初期値:InterpolationModeEnum.Bilinear

MinusValueMode 輝度値(画素値)に負の値を設定時の処理方法をMinusValueModeEnumにより取得/設定します。

初期値:MinusValueModeEnum.Zero

PixelFormat 画像のピクセルフォーマットをSystem.Drawing.Imaging.PixelFormatにより取得します。
Region 画像処理処理範囲(参照範囲)をSystem.Drawing.Rectangleにより取得/設定します。
Sacn0 画像データを格納するメモリのポインタをSystem.IntPtrにより取得します。
Stride 画像データを格納するメモリの幅をバイト数で取得します。
Width 画像の幅(画素数)を取得します。

 

インデクサ

名前 説明
this[float, float, int] 画像の行位置(Y座標)、列位置(X座標)、チャンネル番号(B[0]、G[1]、R[2])を指定して輝度値を取得します。
this[float, float] 画像データの行位置(Y座標)、列位置(X座標)を指定して輝度値を取得します。
this[int, int, int] 画像の行位置(Y座標)、列位置(X座標)、チャンネル番号(B[0]、G[1]、R[2])を指定して輝度値を取得/設定します。
this[int, int] 画像データの行位置(Y座標)、列位置(X座標)を指定して輝度値を取得/設定します。

 

列挙型

名前 説明
BorderTypeEnum BorderTypeプロパティを指定するのに用います。

Clamp:最外周の輝度値を用いる

Mirror:(初期値)画像の外側から折り返した輝度値を用いる

ToZero:画像の外側は輝度値0にする

InterpolationModeEnum InterpolationModeプロパティで画素間の輝度値を取得する際の補間方法を指定するのに用います。

Bicubic:バイキュービック

Bilinear:(初期値)バイリニア

NearestNeighbor:ニアレストネイバー

MinusValueModeEnum インデクサで負の値を指定した時の処理方法を指定します。

Absolute:負の場合は絶対値をとります。

Zero:(初期値)負の場合は0にします。

 

メソッド

名前 説明
Clone() ImageDataクラスオブジェクトのクローンを作成します。画像データもコピーされます。
Clone(bool) 画像データをコピーする(true)/しない(false)を指定してImageDataクラスオブジェクトのクローンを作成します。
CopyMemory(System.IntPtr, System.IntPtr, int) コピー先のポインタ、コピー元のポインタ、コピーサイズを指定して画像データをコピーします。
CopyTo(ImagingSolution.Imaging.ImageData) 画像データを指定したImageDataクラスオブジェクトへコピーします。
Dispose() ImageDataクラスで使用されているリソースを解放します。
ImageData.IsEqualImageSize(

ImagingSolution.Imaging.ImageData, ImagingSolution.Imaging.ImageData)

2つのImageDataクラスの画像サイズが等しい(true)か等しくない(false)かを確認します。
ResetRegion()  指定した領域(Regionプロパティ)を解除します。
Save(string) ImageDataクラスの画像データをファイルに保存します。
ToBitmap() ImageDataクラスオブジェクトをSystem.Drawing.Bitmapクラスへ変換します。
ZeroMemory(System.IntPtr, int) ポインタで示されているメモリから指定バイト数分、0に設定します。

 

サンプルプログラム

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ImageDataSample
{
    public partial class Form1 : Form
    {

        /// 
        ImagingSolution.Imaging.ImageData _img;

        public Form1()
        {
            InitializeComponent();
        }

        /// 
        /// 
        private void mnuFileOpen_Click(object sender, EventArgs e)
        {
            //ファイルを開くダイアログボックスの作成  
            var ofd = new OpenFileDialog();
            //ファイルフィルタ  
            ofd.Filter = "Image File(*.bmp,*.jpg,*.png,*.tif)|*.bmp;*.jpg;*.png;*.tif|Bitmap(*.bmp)|*.bmp|Jpeg(*.jpg)|*.jpg|PNG(*.png)|*.png";
            //ダイアログの表示 (Cancelボタンがクリックされた場合は何もしない)
            if (ofd.ShowDialog() == DialogResult.Cancel) return;

            if (_img != null) _img.Dispose();

            _img = new ImagingSolution.Imaging.ImageData(ofd.FileName);

            picImage.Image = _img.ToBitmap();
        }

        /// 
        /// 
        private void mnuFileSave_Click(object sender, EventArgs e)
        {
            // 画像データが設定されていない場合は何もしない
            if (_img == null) return;

            //名前を付けて保存ダイアログボックスの作成  
            var sfd = new SaveFileDialog();
            //ファイルフィルタ  
            sfd.Filter = "Bitmap(*.bmp)|*.bmp|Jpeg(*.jpg)|*.jpg|PNG(*.png)|*.png|CSV(*.csv)|*.csv";
            //ダイアログの表示 (Cancelボタンがクリックされた場合は何もしない)
            if (sfd.ShowDialog() == DialogResult.Cancel) return;

            // 名前を付けて画像データを保存
            _img.Save(sfd.FileName);
        }

        /// 
        /// 
        /// /param>
        private void mnuFileExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// 

 

ImageDataクラスライブラリへ戻る

【参考文献】ディジタル画像処理[改訂第二版]

旧版のディジタル画像処理の本も、一時期は毎日持ち歩いて、本が手あかで汚くなるぐらい、よく見ていましたが、その新版となると気にならない訳がない。

 

 

本をパラパラめくっていくと、始めのうちは、あまり変わった印象が無かったのですが、途中で藤吉先生の写真が載っていたのをみつけて、あれ?と思ったら、11、12章の記事は藤吉先生により追記されていました。

(2020.5.28)追記
さらに深層学習の項目+αが追加されて、改定第二版となりました。(コメントにより教えて頂きました。)

 

ざっと見た感じでは、旧版と比べてDoGやSIFT、FAST、顔検出などの記事が書かれていたのが新しい感じ。

 

この本は、まったくの入門者にとっては読むのが難しいかもしれませんが、画像処理をテーマとした卒研をする人、新社会人で画像処理の仕事をする人、簡単な二値化やフィルタ処理をやった事はあるけど、もう一段ステップアップを目指したい人には是非読んで欲しい本です。

 

目次

1.イントロダクション

2.ディジタル画像の撮影

3.画像の性質と色空間

4.画像ごとの濃淡変換

5.領域に基づく濃淡変換(空間フィルタリング)

6.周波数領域におけるフィルタリング

7.画像の復元と生成

8.幾何学的変換

9.2値画像処理

10.領域処理

11.パターン・図形・特徴の検出とマッチング

12.パターン認識

13.深層学習による画像認識と生成

14.動画像処理

15.画像からの3次元復元

16.光学的解析とシーンの復元

17.画像符号化

 

詳細な目次(変更内容)はこちらで確認下さい。

 

【参考書籍】Interface7月号はカメラx画像処理特集

Interface 2013年4月号に引き続き、7月号でも画像処理の特集を扱っています。

 

 

個人的には第1章の位置・角度を測る部分が、ちょっとマニアックな感じが好きかも。

 

第2章のKinectの説明は、MFCな部分が、ちょっと、とっつきにくい。(MFCやった事ないもので。)

しかし、みなさんの関心は、もうすでに新しいKinectの方へ向かっているでしょうね。

(参考)

Xbox Oneの新Kinectは大幅進化、表情や心拍も認識。6人同時に全身キャプチャ

http://japanese.engadget.com/2013/05/21/xbox-one-kinect-6/

 

第7章はタイトル的に、いわゆる「形状マッチング」を期待したところだったのですが、輪郭の抽出と、特徴点のマッチングと、テンプレートマッチングだったのが、ちょっと残念。

 

目次

第1章 実験研究!符号パターンをカメラで撮影して位置・角度を測る

第2章 背骨のゆがみ検出にも!奥行きカメラKinectx画像処理ライブラリOpenCV

第3章 スゴイ画像処理が身近になった理由・・・ハードの進化

第4章 画像処理を試すためのハードウェア

第5章 定番 画像処理ライブラリOpenCVの準備

第6章 実験!色と輝度を整える

第7章 実験!形状認識&マッチング

第8章 実験!動きの検出

 

他、第2特集として、Cコンパイラ&最適化入門 という項目もあります。

こちらはハード寄りな内容なので、苦手だわ~

 

 

参考文献へ戻る

画像センシング展/SSII2012

2012.6.6(水)~2012.6.8(金)にパシフィコ横浜にて画像センシング展2012 が開催されます。

 

この展示会は12月に開催される画像機器展に並んで画像処理機器(カメラ、レンズ、キャプチャボード、照明、画像処理ライブラリなど)が一堂に会する大きな(画像処理の展示会としては)展示会です。

 

画像処理を使った検査システムなどを検討している方であれば、是非とも参加されると良いと思います。
技術相談コーナーなどもあります。
何故か私も最終日の最後の時間に相談コーナーを担当する事に...

http://www.adcom-media.co.jp/iss/gijutsu_schedule/?ml_20120604

 

また、主に出展社による無償のイメージングセミナーも開催されるので、興味のある内容があれば参加されると良いと思います。

 

私は「KINECT テクノロジーと NUI の可能性」という内容に興味津々。(仕事とは全く関係ありませんが。)

 

今回の展示会では、Kinect for Windowsが出た事でKinectの商用利用が可能になったので、Kinect関連の展示や発表がある事を期待しつつ...

 

同時開催でSSII2012というシンポジウム(有料)も開催されます。

 

こちらは主に研究論文発表の内容ですが、画像処理の研究に携わっている方であれば、かなり有意義だと思います。

 

主な内容

特別講演 http://www.ssii.jp/special_program_lecture.html

チュートリアル http://www.ssii.jp/special_program_tutorial.html

オーガナイズドセッション http://www.ssii.jp/special_program_organized.html

ん~、気になる。

バイラテラルフィルタ

ガウシアンフィルタなどのフィルタでは、ノイズをできるだけ除去しようとすると、輪郭もボケてしまうという欠点がありました。
この欠点を解決しようとした処理アルゴリズムがバイラテラルフィルタ(bilateral filter)です。

 

バイラテラルフィルタは処理前の画像データの配列をf(i, j)、処理後の画像データの配列をg(i, j)とすると

 

 

となります。

 

ただし、がカーネルのサイズ、σがガウシアンフィルタを制御、σが輝度差を制御しています。

と言われても、何だか式が難しくて良く分かりません。

 

でも、分母分子に出てくる最初のexpの部分はガウシアンフィルタで見たことがあるな~

という事に気が付けば、突破口が開けます。

 

2つ目のexpの部分が良く分からないので、とりあえず取っちゃってみて、

 

 

とすると、分母の部分がガウシアンフィルタと少し違うけど、Σの範囲が-W~Wなので、(2W+1)×(2W+1)のカーネルサイズのガウシアンフィルタになっています。

 

結局、分母はカーネルの値の合計なので、やっぱりガウシアンフィルタそのものだという事に気が付きます。

 

ここで、ガウシアンフィルタを使って、どうやれば輪郭をぼやかさず、ノイズだけを除去できるか?を考えると、カーネルの中心の輝度値と差の少ないところだけをガウシアンフィルタで平滑化すればいいのではないか?という発想が浮かびます。

 

例えば次のような画像において、

 

四角で囲まれた部分を拡大し注目すると、5x5のガウシアンフィルタの場合、中心の輝度値に近い部分を重み「1」、輝度差が大きい部分は重みが「0」になるようにして、

 

 

一般的な5x5のガウシアンフィルタの係数↓、

 

 

に重みをかけると、それぞれのカーネルの値は

 

カーネルの合計の169で割る カーネルの合計の209で割る

 

となります。

このようにして、場所、場所のカーネルの値を画像に合わせて変えて行くと、輪郭を残しつつノイズだけを除去できそうな感じがします。

 

しかし、輪郭付近の重みは「1」にするべきか?「0」にするべきか?少し悩みます。

そこで、カーネルの中心の輝度値との差に基づいて、重みを「0.3」や「0.8」のようなグレーゾーンを設けるために、輝度差を横軸にした正規分布を用いてみます。

 

正規分布のグラフはこんな感じ↓

 

になっていて、中心の0付近ほど値が大きく、外側(+方向、-方向)へ行くに従って値が小さくなります。

この性質を使ってカーネルの中心の輝度値との差「f(i, j)- f(i + m, j + n) 」を横軸にとった正規分布の式は

 

 

となり、これを重みに使うと、「1」「0」だった重みが、輝度差が小さいと重みが大きく、輝度差が大きいと重みが小さくなるように、なだらかに変化します。

 

この正規分布を重みとしたのが、まさにバイラテラルフィルタなのです。

つまりバイラテラルフィルタは

 

正規分布の重み付きガウシアンフィルタ

 

なのです。

 

ここで、最初に示したバイラテラルフィルタの式を見てみると、

分子は(2W+1)×(2W+1)サイズのカーネルの範囲内の輝度値に、ガウシアンフィルタの係数をかけ、さらにカーネルの中心との輝度差を用いた正規分布の値を掛け合わせた値、

分母はカーネルの合計値で、カーネルの合計値が1になるように調整しています。

 

Wはカーネルの大きさ、σは通常のガウシアンフィルタの係数と同じ。

σの値が、カーネルの中心の輝度値との差をどの程度許容するか?を制御している事が分かります。

そのため、σの値を大きくしていくと、ただのガウシアンフィルタの処理に近づき、輪郭もぼやけてしまいます。逆にσの値を小さくし過ぎると、ノイズ除去効果が弱くなります。

 

そこで、実際にはエッジを保持しつつノイズをできるだけ除去したい場合は、1回のバイラテラルフィルタでσ1、σの値を調整しようとするよりも、バイラテラルフィルタを何回か繰り返した方が効果的です。

 

原画 1回目 2回目
3回目 4回目 5回目

 

このように、バイラテラルフィルタはガウシアンフィルタのカーネルに輝度差に基づいて重みを付けている訳ですが、この、×××に基づいて重みを付ける処理という考え方は、処理を安定させるポイントになったりします。

 

例えばロバスト推定法なんかも近い感じ。

 

画像処理アルゴリズムへ戻る

国際画像機器展2011

2011.12.7(水)~12.9(金)にパシフィコ横浜にて国際画像機器展2011が開催されます。

国際画像機器展2011

http://www.adcom-media.co.jp/ite/

 

無料ですので、画像処理やカメラ、照明、キャプチャボードなどをお探しの方、まずは事前登録してみては如何でしょうか?

http://www.adcom-media.co.jp/ite/getinv/

 

また、無料のセミナーも開催されています。

http://www.adcom-media.co.jp/ite/seminar/

 

セミナーの内容だけ見ていると、USB3.0、GigE、CoaXPressなどと新しいカメラのインターフェースが登場し、時代が動きつつある感じがします。

 

で、私は、おそらく最終日の9日に説明員で立っているかも?知れないので、お気づきの方はお声をかけて下さい。

 

OpenCV-2.3.2-GPU-demo-pack-win32を試す

久々の投稿ですがSourceforgのOpenCVのページにGPUデモのプログラムが公開されていました。

http://sourceforge.net/projects/opencvlibrary/files/opencv-win/2.3.1/

 

バージョンもOpenCV2.3.2 って、まだ公開されていないのに...

 

で早速、このファイルをダウンロードし、試してみました。

私の環境は

OS:Windows7 64bit (ただし、サンプルは32bit動作)

CPU:Core i7 870 (2.93GHz)

GPU:NVIDIA GeForce GTX470

 

で、パフォーマンス評価用のサンプル(demo_performance.exe)を実行した時の結果がこちら↓

CPU msec   GPU msec   SPEEDUP   DESCRIPTION
matchTemplate
480 5 x86.5 src 3000, templ 5, 32F, CCORR
545 21 x24.9  src 3000, templ 25, 32F, CCORR
792 31 x25.4  src 3000, templ 5, 32F, CCORR
minMaxLoc
7  1  x4.81  src 2000, 32F, no mask
28  2  x14.3  src 4000, 32F, no mask
 106  3  x28.2  src 8000, 32F, no mask
 remap   
 8  0  x35.9  src 1000,8UC1
 30  0  x50.6  src 2000,8UC1
 124  2  x59.1  src 4000,8UC1
 12  0  x32.2  src 1000,8UC3
 42  1  x34.6  src 2000,8UC3
 164  4  x34.7  src 4000,8UC3
 11  0  x37.8  src 1000,8UC4
 40  0  x41.4  src 2000,8UC4
 156  3  x43.7  src 4000,8UC4
 29  0  x79.7  src 1000,16SC3
 117  1  x88.8  src 2000,16SC3
 481  4  x98.1  src 4000,16SC3
 dft   
 107  3  x29.2  size 1000, 32FC2, complex-to complex
 402 8  x45.3  size 2000, 32FC2, complex-to complex
 1781  32  x55.4  size 4000, 32FC2, complex-to complex
 cornerHarris
 129  19  x6.55  size 2000, 32F
 499  70  x7.06  size 4000, 32F
 integral
28 15 x1.82  size 4000, 8U
20 9 x2.04  size 4000, 8U
20 9 x2.11  size 4000, 8U
21 9 x2.2  size 4000, 8U
21 9 x2.15  size 4000, 8U
norm
139 5 x27.3 size 2000, 32FC4, NORM_INF
310 8 x35.2 size 3000, 32FC4, NORM_INF
555 14 x38.2 size 4000, 32FC4, NORM_INF
meanShift
304 8 x36.3  size 400, 8UC3 vs 8UC4
1187 29 x40.2  size 800, 8UC3 vs 8UC4
SURF
5179 117 x43.9
BruteForceMatcher
1230 8 x144 match
1257 9 x136 knnMatch, 2
1301 9 x139 knnMatch, 3
1220 9 x135 radiusMatch
magnitude
41 0 x86 size 2000
88 1 x85 size 3000
162 1 x95 size 4000
add
10 0 x20.1 size 2000, 32F
22 1 x21.4 size 3000, 32F
41 1 x23.9 size 4000, 32F
log
25 0 x55.8 size 2000, 32F
54 0 x66.1 size 3000, 32F
97 1 x71.3 size 4000, 32F
exp
25 0 x53 size 2000, 32F
58 0 x73.7 size 3000, 32F
10 1 x74.6 size 4000, 32F
mulSpectrums
24 0 x53 size 2000, 32F
58 0 x73.7 size 3000, 32F
101 1 x74.6 size 4000, 32F
resize
7 0 x33.5 size 1000, 8UC1, up
28 0 x50.4 size 2000, 8UC1, up
64 1 x53.3 size 3000, 8UC1, up
2 0 x26.6 size 1000, 8UC1, down
8 0 x51.6 size 2000, 8UC1, down
18 0 x94.6 size 3000, 8UC1, down
21 2 x8.5 size 1000, 8UC3, up
82 8 x10.2 size 2000, 8UC3, up
187 17 x10.9 size 3000, 8UC3, up
7 0 x13.8 size 1000, 8UC3, down
26 1 x23.8 size 2000, 8UC3, down
58 1 x32.7 size 3000, 8UC3, down
27 1 x17.1 size 1000, 8UC4, up
108 4 x26 size 2000, 8UC4, up
250 9 x27.5 size 3000, 8UC4, up
9 0 x14.6 size 1000, 8UC4, down
32 0 x37.4 size 1000, 8UC4, down
73 1 x52 size 1000, 8UC4, down
9 2 x4.72 size 1000, 32FC1, up
41 6 x6.25 size 2000, 32FC1, up
88 13 x6.45 size 3000, 32FC1, up
2 0 x4.92 size 1000, 32FC1, down
10 0 x12.1 size 2000, 32FC1, down
23 1 x17.2 size 3000, 32FC1, down
cvtColor
29 0 x37.1 size 4000, CV_GRAY2BGRA
82 1 x61.8 size 4000, CV_BGR2YCrCb
105 1 x71.3 size 4000, CV_YCrCb2BGR
123 1 x84.4 size 4000, CV_BGR2XYZ
116 1 x83.7 size 4000, CV_XYZ2BGR
195 2 x72.9 size 4000, CV_BGR2HSV
550 2 x189 size 4000, CV_HSV2BGR
erode 
9 3 x2.65 size 2000
22 7 x3.07 size 3000
39 11 x3.35 size 4000
threshold   
0 0  x1.62 size 1000, 8U, THRESH_BINARY
1 0  x6.15 size 2000, 8U, THRESH_BINARY
3 0  x11.1 size 3000, 8U, THRESH_BINARY
6 0  x11.4 size 4000, 8U, THRESH_BINARY
1 0  x7.49 size 1000, 32F, THRESH_BINARY
6 0  x18.8 size 2000, 32F, THRESH_BINARY
14 0  x19.6 size 3000, 32F, THRESH_BINARY
25 1  x21.9 size 4000, 32F, THRESH_BINARY
pow   
5 0 x32.9 size 1000, 32F
20 0 x57.1 size 2000, 32F
44 0 x58.2 size 3000, 32F
83 1 x55.8 size 4000, 32F
projectPoints
49 2 x23.3 size 1000000
37 2 x16.1 size 714285
26 1 x24.5 size510203
19 0 x20.9 size 364430
12 0 x21.6 size 230307
solvePnPRansac
217 118 x1.84 num_points 5000
392 120 x3.25 num_points 18800
1315 127 x10.3 num_points 70688
4984 160 x31 num_points 265786
GaussianBlur
1  0 x4.26 8UC1, size 1000
6  1 x5.61 8UC1, size 2000
14  2 x6.08 8UC1, size 3000
24  3 x6.36 8UC1, size 4000
5  0 x7.57 8UC4, size 1000
24  2 x10.2 8UC4, size 2000
56  4 x11.8 8UC4, size 3000
100  8 x11.7 8UC4, size 4000
2  0 x5.16 32FC1, size 1000
8  1 x7.39 32FC1, size 2000
17  2 x7.6 32FC1, size 3000
33  3 x8.63 32FC1, size 4000
pryDown
18 5 x3.55 8UC1, size 4000
10 2 x3.62 8UC1, size 3000
4 1 x3.41 8UC1, size 2000
1 0 x2.74 8UC1, size 1000
64 6 x9.52 8UC3, size 4000
38 3 x10 8UC3, size 3000
16 1 x9.26 8UC3, size 2000
4 0 x7.71 8UC3, size 1000
66 7 x8.98 8UC4, size 4000
38 4 x9.14 8UC4, size 3000
17 2 x6.36 8UC4, size 2000
4 0 x7.98 8UC4, size 1000
107 6 x15.6 16SC4, size 4000
60 3 x15.6 16SC4, size 3000
26 1 x14.9 16SC4, size 2000
7 0 x10.2 16SC4, size 1000
29 5 x5.7 32FC1, size 4000
17 3 x5.92 32FC1, size 3000
7 1 x5.65 32FC1, size 2000
1 0 x4.62 32FC1, size 1000
88 6 x13 32FC3, size 4000
52 3 x13.8 32FC3, size 3000
22 1 x12.7 32FC3, size 2000
5 0 x4.92 32FC3, size 1000
122 7 x17 32FC4, size 4000
67 4 x16.9 32FC4, size 3000
30 1 x16.6 32FC4, size 2000
7 0 x14.4 32FC4, size 1000
pyrUp
51 5 x8.97 8UC1, size 2000
12 1 x8.52 8UC1, size 1000
145 8 x17.3 8UC3, size 2000
36 2 x16.5 8UC3, size 1000
198 11 x17.6 8UC4, size 2000
48 2 x16.7 8UC4, size 1000
170 8 x19.6 16SC3, size 2000
44 2 x20.1 16SC3, size 1000
69 5 x12.2 32FC1, size 2000
17 1 x11.4 32FC1, size 1000
205 7 x27.3 32FC3, size 2000
21 1 x26.2 32FC3, size 1000
equalizeHist
2 1 x1.93 size 1000
10 2 x4.78 size 2000
23 3 x6.37 size 3000
Canny
29 3  x9.63
reduce
1 0 x7.79 size 1000, dim = 0
1 0 x10.1 size 1000, dim = 1
6 0 x14.5 size 2000, dim = 0
6 0 x25.2 size 2000, dim = 1
13 0 x17.5 size 3000, dim = 0
13 0 x29.3 size 3000, dim = 1

average GPU speedup: x29.202

 

sizeの表記は、size1000の場合、画像サイズは1000×1000となります。

 

一般的にGPUを使った画像処理ではメモリの転送時間がかかり処理時間トータルでは、あまり高速化されない

と言われる場合も多いので、ソースコードを見てみないと、いまいち結果をそのまま信用できない...

でも、とりあえずは、そこそこ速そうな結果でした

 

ソースコードは こちら

https://code.ros.org/trac/opencv/changeset/6950?utm_source=twitterfeed&utm_medium=twitter

 

OpenCVへ戻る

 

【展示会情報】画像センシング展/SSII2011

2011年6月8日(水)~10日(金)、パシフィコ横浜にて

 

画像センシング展2011

 

が開催されます。

 

国内外のカメラ、照明、画像入力ボード、画像処理ソフトメーカが一堂に会するので、画像処理を使った検査を検討している方は是非とも参加されてみては如何でしょうか?

 

また、同時開催で

 

SSII2011

 

という、非常に参考になるセミナー(有料)があるので、興味がある方参加すると良いと思います。

詳しいセミナーのプログラム内容はこちら

私も参加申し込みをしました。

 

また、ちょっと宣伝が入ると思いますが、展示会の出展社による無料の

 

イメージングセミナー

 

も開催されます。

 

内容は最新の物、かつ、実際に検査などで使われている技術ばかりなので、現実的で、こちらも役にたつと思います。

 

また、コンピュータビジョン最先端ガイドなど、ちょっとマニアックな本も会場で販売されていると思います。この本を手に取って見る事のできる数少ないチャンスなので、こちらも見てみて下さい。

 

フィルタ処理の高速化アルゴリズム(縦横に処理を分ける)

前回、フィルタ処理の高速化アルゴリズム(重複した計算を行わない)で紹介した方法ではカーネルの値が全て同じでないと使えないので、今回はフィルタ処理を縦方向と横方向に分けて行う事でフィルタ処理の高速化を行う方法をガウシアンフィルタを例にとって紹介します。

 

ガウシアンフィルタのカーネルには、

 

が良く用いられますが、この処理を注目画素の周辺の輝度値をI0~I8とした場合、
ガウシアンフィルタの処理を行列で

 

と、表すこともでき、この事は縦方向に3×1のガウシアンフィルタ処理をおこなってから、
横方向に1×3のガウシアンフィルタ処理を行うことを意味しています。
(横方向に処理をしてから縦方向に処理をしても同じです。)

 

このように処理を縦と横に分けることで、カーネルのサイズm×nの場合、通常の処理では
m×n回の掛け算を行うところ、m+n回の掛け算で済む事になります。
(ただし、縦横に処理を分ける事で全画素を2回参照することになるので、カーネルのサイズが
小さいと効果はあまりありません。)

他にも、移動平均フィルタの場合

ソーベルフィルタの場合

 

となります。
ソーベルフィルタの行列を見ると、縦方向にガウシアンフィルタ処理をしてから、横方向に微分処理している事が分かりやすくなっているかと思います。

 

また、比較的処理の重いメディアンフィルタにおいても、処理を縦と横に分けることによって、
ほぼ、同様な効果を得ることができます。
厳密には同じ結果にはならないのですが、スパイクノイズを除去するという意味では
十分な結果を得る事が出来ます。

試しに何回か、メディアンフィルタ処理を縦方向に1列分の処理を行ってから、横方向に1列分の
処理を行ってみましたが、ほぼ、良好な結果を得る事ができていると思います。
(画像にするともう少し分かりやすいかと思いますが、プログラムが無いもので...)

 

 

画像処理アルゴリズムへ戻る

 

フィルタ処理の高速化アルゴリズム(重複した計算を行わない)

画像フィルタ処理の高速化のテクニックを移動平均フィルタを例にとって紹介したいと思います。

 

カーネルのサイズが5×5の移動平均フィルタの場合、注目画素の周辺の5×5の輝度値を合計し、
その輝度値の合計を画素数(5×5=25)で割る処理をラスタスキャンしながら、全画素に対して
処理を行います。

 

 

ここで、隣の画素へ処理が移った時に、輝度値の合計の計算処理は前に行った輝度値の合計の
処理とかなりかぶっている(下図の緑色の部分)事に気が付きます。

 

 

そこで、最初の輝度値の平均値を計算をした時の輝度値の合計値を保持しておき、最初の輝度値の
合計値から、最初のカーネルの左端の1列分の輝度値(上図の赤色の部分)を引き、
次のカーネルの右端の1列分の輝度値(上図の青色の部分)を足すと、次のカーネル内の
輝度値の合計値を求める事が出来ます。

 

そうすると、カーネル内の輝度値の合計の計算に25回の足し算をしていたところ、5回の引き算と
5回の足し算の計10回の計算で済ませることが分かります。
この効果はカーネルサイズが大きくなればなる程、大きくなります。

 

さらに画像の1行分の合計値を確保するメモリを確保しておくと、縦方向に関しても同様の処理が
できるので、高速化が期待できます。

 

と、今回は画像のフィルタ処理を例にとって紹介していますが、この考え方は他にもいろいろと
応用が効くので、輝度値の合計の計算に留まらず、毎回同じような処理をしているな~と思ったら、
前回行った処理の使いまわしができないか?検討してみると良いでしょう。

 

画像処理アルゴリズムへ戻る

 

【展示会情報】VISION Japan 2011

2011年4月20日(水)~22日(金)に神奈川県横浜市のパシフィコ横浜にて、カメラや光学機器メーカーが集う、

VISON Japan 2011

が開催されます。

 

今回は日本インダストリアルイメージング協会(JIIA)主催の無料のセミナーもあるので、ご興味のある方は参加されてみては如何でしょうか?

http://www.optronics.co.jp/optworld/spring/event_jiia.php

 

 

 

標準画像データベースSIDBA(Standard Image Data-BAse)

画像処理をしていると、一度は見た事があるであろう、マンドリルの画像↓

ですが、これは画像処理用の  標準画像データベースSIDBA(Standard Image Data-BAse)から用いられています。このSIDBAの画像データ入手先ですが、SIDBAで検索すると、あちこちで画像が公開されているので、どこが本家なのか?良く分かりませんが、比較的入手しやすいホームページは以下のページでしょうか...

 

※女性の画像(レナ)も有名なのですが、本人の希望により、使って欲しくないとのことなので、使用は避けましょう。
(参考)https://www.zaikei.co.jp/article/20200717/576594.html

神奈川工科大学

http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html

南カリフォルニア大学

http://sipi.usc.edu/database/

 

画像処理アルゴリズムへ戻る