二分法の処理アルゴリズムと応用例

簡単には解けない複雑な計算式の解を求める手法の一つに二分法という手法があります。

これは、2点間の間に必ず解がある場合、2点間の半分の位置の値を求め、解が小さい方にあるのか?大きい方にあるのか?を確認し、解がある方のさらに半分の位置の値を求め...と解のある位置を追い込む手法です。

 

説明が難しいので具体例で示します。

下図は y = x2 – 10 のグラフですが、y=0 となるxの値を求める場合の例です。

この例では、普通に解を求める事ができますが、あえて二分法で解きます。

 

y=0 となるxの値は、-4 ~ -2 と 2 ~ 4 の間にありそうな事はわかります。

今回は 2 ~ 4 の間にある解を求めます。

 

2と4の中間の3の時のyの値を計算すると、yの値は負になるので、y=0となるxの値は
3 ~ 4 の間にあることがわかります。

 

さらに、3と4の中間の3.5の時のyの値を計算すると、yの値は正になるので、
y=0となるxの値は3 ~ 3.5 の間にあることがわかります。

 

さらに、3と3.5の中間の3.25の時のyの値を計算すると、yの値は正になるので、
y=0となるxの値は3 ~ 3.25 の間にあることがわかります。

 

・・・と、この処理を2点間の範囲が十分小さくなるまで繰り返すことで、y=0 となる値を求める手法が二分法となります。

 

二分法の考え方は非常に簡単ですが、個人的には数式の解を求めるために使うことは、ほとんど無く、二分法の考え方を応用することはよくあります。

 

その一例を示したいと思います。

 

プログラムのエラーの場所を特定する場合

プログラム中のどこかでエラーが発生しているときに、どこで発生しているのか?さっぱり見当が付かない場合に二分法の考え方を応用します。

 

まず、エラーが発生しているプログラムにおいて、プログラムシーケンスのだいたい真ん中あたりとなる位置にブレークポイントを置いてデバック実行を行います。

もし、ブレークポイントまでエラーが無ければ、ブレークポイントの後半の処理でエラーが発生している事になります。

 

次に後半の処理部分のさらに真ん中あたりにブレークポイントを置き、デバッグ実行をしたときに、後半の中の前半の部分でエラーが発生していれば、エラーは全体の後半の中の前半部分で発生することがわかります。

 

これを繰り返すと、エラーが発生している位置は、全体の1/2⇒1/4⇒1/8・・・と追い込む事ができるので、やみくもにエラーの場所を探そうとするよりは、バグの位置を特定しやすくなるかと思います。

 

 

ラインセンサカメラで撮影した画像の縦横比を合わせる場合

ラインセンサカメラ(スキャナのような物)で撮影した画像は、何も考えずに撮影すると、縦横比が合わなく、丸い物を撮影しても楕円状になってしまいます。

これをまじめに縦横比を合わせるには、画像の横方向の撮影分解能(mm/画素)を画像から算出し、カメラのスキャンレート、エンコーダの撮影分解能、被写体の移動速度などから縦横比の合う値を算出します。

 

しかし、計算するのも少し面倒なので、ただ、撮影するだけの場合、私は画像を見ながら被写体の搬送速度を調整して縦横比を調整しています。

(実際の装置の場合、搬送速度は装置の生産能力に係わる部分なので、調整することは仕様的に難しい場合が多く、カメラのスキャンレートやエンコーダの撮影分解能などを調整して縦横比を合わせます。)

 

以下、具体例です。

送り方向に関しては、パルスモータで搬送しているとして、画像が縦長、横長になるパルス速度を見つけます。

(画像が縦長になるのは搬送速度が遅く、画像が横長になるのは搬送速度が速い事を意味します。)

 

ここでは、パルス速度1000Hzで搬送した時に縦長、2000Hzで撮影した時に横長になったとすると、縦横比が合う搬送速度は1000~2000Hzの間にある事がわかります。

ここで、二分法の考え方を応用して、1500Hzで撮影した時に画像が横長になったとすると、縦横比が合う搬送速度は1000~1500Hzの間となります。

 

1250Hzで撮影した時に画像が横長になったとき、1000~1250Hzの間、

1125Hzで撮影した時に画像が縦長になったとき、1125~1250Hzの間、

1187.5Hzで撮影した時に・・・と繰り返していくと、縦横比の合う搬送速度を見つけ出す事ができます。

 

 

 

このように、二分法を知っていると、数式を解くだけでなく、効率的に目的の場所や値を求めることが出来るようになります。

 

使える数学へ戻る

C#から使える画像処理ライブラリ

画像処理のプログラムでは、当然ながら画像の表示や、操作するボタンなどが欲しくなるので、GUIのプログラム作成が簡単なC#が割とよく用いられています。

しかし、画像処理そのものをC#でやるには処理速度に不満もあるので、GUIはC#、画像処理部はC言語で処理が書かれた画像処理ライブラリを用いる事となります。

 

そこで、C#から使える画像処理ライブラリにはどんなものがあるのか?紹介したいと思います。

 

個人で使うなら

ほぼ、OpenCvSharpで決まりだと思います。

USBカメラも使えるし、バージョンアップもされているし、作者は日本人なので。。

OpenCVをラップしているので、画像処理時間も普通にプログラムを組むよりは圧倒的に速いかと思います。

 

インストール方法については、GitHubに少し書かれていますが、NuGetからインストールするのが基本のよう。

詳細は「OpenCvSharp インストール」で検索してみて、新しめの記事を参照して頂ければわかると思います。

 

マニュアル

https://shimat.github.io/opencvsharp_docs/index.html

GitHub

https://github.com/shimat/opencvsharp

 

会社で使うなら

「会社で使う」というか、使用するカメラが工業用のカメラを使うなら、以下の2つの画像処理ライブラリが、よく使われているように感じます。

 

●Cognex Vision Pro

●MVTec HALCON

 

両方とも数十万円するので、個人で買えるようなレベルではありませんが、OpenCVと何か違うか?というと、私が思うにはテンプレートマッチングの性能と工業用のカメラに対応している部分だと思います。

 

テンプレートマッチングについては、OpenCVのテンプレートマッチングでは回転やスケール変動には対応していませんが、これらのライブラリは対応しています。

さらには、袋の表面に印刷された画像のように、画像が歪んだ状態でもマッチングしてくれる機能もあります。OpenCVにも特徴点ベースのマッチングはありますが、やっぱりこっちの方が良いかと思います。

 

工業用のカメラを使うと何がいいか?という部分についてですが、カメラの画素数やフレームレートなどもそうですが、一番大きいのは同期撮影が出来る点だと思います。

例えば、ベルトコンベア上を流れる製品を撮影するような場面では、被写体は毎回画像の中央で撮影したくなりますが、カメラのフレーム単位で撮影タイミングを制御できないと、毎回撮影位置がばらついてしまいます。

 

OpenCVからは工業用のカメラを制御することは、ほとんどの場合、出来ないのですが、カメラ専用のSDK(もしくは画像入力ボードのSDK)を用いて画像を撮影し、撮影したデータをOpenCVに渡して画像処理を行う事も可能なので、やっぱり、一番差が大きいのはテンプレートマッチングの性能でしょうかね。

 

ちなみに、マシンビジョン業界でPythonが使われる事はほとんどありません。(聞いた事がありません。)

Pythonが出来ない私。。

 

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

Osmo Mobile 3 のレビュー

私自身、初のジンバル(Osmo Mobile 3)を購入しました。

 

Osmo Mobile 3には、通常のOsmo Mobile 3

 

さらに、三脚とハードケースの付いた Osmo Mobile 3 コンボ

 

というのがあるのですが、すぐにでも欲しかったので、在庫のあった安い、素の Osomo Moble 3 を購入しました。

 

内容物は以下の通りです。

本体、ソフトケース、ストラップ、取説、USBケーブル(充電用)、干渉用?シール(使い方分からず。。) が入っています。

 

しばらく使ってみましたが、結局、手持ちで撮影するので、三脚は無くても大丈夫かな?という感じです。

 

今回、初ジンバルという事で、最初は振り子的に重りでバランスをとるだけかな?と思っていたのですが、スマホとBluetoothで接続され、ジャイロセンサを使ってるんだか?画像処理的に振動を抑えているんだか?で、自分の想像以上に画像が安定してくれます。

 

もともと、花火大会に合わせて、すぐにでもこのOsmo Mobile 3 が欲しかったのですが、その花火大会をiPhoneX + Osmo Mobile 3 の手持ちで撮影した動画(音声あり)がこちら↓です。

 

もう、ほとんどビタ止まり状態で、手振れな感じは、まるでありません。

 

さらに、撮影する被写体を選択すると、その被写体をトラッキングしてくれる機能があるのですが、その撮影結果がこちら↓

ちょっと、寄りすぎたせいか?まだ自分の使い方が甘いのか?手振れが目立ってます。。

 

大きさも手のひらサイズだし軽い(405g)ので、動画を撮影する時の必需品になってくれそうです。

 

¥13,500しましたが、満足度の高い買い物でした!!

【C#】領域(Rectangle)全体を大きくする、小さくする

Rectangle構造体であらわされた領域全体を左右方向、上下方向に大きく/小さくするには

Inflateメソッドを用います。

 

コード例

private void Form1_Paint(object sender, PaintEventArgs e)
{
    // 元の領域1
    var rectSrc1 = new Rectangle(20, 20, 50, 30);
    e.Graphics.DrawRectangle(Pens.Black, rectSrc1);

    // 領域を大きくする
    // Rectangleを左右それぞれ3画素、上下それぞれ6画素大きくする
    rectSrc1.Inflate(3, 6);
    e.Graphics.DrawRectangle(Pens.Red, rectSrc1);

    // 元の領域2
    var rectSrc2 = new Rectangle(120, 20, 50, 30);
    e.Graphics.DrawRectangle(Pens.Black, rectSrc2);

    // 領域を小さくする
    // Rectangleを左右それぞれ6画素、上下それぞれ3画素小さくする
    rectSrc2.Inflate(-6, -3);
    e.Graphics.DrawRectangle(Pens.Red, rectSrc2);
}

 

実行結果

 

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

【C#】RectangleRectangleFの相互変換

あまりやる事は無いのですが、Rectangle(名前空間:System.Drawing)とRectangleFの相互変換について調べてみました。

RectangleからRectangleFへ変換

これに関しては、型は変わるものの、値そのものは変わらないので、ほぼ、代入するようなノリで以下のようにすると変換ができます。

var rect = new Rectangle(20, 10, 60, 40);
RectangleF rectF = rect;

RectangleFからRectangleへ変換

こちらは値を小数から整数へ切り詰める必要があるので、値を切り捨てや四捨五入する必要が出てきますがRectangle構造体には値を切り捨てる(intでキャストする)Truncateメソッド、切り上げを行うCeilingメソッド、四捨五入を行うRoundメソッドが用意されています。

ここでいうですが、RectangleF構造体のX, Y, Width, Heightプロパティに関して切り捨て、切り上げ、四捨五入が行われます。

 

プログラム例

var rectF = new RectangleF(0.5f, -1.5f, 10.5f, 5.5f);

Rectangle rect;
rect = Rectangle.Round(rectF);      // 四捨五入
rect = Rectangle.Truncate(rectF);   // 切り捨て(intでキャスト)
rect = Rectangle.Ceiling(rectF);    // 切り上げ

結果は

X Y Width Height
オリジナル 0.5 -1.5 10.5 5.2
Round 0 -2 10 5
Truncate 0 -1 10 5
Ceiling 1 -1 11 6

となります。

ここで気になるポイントとしては、Round(四捨五入)でX座標の0.5が0になってしまっています。(期待しているのは1)

 

この四捨五入に関しては

【C#】四捨五入

のページでも書いていますが、座標に関する値の四捨五入は、私は

int x, y;
y = (int)Math.Floor(x + 0.5);

のように書くようにしています。

 

以上のことから、RectangleFからRectangleへ変換するには

var rectF = new RectangleF(0.5f, -1.5f, 10.5f, 5.5f);
var rect = new Rectangle(
        (int)Math.Floor(rectF.X + 0.5),
        (int)Math.Floor(rectF.Y + 0.5),
        (int)Math.Floor(rectF.Width + 0.5),
        (int)Math.Floor(rectF.Height + 0.5)
    );

のように書くようにしています。

このときの結果は

X Y Width Height
オリジナル 0.5 -1.5 10.5 5.2
変換後 1 -1 11 5

となります。

 

せっかくRectangle構造体にRoundメソッドが用意されているのに、いまいち使えず、ベタに書いた方が良いようです。。

 

【参考ページ】

●.NET Frameworkのソースコード(Rectangle構造体)

https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Rectangle.cs

●Microsoft Docs Rectangle構造体

https://docs.microsoft.com/ja-jp/dotnet/api/system.drawing.rectangle

 

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

【C#】座標が領域内にあるか?調べる方法

マウスをクリックした時など、任意の座標がある領域の範囲内にあるか?どうか?調べたい場合があります。

 

これを調べるには、四角形の領域の場合、Rectangleクラス(名前空間:System.Drawing)のContainsメソッドを用います。

さらに複雑な形状の領域の場合、GraphicsPath(名前空間:System.Drawing.Drawing2D)のIsVisibleメソッドを用います。

 

四角形領域の場合のコード例

bool insideFlg;

var rect = new Rectangle(2, 2, 8, 8);
for (int i = 0; i < 12; i++)
{
    insideFlg = rect.Contains(i, i);
    System.Diagnostics.Debug.WriteLine($"四角の場合:{i}, {insideFlg}");
}

実行結果

四角の場合:0, False
四角の場合:1, False
四角の場合:2, True
四角の場合:3, True
四角の場合:4, True
四角の場合:5, True
四角の場合:6, True
四角の場合:7, True
四角の場合:8, True
四角の場合:9, True
四角の場合:10, False
四角の場合:11, False

丸領域の場合のコード例

bool insideFlg;

var path = new System.Drawing.Drawing2D.GraphicsPath();
path.AddEllipse(2, 2, 8, 8);
for (int i = 0; i < 12; i++)
{
    insideFlg = path.IsVisible(i, i);
    System.Diagnostics.Debug.WriteLine($"丸の場合:{i}, {insideFlg}");
}

実行結果

丸の場合:0, False
丸の場合:1, False
丸の場合:2, False
丸の場合:3, False
丸の場合:4, True
丸の場合:5, True
丸の場合:6, True
丸の場合:7, True
丸の場合:8, True
丸の場合:9, False
丸の場合:10, False
丸の場合:11, False

 

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

画像の拡大

例えば、下図のように2x2画素の画像を4x4の画像に拡大する場合、アフィン変換を使えばいいんでしょ!と、安易に考えていると、思わぬ落とし穴があったりもします。

大事なポイントとして、

●画像の座標の原点は左上の画素の中心が原点(0.0、0.0)となる。(例外もあります)

●アフィン変換の拡大縮小は原点を基準として拡大縮小される。

 

となります。

これを気にせず、ただ、アフィン変換で画像を2倍に拡大すると、左上の画素の中心を基点に画像が拡大されます。

これで、一見良さそうにも感じるのですが、拡大後の画像において、画素の中心が原点であることから、4x4画素の領域は下図の四角で示した領域であり、画像全体が左上に0.5画素ズレた状態になっていまいます。

 

 

アフィン変換で画像を拡大する時の変換前と変換後の状態は、以下のようになるのが正解です。

 

この変換をアフィン変換で実現するには以下のように行います。

 

変換前の状態

 

①画像全体を右下に(+0.5,+0.5)画素移動
$$\begin{pmatrix} 1 & 0 & 0.5 \\ 0 & 1 & 0.5 \\ 0 & 0 & 1 \end{pmatrix}$$
 

②画像を2倍に拡大

$$\begin{pmatrix} 2 & 0 & 0 \\ 0 & 2 & 0 \\ 0 & 0 & 1 \end{pmatrix}$$
 

③画像全体を左上に(-0.5,-0.5)画素移動

$$\begin{pmatrix} 1 & 0 & -0.5 \\ 0 & 1 & -0.5 \\ 0 & 0 & 1 \end{pmatrix}$$

 

となります。

この一連の変換をアフィン変換行列であらわすと

$$\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}=\begin{pmatrix} 1 & 0 & -0.5 \\ 0 & 1 & -0.5 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} 2 & 0 & 0 \\ 0 & 2 & 0 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} 1 & 0 & 0.5 \\ 0 & 1 & 0.5 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}\\ \\ $$

$$\begin{pmatrix} { x }^{ ‘ } \\ { y }^{ ‘ } \\ 1 \end{pmatrix}=\begin{pmatrix} 2 & 0 & 0.5 \\ 0 & 2 & 0.5 \\ 0 & 0 & 1 \end{pmatrix}\begin{pmatrix} x \\ y \\ 1 \end{pmatrix}\\ \\ $$

 

となり、単に拡大のアフィン変換行列だけを掛ければOKでは無いことが分かります。

 

ちなみに、C#で2x2画素の画像をPictureBoxのSizeModeプロパティをZoomにして、ImageプロパティにBitmapを設定すると、このようになります。

 

なんとなく、画像が左上にズレているようで、なんか怪しい!!

 

【関連記事】

【C#】画像の座標系

アフィン変換(平行移動、拡大縮小、回転、スキュー行列)

画素の補間(Nearest neighbor,Bilinear,Bicubic)の計算方法

画像の回転

 

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