UVWステージのしくみ(位置決め用高精度XYθステージ)

工業用の画像処理の分野では画像を撮影して検査をするばかりでなく、半導体やFPDの業界では位置決め(アライメント)用途にも応用されています。今回は位置決め用途で用いられているUVWステージという、高精度のステージを紹介したいと思います。

 

通常のXYθステージUVWステージの基本構造は以下のようになっています。(若干異なる場合もあります。)

【通常のXYθステージ】

 

【UVWステージ】

 

UVWの各軸は、下図のように可動します。

 

UVWステージでXYθの位置決めが出来る原理は、

 

上図のようにU軸を動かすとステージはX方向へ移動し、V軸とW軸を同時に動かすとY方向へ移動します。
さらにU軸、V軸、W軸を同時に動かすとステージは回転します。

 

UVWステージの特徴は

  • ステージの高さを低く抑えられる
  • 二層構造なので、ベース面とステージ面との平行度を出しやすい
  • モーターが移動しないので、配線が用意(断線する可能性が低い)
  • 高精度

など。
なぜ?UVWステージが高精度の位置決めが可能になるかというと、特に回転方向の位置決めにおいて、同じ移動分解能のモータを使う場合、てこの原理のように、回転中心から離れた位置でステージを動かした方が回転の分解能は向上します。

 

FPDなどの位置決めにおいては、ステージのサイズも大きくなり易いので、ステージの両端で回転方向を制御するUVWステージは有効的に高精度な位置決めを可能としてくれます。

 

そんな良い事だらけのUVWステージですが、欠点もあります。

  • 移動可能範囲が狭い
  • ステージ制御が難しい

など。
移動可能範囲が狭いのは、もともと位置決め用途で使われる場合の多いUVWステージなので、最初にメカ的に粗調整しておき、UVWステージで精調整する場合が多いので、あまり問題にはならないと思います。

 

ステージ制御においては、位置決めマークの位置を画像処理で抽出し、ズレ量を算出し、目的の位置へと合わせ込みますが、この時のずれ量は通常、X、Y、θのずれ量で算出されます。
しかし、XYθ軸の移動量からUVW軸の移動量へと変換するには、ちょっと面倒なのでUVWステージの制御や位置決めの処理をライブラリ化している物もあるので、使用を検討してみては如何でしょうか?

 

【主なUVWステージメーカ】

ヒーハイスト精工株式会社

【Excel】任意関数のグラフの描画

これまで紹介してきたテクニックを使って、正規分布の確率密度関数を例にとって、任意関数のグラフの描画方法を紹介します。

 

確率密度関数は以下の式です。

 

この式のグラフを書くには、まず、Xの値を記入します。
この方法は連番の入力でも紹介しましたが、A7のセルに初期値の-6A8のセルに次の値の-5.5を入力し、マウス操作によりA列のセルに-6~6までの連番を記入しています。

次にf(x)の計算式にはB7のセルに

=1/(SQRT(2*PI())*B$5)*EXP(-($A7^2/2/B$5^2))

と入力します。
このB7のセルをコピーして他のf(x)に相当する部分に貼り付けても、X座標およびσの値の参照先が移動しないようにアルファベット部分および数字部分の頭にを付けてから、コピーおよび貼り付けをします。
なぜを付けるのか?については固定セルの参照(絶対参照)のページを参考にして下さい。

 

これで、f(x)の値は全て計算できるので、あとはキー操作を駆使してグラフの描画範囲を選択しグラフの散布図を用いてグラフ表示します。

 

ファイルの実体はこちら  gauss.xls

【Excel】固定セルの参照(絶対参照)

エクセルのセルに、他のセルを参照した式を他のセルにコピーすると、数式は行方向、列方向の位置関係を保ったまま、数式がコピーされます。この事は相対参照というのですが、エクセルの初期状態ではこの相対参照となっています。

 

しかし、何かしらのレートなどを参照したい場合は、セルの式をコピーしても常に同じセルを参照したい場合があります。

 

この常に同じセルを参照する方法を絶対参照といい、この絶対参照の方法を工数の見積表の作成を例にとって紹介します。

 

結論からすると、固定したいセルの名前の頭に

$A$1、$A1、A$1

などと$を付ければいいのですが、

列方向を固定したい場合はアルファベットの頭にを、
行方向を固定したい場合は数字の頭に

を付けます。

 

以下、具体例です。

 

上図のように工数の見積表を作成する場合は、小計のセルには全て工数×工数単価の値を
入力しますが、最初にC4のセルに工数×工数単価の計算式「=B4*C1」を入力しC4のセルを
コピーして下の小計欄に全て貼り付けると下図のようになってしまいます。

 

 

これはエクセルではデフォルトで相対的な位置のセルを参照してしまう(今回の例では常に3つ上のセルを参照)ためで、この参照方法を相対参照といいます。
実際の計算式は下図のようになっています。

 

 

しかし、実際にはC1~C8のセルは全てC1をしたいので、C4のセルをコピーして他の小計のセルに貼り付けて、常にC1のセルを参照するようにセルの名前(B4C1など)のアルファベット部分か
数字部分の変えたくない方の頭に$をつけておきます。
今回の例では数字部分を変えたくないので、C$1とします。
ちなみに、アルファベット部分、数字部分両方を固定するのが絶対参照、どちらか一方だけを
固定するのが、複合参照というそうです。

 

 

この状態で、C4のセルをコピーして、他の小計のセルに貼り付けると、目的どおりに工数単価は
固定セル(C1)を参照することが出来ます。

 

 

実際の計算式は下図のようになってくれています。

 

 

この方法を知る前は、すべてのセルに手入力で計算式を入れてました...

 

Excelへ戻る

Windows7がシャットダウンしない場合

最近、私のWindows7マシンをシャットダウンする時に、シャットダウンしていますと表示されたまま、シャットダウンしない現象が多発し、しょうがないので、電源ボタンの長押しをして電源を落としていました。

 

シャットダウンしない原因を探してみると以下のような原因が考えられるとのこと。

 

①ビデオカードなどのドライバの不具合
②常駐ソフトによる問題(私の場合はこれが原因でした)
③ハードディスクの電源がOFFになっているため

 

など。
①に関しては、Windows Updateやドライバの更新などもやってみましたが、現象は変わらず

 

②は手間ですが、怪しい常駐ソフトを終了してみてシャットダウンして、シャットダウンすれば、そのアプリがかなり怪しいと思われます。

 

私の場合は、iPhoneを無線LANにつなぐために使っていたGW-USNano2-GというUSBタイプの無線LANの常駐ソフト(クライアント・マネージャ)をアンインストールしたところ、今のところシャットダウンしてくれるようになりました。

(せっかく買ったのに、使い物にならなくなってしまいました...)

 

③はハードディスクがシャットダウン時に電源がOFFになっているとシャットダウンできない場合があるとのこと。
試しにシャットダウンの前にエクスプローラで各ドライブをクリックしてドライブの内容を表示させ、ドライブをONにしてからシャットダウンを行った。これでシャットダウンしれくれる場合もありました。

 

ということで、私の場合は増設したハードディスクの電源がOFFになっていたため、Windows7がシャットダウンできなかったみたい。
そのため、電源管理の設定をいろいろ見てみたが、非アクティブなハードディスクの電源を切る時間設定はあるものの、常にオンのような設定が無い。

 

そこで、③の操作をC#でプログラム的に行い、最後にshutdown.exeを実行させるだけのプログラムを作ってみました。

 

シャットダウンしてくれない人は試してみては?
これ↓

 

ShutdownEx.zip

 

使い方は上記のファイルをダウンロード、解凍し、ファイル(ShutdownEx.exe)をデスクトップなど、どこでもいいのですが、ファイルを置いておき、シャットダウンしたい時に、このファイルをダブルクリックして実行します。

 

ただし、プログラム実行後、すぐにシャットダウン動作に入るので、実行する前に編集中のファイルを保存するなどして、ご注意下さい

 

プログラミングへ戻る

【Excel】連番の入力

エクセルで1、2、3・・・と連番を入力する場合に簡単に入力する方法を紹介します。

 

 

まず、始めに初期値次の値の順でセルに値を入力します。

 

次にこの2つのセルを選択し、右下に表示される黒四角(■)の部分にマウスを合わせます。
するとマウスのアイコンが十字 (+) に変わるので、この状態でマウスをクリックしたまま
下の方にマウスを移動します。

 

 

すると下図のように連番を入力することが出来ます。

 

 

他にも下図のような初期値次の値を入力すると

 

 

このように↓なります。

 

 

この規則性はエクセルのツール⇒オプションユーザー設定リストのタブで指定できます。

 

 

 

Windowsドライバの削除方法

Kinectなどで遊んでいると、OpenNIなど、いくつかのドライバ・ライブラリが出回っているため、使うソフトによって、ドライバを削除・インストールを行い切り替える必要があるのですが、ちょっとはまりやすいので、ドライバの削除方法について、まとめておきます。

 

注)ドライバの削除を誤ると、他のデバイスが動作しなくなる場合がありますので、ドライバの削除は十分注意のうえ、自己責任で行って下さい。

 

Windowsのドライバを削除する場合、スタートメニューよりコントロールパネル→デバイスマネージャーで削除するデバイスを右クリックして削除を行っても、デバイスマネージャーのメニューの操作→ハードウェア変更のスキャンをクリックすると削除したはずのドライバが元に戻ってしまいます。

 

 

そうならないようにするためにもドライバを完全に削除する方法を紹介します。

まず、削除するデバイスを選択し、右クリックしプロパティをクリックします。

 

 

次に表示されたウィンドウのドライバータブのドライバーの詳細をクリックします。

 

 

するとデバイスドライバが使用しているファイルが表示されているので、これらのファイルを削除します。

 

ちなみにKinect-v**-withsourceを使ってドライバをインストールした場合は以下の3つのファイルを削除します。
C:\Windows\System32\drivers\libusb0.sys
C:\Windows\System32\libusb0.dll
C:\Windows\SysWOW64\libusb0.dll
(ただし、削除したドライバが元に戻らなくするだけなら、これらのファイルを削除する必要はありません。
あくまで、完全にドライバを削除する場合)

 

次にC:\Windows\infのフォルダ内にある*.infファイルの中から、削除するデバイスが使用していると思われるファイルを見つけます。
infファイルをメモ帳などで開き、DeviceNameやSourceNameなどを参考にしながら対象のinfファイルを探し出します。

 

ちなみに、Kinect-v**-withsourceを使ってドライバをインストールした場合は、以下のように表示されている3つのファイルが相当します。

; Xbox_NUI_Motor.inf
; Copyright (c) 2010 libusb-win32 (GNU LGPL)
[Strings]
DeviceName = "Xbox NUI Motor"
VendorName = "Microsoft Corp."
SourceName = "Xbox NUI Motor Install Disk"
DeviceID   = "VID_045E&PID_02B0"
DeviceGUID = "{283E6E32-2356-4775-9D97-0B4EA8775D39}"
・・・

 

; Xbox_NUI_Camera.inf
; Copyright (c) 2010 libusb-win32 (GNU LGPL)
[Strings]
DeviceName = "Xbox NUI Camera"
VendorName = "Microsoft Corp."
SourceName = "Xbox NUI Camera Install Disk"
DeviceID   = "VID_045E&PID_02AE"
DeviceGUID = "{D3074D04-3130-4FF6-A71F-D810C5D59867}"
・・・

 

; Xbox_NUI_Audio.inf
; Copyright (c) 2010 libusb-win32 (GNU LGPL)
[Strings]
DeviceName = "Xbox NUI Audio"
VendorName = "Microsoft Corp."
SourceName = "Xbox NUI Audio Install Disk"
DeviceID   = "VID_045E&PID_02AD"
DeviceGUID = "{D4AF50D4-8548-420F-87E4-F3B9AE4F5918}"
・・・

 

*.infファイルはoem**.infというようなファイル名になっている場合が多いのですが、oem**.infファイル同じ名前のoem**.PNFファイルの2ファイルをセットで削除します。
Kinectの場合、Motor、Camera、Audio用のファイル3セット(*.infと*.PNF)、計6つのファイルを削除します。

そして最後に?(最後じゃなくてもいいかも?)、削除するデバイスを選択して右ボタンクリックで削除をクリックします。

これで、ハードウェア変更のスキャンを行っても、ドライバが元に戻る事は無くなるので、新たにドライバをインストールすることが可能となります。

 

 

Kinect参考書籍、KINECTセンサープログラミング

世界初と言われる、Kinectの参考書籍が発売されます。(5月24日発売予定)

 

 

この本の対象者はプログラム中級者としているようにKinectのプログラムは最低限のC言語、C++の知識が必要となります。

 

私も勢いでKinectを買ってみたものの、動作が拾えるまではいいのですが、その先、何をするか?が一番の問題。

 

目次

OpenNIおよびSensorモジュールのライセンス
OpenCVのライセンス
1. Kinect入門

1.1. KinectとOpenNI

1.1.1. Kinectの概要
1.1.2. OpenNIの概要
1.1.3. Kinectプログラミングにあたって

2. OpenNIプログラミング環境の作成

2.1. OpenNI開発環境の作成

2.1.1 本書の開発環境
2.1.2. Windows C++
2.1.3. Windows C#
2.1.4. Linux(Ubuntu)
2.1.5. Mac OS
2.1.6. 開発環境作成における注意点

3. OpenNI入門

3.1. OpenNIの構成

3.1.1. OpenNIライブラリのクラス概要
3.1.2. Generator
3.1.3. MetaData
3.1.4. Capability

3.2. OpenNIプログラミング

3.2.1. Kinectの初期化や情報取得をする
3.2.2. カメラ画像を表示する
3.2.3. 距離を測定する
3.2.4. デプス(深度)マップを作成する
3.2.5. ビューポイントの設定
3.2.6. ミラー処理
3.2.7. ユーザーの検出をする
3.2.8. ユーザーのスケルトンを作成する
3.2.9. ジェスチャーを検出する
3.2.10. 手の動きをトラッキングする
3.2.11. Kinectから取得したデータを記録する
3.2.12. 記録したデータを再生する

4. NITE入門

4.1. NITEの構成

4.1.1. NITEライブラリのクラス
4.1.2. Session
4.1.3. Detector

4.2. NITEプログラミング

4.2.1. セッションの開始と停止
4.2.2. 腕の前後動作(プッシュ)を検出する
4.2.3. 腕の左右動作(ウェーブ)を検出する
4.2.4. 腕の円運動を検出する
4.2.5. 腕の上下左右運動(スワイプ)を検出する
4.2.6. 動作の停止を検出する
4.2.7. マルチプロセスに対応する(サーバ)
4.2.8. マルチプロセスに対応する(クライアント)

5. 応用編

5.1. 光学迷彩
5.2. 背景のマスク
5.3. ポーズの検出
5.4. 物体検出
5.5. 複数のKinectを操作する
5.6. KinectでPowerPointの操作

6. 補遺

6.1. 開発環境の作成

6.1.1. Windows
6.1.2. Linux
6.1.3. Mac OS

 

ちなみに、KinectをPCに接続する場合は、Kinectセンサー単品で購入しないとUSBの接続ケーブルが付いてこないそうなので、ご注意下さい。

 

ガウスの消去法

ガウス(Gauss)の消去法は連立一次方程式を解くのに用いられます。

 

基本的な方針は、下記のような連立一次方程式

 

 

を行列であらわすと、

 

 

となりますが、対角成分を全て、左下の成分をになるように、行の入替えや足し算、引き算などを行い、下記の行列になるように調整します。

 


(?の部分は任意の値で可)

 

これまでの処理を前進消去といいます。
ここで、最後の行の部分(3の部分)に着目すると、答えが確定しています。
この値を使って最後から2行目の値を計算すると答えが算出できます。
このように最後の行から順々に計算すると答えが全て計算することができます。
この処理は後退代入といいます。
この前進消去と後退代入の処理を合わせてガウスの消去法といいます。

 

さらに、前進消去のときに対角成分を1にするときの割り算の計算のときにおいて、
ピボット選択を行ったGauss-Jordan法でも説明しましたが、

 

  • 0(ゼロ)で割ることはできない。
  • 値を絶対値の小さい値で割ると、値に誤差が含まれる場合、計算結果に大きく影響が出てしまう。

     

    という性質を考慮します。

     

    ということで、以下、具体的な計算例を示します。

     

    連立一次方程式


     

    のうち、まず最初に1の項のどれか1つを1にするのですが、割り算の計算で誤差が少なくなるように、1の係数が一番大きい2行目の式を一番上に持ってきます。

     

     

    次に1行目の式の1の係数がになるように1行目の式全体を1の係数()で割ります。

     

     

    次に1行目以下の式の1の項が消えるように、
    2行目-1行目×23行目-1行目を計算します。

     

     

    ここで、3行目の式の3の項(行列で表現すると対角成分)が消えてしまったので、
    このままだと対角成分をにできないので、2行目3行目を入れ替えます。

     

     

    次に2行目の式の2の係数をにするように、2行目の式全体に3/2を掛けます。

     

     

    次に2行目以下の2の項が消えるように3行目+2行目 × 2/3を計算します。

     

     

    3行目の式のの係数はすでになので、前進消去はこれで終了です。
    この3本の式を行列であらわすと

     

     

    というように、対角成分が全てがで左下の成分がになったことが分かります。

     

    次に後退代入です。

     

    一番下の行の式はすでに答えが確定(X3 = 3)しているのがわかります。
    一番下の答えを用いて、下から2行目の式の答えを計算、
    一番下、下から2行目の答えを用いて下から3行目の式の答えを計算していきます。
    (今回の例だけ特別に、下から2行目の式の答えが確定してしまっています。)

     

     

    こうして全ての答えが求まります。

     

     

    この一連の処理がガウスの消去法です。

     

    このガウスの消去法を用いると、連立方程式を求めたり、最小二乗法の未知数を
    求めることができます。

     

    使える数学へ戻る

     

    ピボット選択を行ったGauss-Jordan法

    前回紹介したGauss-Jordan法で解く逆行列の計算では例えば、

    のように対角要素に0(ゼロ)が来ると、0で割れないため、対角要素を1にする事ができません。

     

    そこで、対角要素に0(ゼロ)が来ないように行を入れ替えてGauss-Jordan法を行います。
    さらに、ある値を割る場合、分母の値は絶対値が大きい方が割り算の誤差が小さくなります。
    (この割る要素の事をピボット(pivot)と言います。)

     

    この事を考慮し、Gauss-Jordan法で逆行列を求める例を示します。

     

    行列の右側に単位行列を追加します。

    1行1列目の要素の絶対値が最大となるように1行目3行目を入れ替えます。

    1行1列目の要素が1となるように1行目4で割ります。

    1列目の要素が(1 0 0)となるように

    [2行目] = [2行目]ー[1行目]×3
    [3行目] = [3行目]ー[1行目]×2

    を計算します。

    ここで2行2列目の要素の絶対値はすでに最大なので、2行2列目の要素が1となるように
    2行目-9/4で割ります。

    2列目の要素が(0 1 0)となるように

    [1行目] = [1行目]-[2行目]×3/4
    [3行目] = [3行目][2行目]×1/2

    を計算します。

    ここで、3行3列目の要素はすでに1なので、3列目の要素が(0 0 1)となるように

    [1行目] = [1行目]-[3行目]×2/3
    [2行目] = [3行目]+[3行目]×2/9

    を計算します。

    これで、左側が単位行列となり、右側に出来た行列が求める逆行列となります。

     

    このようにピボット選択を考慮する事で、対角要素が0であっても逆行列を解くことが可能となる場合があります。

     

    使える数学へ戻る

     

    逆行列(Gauss-Jordan法)

    2×2行列の逆行列

    行列

    の逆行列は

    となります。
    ただし、ad-bc = 0 のとき、逆行列は存在しません。

     

    3×3以上の行列の逆行列

    逆行列を解く手法はいくつかありますが、ここでは比較的分かり易いGauss-Jordan法を紹介します。

     

    Gauss-Jordan法では行列の右側に単位行列を付けたして、行ごとに掛け算、足し算、引き算を行い、行列の左側が単位行列になるように計算を繰り返し、最後に右側に残った行列が逆行列となります。

     

    といっても分かりづらいと思うので、具体的な計算例は以下の通りです。

     

    行列

    の右側に単位行列を追加します。

    1行1列目の要素が1となるように1行目2で割ります

    1列目の要素が(1 0 0)となるように

    [2行目] = [2行目]ー[1行目]
    [3行目] = [3行目]ー[1行目]×4

    を計算します。

    2行2列目の要素が1となるように2行目2倍します。

    2列目の要素が(0 1 0)となるように

    [1行目] = [1行目]ー[2行目]×3/2
    [3行目] =
    [3行目]+[2行目]

    を計算します。

    ここで、3行3列目の要素はすでに1なので、3列目の要素が(0 0 1)となるように

    [1行目] = [1行目]+[3行目]×2
    [2行目] = [2行目]ー[3行目]×2

    を計算します。

    これで、左側が単位行列となり、右側に出来た行列が求める逆行列となります。

     

    ただ、このままの方法では、求める行列の対角要素(行数と列数の同じ場所)に0(ゼロ)がある場合は対角要素を1に出来ない(0で割れない)ので、ここにピボット選択という手法を導入します。
    このピボット選択についてはピボット選択を行ったGauss-Jordan法にて紹介しています。

     

    使える数学へ戻る

     

    【Excel】キー操作

    セル内改行

     

     

    矢印方向のセル追加選択

    or or or

     

     

    を押すと↓

     

     

    矢印方法の端のセル選択

    or or or

     

     

    + を押すと↓

     

     

    Shiftキー+Ctrlキーの合わせ技

    ShiftキーとCtrlキーを合わせて使うことで、グラフ表示領域の選択などが簡単に出来ます。

     

     

    を押すと↓

     

     

    次に

    を押すと↓

     

     

    となります。

     

    アクティブセルを編集状態にする

     

    再計算

    RAND()関数などの再計算を行います。

     

    再実行

    最後に行った処理を再実行します。
    セルの色の変更、フォントの変更などの処理を繰り返すのに便利

     

    【OpenCV2対応参考書籍】OpenCV 2 Computer Vision Application Programming Cookbook

    2011.5月現在、OpenCV2以降に対応した参考書籍としては、まだ、発売されていませんが、これのみ?しかも英語

     

     

    OpenCV Ver2.0からはC++インターフェースが追加され、大きく変わったのですが、このC++に対応していると思われる?この書籍の概要は以下の通りです。

     

    • Teaches you how to program computer vision applications in C++ using the different features of the OpenCV library
    • Demonstrates the important structures and functions of OpenCV in detail with complete working examples
    • Describes fundamental concepts in computer vision and image processing
    • Gives you advice and tips to create more effective object-oriented computer vision programs
    • Contains examples with source code and shows results obtained on real images with detailed explanations and the required screenshots

     

    なにせ、発売前なので、詳細は不明ですが、気になるところです。

     

    OpenCVへ戻る

     

    【OpenCV】輪郭処理(cvFindContours)を使ったラベリング処理

    OpenCVには標準的にはcvLabelingのようなラベリングの関数は無いので、

     

     

    を使いましょう!というのが一般的になってきているように思いますが、最初のラベリングクラスでは、画像の幅の画素数が4の倍数で無い場合、うまく動作してくれなかった気がするし、Blob extraction library は英語なので良く分からないし・・・
    ※追記)現在、はconnectedComponentsが使えます。

    ということで、OpenCVに標準的にある輪郭処理の関数【cvFindContours】を使ってラベリングの処理ができないか?調べてみました。

     

    結果、OpenCVの関数だけで、こんな感じ↓まで出来ました。

     

    何はともあれ、まずは分かりづらいcvFindContoursの関数です。

     

    関数の定義は

    int cvFindContours(
              CvArr* image,			                    // 入力画像(8Bitモノクロ)
              CvMemStorage* storage,                // 抽出された輪郭を保存する領域
              CvSeq** first_contour,	              // 一番最初の輪郭(ツリー構造を持つ)へのポインタ
              int header_size = sizeog(CvContour),  // シーケンスのヘッダサイズ
              int mode = CV_RETR_LIST,	            // 抽出モード
              int method = CV_CHAIN_APPROX_SIMPLE,  // 近似手法
              CvPoint offset = cvPoint(0, 0)        // オフセット
              );

    となっているのですが、とにかく分かりづらい抽出モード( mode )の理解から。

     

    まずは mode = CV_RETR_TREE を例に取って説明したいと思います。

    処理前の画像↓

     

    この画像の輪郭を外側から順に追いかけると、

     

    の内側に 、  の内側に  と  ・・・ というような構造になっています。

     

     

    この構造をツリーのように階層構造で表すと、

     

    階層
    (Level)
    輪郭構造

    のように、一番外側に白の輪郭(Level = 1)があり、その内側に黒の輪郭(Level = 2)、さらにその内側に白の輪郭(Level = 3)・・・と、一番外側の白の輪郭から始まり、その輪郭の内側に黒の輪郭→白の輪郭→黒の輪郭→白の輪郭・・・とレベルが大きくなるにつれ、さらに内側に輪郭が存在しています。
    この構造を保持しているのが CvSeq** first_contour となります。
    このfirst_contourには一番最初の輪郭を示すポインタが格納されています。

     

    同じ階層(Level)にある別の輪郭を参照したい場合は

    CvSeq* contour = first_contour->h_next;

    とすれば、同じ階層にある輪郭を参照できます。

     

    さらに

    contour = contour->h_next;

    とすれば、さらに次の輪郭を参照出来ます。

     

    もし、contourNULL になったら同じ階層に、同じ親を持つ別の輪郭は無い事を意味しています。

     

    同じ様に

    contour = contour->v_next;

    とすれば、現在の輪郭のさらに内側にある輪郭へとポインタが移動します。

     

    このようにh_nextv_nextを使うと、全ての輪郭を構造的に参照することが可能となります。

     

    同様に

    【mode = CV_RETR_EXTERNAL の場合】

    階層
    (Level)
    輪郭構造

    のように、一番外側の白の輪郭のみを取得します。

     

    【mode = CV_RETR_LIST の場合】

    階層
    (Level)
    輪郭構造

    のように、白の輪郭、黒の輪郭、内側、外側関係なく、同じ階層で輪郭が取得されます。

     

    【mode = CV_RETR_CCOMP の場合】

    階層
    (Level)
    輪郭構造

    のように、白の輪郭の一つ下のレベルに黒の輪郭を持つ構造となります。
    ただし、ここで大事なのは白の輪郭のさらに内側にある白の輪郭(上図の5や6)も同じ階層となるので、ご注意下さい。

     

    ※modeの設定は共通して最初の階層の輪郭は白色の輪郭になっているようです。
    そのため、白色の地に黒色の輪郭のある画像を処理すると、最初の輪郭は画像全体となるのでご注意下さい。

     

    ということで、cvFindContours関数を使って輪郭を描画するプログラムはこんな感じ↓になります。

    // Labelling.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
    //
    
    #include "stdafx.h"
    
    //プロジェクトのプロパティ⇒C/C++⇒全般 の追加のインクルードディレクトリに
    // 『C:\OpenCV2.2\include』を追加のこと
    #include "opencv2\\opencv.hpp"
    
    #ifdef _DEBUG
        //Debugモードの場合
        #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220d.lib")
        #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220d.lib")
        #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220d.lib")
    #else
        //Releaseモードの場合
        #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_core220.lib")
        #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_imgproc220.lib")
        #pragma comment(lib,"C:\\OpenCV2.2\\lib\\opencv_highgui220.lib")
    #endif
    
    // 関数宣言
    void GetContourFeature(CvSeq*);
    void DrawChildContour(IplImage*, CvSeq*,int);
    void DrawNextContour(IplImage*,	CvSeq*, int);
    void cv_Labelling(IplImage*, IplImage*);
    
    //各種輪郭の特徴量の取得
    void GetContourFeature(CvSeq *Contour){
    	//面積
    	double Area = fabs(cvContourArea(Contour, CV_WHOLE_SEQ));
    	//周囲長
    	double Perimeter = cvArcLength(Contour);
    	//円形度
    	double CircleLevel = 4.0 * CV_PI * Area / (Perimeter * Perimeter);
    
    	//傾いていない外接四角形領域(フィレ径)
    	CvRect rect = cvBoundingRect(Contour);
    	//輪郭を構成する頂点座標を取得
    	for ( int i = 0; i < Contour->total; i++){
    		CvPoint *point = CV_GET_SEQ_ELEM (CvPoint, Contour, i);
    	}
    }
    
    void DrawChildContour(	//子の輪郭を描画する。
    	IplImage *img,	//ラベリング結果を描画するIplImage(8Bit3chカラー)
    	CvSeq *Contour, //輪郭へのポインタ
    	int Level		//輪郭のレベル(階層)
    	){
    
    	// 領域の色
    	CvScalar color;
    	// 輪郭を描画する色の設定
    	CvScalar ContoursColor;
    
    	if ((Level % 2) == 1){
    		//白の輪郭の場合
    		// 領域の色
    		color = CV_RGB( rand()&255, rand()&255, rand()&255 );
    		// 輪郭の色
    		ContoursColor = CV_RGB( 255, 0, 0 );
    
    	}else{
    		//黒の輪郭の場合(内側の場合)
    		// 領域の色
    		color = CV_RGB(0, 0, 0);
    		// 輪郭の色
    		ContoursColor = CV_RGB( 0, 0, 255 );
    	}
    
    	//輪郭の描画
    	cvDrawContours( img, Contour, color, color, 0, CV_FILLED);			// 領域
    	cvDrawContours( img, Contour, ContoursColor, ContoursColor, 0, 2);	// 輪郭
    
    	//輪郭を構成する頂点座標を取得
    	for ( int i = 0; i < Contour->total; i++){
    		CvPoint *point = CV_GET_SEQ_ELEM (CvPoint, Contour, i);
    		//cvDrawCircle(img, *point, 3, CV_RGB(0, 255, 0));
    	}
    
    	//各種輪郭の特徴量の取得
    	GetContourFeature(Contour);
    
    	if (Contour->h_next != NULL)
    		//次の輪郭がある場合は次の輪郭を描画
    		DrawNextContour(img, Contour->h_next, Level);
    
    	if (Contour->v_next != NULL)
    		//子の輪郭がある場合は子の輪郭を描画
    		DrawChildContour(img, Contour->v_next, Level + 1);
    }
    
    void DrawNextContour(	//次の輪郭を描画する。
    	IplImage *img,	//ラベリング結果を描画するIplImage(8Bit3chカラー)
    	CvSeq *Contour, //輪郭へのポインタ
    	int Level		//輪郭のレベル(階層)
    	){
    
    	// 領域の色
    	CvScalar color;
    	// 輪郭を描画する色の設定
    	CvScalar ContoursColor;
    
    	if ((Level % 2) == 1){
    		//白の輪郭の場合
    		// 領域の色
    		color = CV_RGB( rand()&255, rand()&255, rand()&255 );
    		// 輪郭の色
    		ContoursColor = CV_RGB( 255, 0, 0 );
    
    	}else{
    		//黒の輪郭の場合(内側の場合)
    		// 領域の色
    		color = CV_RGB(0, 0, 0);
    		// 輪郭の色
    		ContoursColor = CV_RGB( 0, 0, 255 );
    	}
    
    	//輪郭の描画
    	cvDrawContours( img, Contour, color, color, 0, CV_FILLED);			// 領域
    	cvDrawContours( img, Contour, ContoursColor, ContoursColor, 0, 2);	// 輪郭
    
    	//輪郭を構成する頂点座標を取得
    	for ( int i = 0; i < Contour->total; i++){
    		CvPoint *point = CV_GET_SEQ_ELEM (CvPoint, Contour, i);
    		// 頂点座標の描画
    		//cvDrawCircle(img, *point, 3, CV_RGB(0, 255, 0));
    	}
    
    	//各種輪郭の特徴量の取得
    	GetContourFeature(Contour);
    
    	if (Contour->h_next != NULL)
    		//次の輪郭がある場合は次の輪郭を描画
    		DrawNextContour(img, Contour->h_next, Level);
    
    	if (Contour->v_next != NULL)
    		//子の輪郭がある場合は子の輪郭を描画
    		DrawChildContour(img, Contour->v_next, Level + 1);
    }
    
    void cv_Labelling(	//ラベリング処理
    	IplImage *src,	//入力画像(8Bitモノクロ)
    	IplImage *dst	//出力画像(8Bit3chカラー)
    	) {
    
    	CvMemStorage *storage = cvCreateMemStorage (0);
    	CvSeq *contours = NULL;
    
    	if (src == NULL)
    		return;
    
    	// 画像の二値化【判別分析法(大津の二値化)】
    	cvThreshold (src, src, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
    
    	// 輪郭の検出(戻り値は取得した輪郭の全個数)
    	int find_contour_num = cvFindContours (
    			src,					// 入力画像
    			storage,				// 抽出された輪郭を保存する領域
    			&contours,				// 一番外側の輪郭へのポインタへのポインタ
    			sizeof (CvContour),		// シーケンスヘッダのサイズ
    			CV_RETR_TREE,			// 抽出モード
    									// CV_RETR_EXTERNAL - 最も外側の輪郭のみ抽出
    									// CV_RETR_LIST - 全ての輪郭を抽出し,リストに追加
    									// CV_RETR_CCOMP - 全ての輪郭を抽出し,二つのレベルを持つ階層構造を構成する.1番目のレベルは連結成分の外側の境界線,2番目のレベルは穴(連結成分の内側に存在する)の境界線.
    									// CV_RETR_TREE - 全ての輪郭を抽出し,枝分かれした輪郭を完全に表現する階層構造を構成する.
    			CV_CHAIN_APPROX_SIMPLE	// CV_CHAIN_APPROX_SIMPLE:輪郭の折れ線の端点を取得
    									// CV_CHAIN_APPROX_NONE: 輪郭の全ての点を取得
    									// CV_CHAIN_APPROX_TC89_L1		:Teh-Chinチェーンの近似アルゴリズム中の一つを適用する
    									// CV_CHAIN_APPROX_TC89_KCOS
    			);
    
    	if (contours != NULL){
    		//処理後画像を0(黒)で初期化
    		cvZero(dst);
    		//輪郭の描画
    		DrawNextContour(dst, contours, 1);
    	}
    
    	//メモリストレージの解放
    	cvReleaseMemStorage (&storage);
    
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    
        //画像データの読込(グレースケールで読込)
        IplImage* src = cvLoadImage("sample.bmp", CV_LOAD_IMAGE_GRAYSCALE);
        if (src == NULL){
            return 0;
        }
    
        //表示ウィンドウの作成
        cvNamedWindow("src");
        cvNamedWindow("dst");
    
        //処理後画像データの確保
        IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, 3);
    
    	// 入力画像の表示
    	cvShowImage ("src", src);
    
        // ラベリング処理(※入力画像(src)はcvFindContoursにより変更されます。)
        cv_Labelling(src, dst);
    
        // ラベリング画像の表示
        cvShowImage ("dst", dst);
    
        // キー入力待ち
        cvWaitKey (0);
    
        // 全てのウィンドウの削除
        cvDestroyAllWindows();
    
        // 画像データの解放
        cvReleaseImage(&src);
        cvReleaseImage(&dst);
    
    	return 0;
    }

    サンプルプログラムのダウンロードはこちらより

    opencv-labelling.zip

    (OpenCV2.2対応。Visual Studio 2010 C++ Expressにより作成)

     

    上記、サンプルプログラムではcvFindContours関数で輪郭情報を取得し、オリジナルのDrawNectContour関数を用いて再帰的に輪郭、および領域を描画しています。

     

    また、GetContourFeature関数で、面積、周囲長、円形度、フィレ径、輪郭の頂点座標の計算だけをしています。この部分は必要に応じて改良してみて下さい。

     

    ここで注意が必要なのは、輪郭から面積を計算する関数cvContourAreaは、下図の様に輪郭線で囲まれた領域の内側の面積を計算します。(画素数ではありません。

    下図の例では面積は 17.5 となります。
    輪郭の内側の穴の面積は考慮されないので、穴の部分を除外したい場合はv_nextで一つ下の階層にある黒の輪郭を全て取得し、黒の面積を白の面積から引いて下さい。

     

    輪郭座標について

    CV_GET_SEQ_ELEMマクロで取得している輪郭を構成する座標はcvFindContours関数の6番目の引数methodの設定できまります。

     

    method = CV_CHAIN_APPROX_NONE の場合

    上図のように輪郭を構成している座標全てを取得します。

     

    method = CV_CHAIN_APPROX_SIMPLE の場合

    上図のように輪郭を構成している折れ線の角の座標を取得します。

     

    method = CV_CHAIN_APPROX_TC89_L1
    もしくは     CV_CHAIN_APPROX_TC89_KCOS の場合

     

    上図のように輪郭を構成している座標をTeh-Chinチェーンの近似アルゴリズムに基づいて近似した線の折れ線の角の座標を取得します。
    との事ですが、詳細は良く分かりませんでした...(上図も実際の結果と異なるかも?)

     

    OpenCVでは他にも、いろいろな特徴量を計算する関数が用意されています。
    おそらくcvFindContours関数を使ってラベリング処理をした方が、いろんな使い道が考えられると思うので、上記のサンプルプログラムを目的に応じて改良してみて下さい。

     

    OpenCVへ戻る

     

    【OpenCV】インプレースモード

    OpenCVの関数では入力画像(src)と出力画像(dst)に同じ値(src=dst)を指定しても処理してくれる
    関数があり、このことをインプレースモードと言います。

     

    例えば

    cvErode(src, src);
    cvErode(src, src);
    cvDilate(src, src);
    cvDilate(src, src);

    と処理を行っても大丈夫な関数があります。
    どの関数がインプレースモードに対応しているのかは、OpenCV.jp、他、リファレンスマニュアルを参照下さい。

     

    普通に考えれば入力画像と出力画像に同じデータを用いる事はできない場合が多いかと思いますが、OpenCVの関数のソースコードを見てみると、入力画像(src)と出力画像(dst)の画像データのポインタが等しい場合は、関数内部で処理後の画像データを格納するためのメモリを確保し、画像処理後に処理後の画像データを入力画像にコピーして、メモリを解放する処理が行われています。

     

    そのため、インプレースモードを使えば、ちょっとした処理の評価をするにはプログラムのコード量も減って簡単に処理ができるのですが、処理を何回も繰り返す場合には、入力画像と出力画像のデータを分けた方が高速に処理を行うことができる(余計なメモリの確保やコピー処理が行われない)ので、入力画像(src)と出力画像(dst)は別の値を指定した方が良いと思います。

     

    また、cvSmoothのように同じ関数でも、引数によって処理を分けている場合、引数によってインプレースモードに対応、非対応な場合があるので、ご注意下さい。

     

    例えばcvSmoothの場合、

    平滑化の方法
    smoothtype
    処理内容 インプレースモード
    CV_BLUR_NO_SCALE スケーリング無しの単純平滑化 対応
    CV_BLUR 単純平滑化 対応
    CV_GAUSSIAN ガウシアンフィルタ 対応
    CV_MEDIAN メディアンフィルタ 非対応
    CV_BILATERAL バイラテラルフィルタ 非対応

     
     

    OpenCVへ戻る

     

    行列の基礎

    正方行列

    行と列の数が同じ行列

    など

    単位行列

    行と列の位置が同じ場所の成分(対角成分)が1、それ以外が0となる行列

    など。
    EIであらわされる。

    AE=EA=A

    が成り立つ。

    零行列

    全ての行列の成分が  となる行列

    など。
    O であらわされる。

    転置行列

    行と列の成分を入れ替えた行列を転置行列という。
    行列Aの転置行列はTを使ってATとあらわす。

    の転置行列は

    となる。

    行列の足し算、引き算

    行列の積

    行列の計算

    各行列A、B、Cにおいて

    A + B = B + A
    (A + B) + C = A + (B + C)
    (AB)C = A(BC)
    A(B + C) = AB + AC
    (AB)-1 = B-1A-1
    (AB)T = BTAT
    (AT)T = A
    (ATB)T = BTA

    が成り立ちます。

    ただし、単位行列以外の計算では、行列の掛ける順番を入れ替えると、答えは異なります。

    AB≠BA

    使える数学へ戻る

    【参考書籍】ディジタル画像処理

    画像処理関連の本はかなり持っていますが、その中でも一番のお気に入りがこの本。

     

    はじめての画像処理技術のような本で画像処理の基礎の基礎は覚えてから読むとちょうど良いと思います。

    書かれている内容もカメラや照明、レンズなどの概要説明から始まり、文字やナンバープレート、指紋などのパターン認識の概要など難しめのテーマの概要も説明されています。

     

    画像処理をするのであれば、まずは持っておきたい一冊です。

     

     

    目次

    1. イントロダクション
    2. 画像入出力
    3. 画像生成モデル
    4. 画像の性質と撮影パラメータ
    5. 画素ごとの濃淡変換
    6. 領域に基づく濃淡変換(空間フィルタリング)
    7. 周波数領域におけるフィルタリング
    8. 画像の復元と再構成
    9. 幾何学的変換
    10. 2値化画像処理
    11. 領域処理
    12. パターンと図形の検出
    13. パターン認識
    14. 動画像処理
    15. 空間情報の取得と利用
    16. 画像符号化
    17. 応用