直行座標の点 (x,y)を通る全ての直線は その直線と直角に交わる垂線のx軸との角度theta(θ)と長さrho(ρ)で表わされます。(x, y)をθとρの組み合わせに変換することをHough変換(直線を求めるための)といいます。(CodeZineより)
めちゃくちゃ簡単にいうと 画面上にしらみつぶしに直線をひいてみてその上にどれだけエッジが乗っかるかで直線を見つける手法です。
OpenCvSharpで 今回取り上げる 古典的Hough変換とよばれる手法を実現するには HoughLines2メソッドを使います。VisualStudioを使うとHoughLineまで入力して Ctrl+スペースキーを押下すると HoughLinesStandard という いかにも古典的Hough変換ぽいメソッドが表示されます。しかし後で示しますが結果NGでした。
また CvSeq HoughLines2(CvArr image,CvMat line_storage,HoughLinesMethod method,double rho,double theta,int threshold)
との形式もインテリセンスで表示されます。いろいろ実験しましたが line_storageの定義方法がわからりません。ここでは Schimaさんのサンプルにあった形式
static CvSeq HoughLines2( CvArr image, CvMemStorage line_storage,HoughLinesMethod method,double rho, double theta, int threshold, double param1, double param2) を使うことにします。
CvArr image: 8ビット,シングルチャンネルの2値入力画像。
CvMemStorage line_storage: 作業用メモリ領域
HoughLinesMethod method: 次の3種類の直線検出方法から選択します。
Standard 古典的Hough変換
MultiScale マルチスケールHough変換
Probabilistic 確率的Hough変換
double rho: ピクセル単位で表される距離分解能。
double theta: ラジアン単位で表される角度分解能。
int threshold: 閾値パラメータ.十分なヒット数(> threshold)を得た直線のみが出力されます。
double param1, double param2: methodにMultiScale(マルチスケールHough変換)を
指定した場合有効になるパラメータで Android版の説明に示した double srnおよびdouble stn に対応します。
今回は param1, param2を省略した古典的Hough変換を実験します。
コーディング |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using OpenCvSharp; using System.Diagnostics; namespace OpenCvShp { public partial class Form1 : Form { private IplImage src; IplImage edge; private Boolean flg = false; public Form1() { InitializeComponent(); } //初期処理(画像データ読み込み・表示) private void timer1_Tick(object sender, EventArgs e) { timer1.Enabled = false; src = new IplImage(@"C:\OpenCvTest\Data\a640_480.jpg", LoadMode.Color); edge = new IplImage(src.Size, BitDepth.U8, 1); Cv.Canny(src, edge, 100, 200, ApertureSize.Size3); flg = true; pictureBox1.Invalidate(); } private void btnHough_Click(object sender, EventArgs e) { CvMemStorage storage = new CvMemStorage(); CvSeq lines = Cv.HoughLines2(edge, storage, HoughLinesMethod.Standard, 1, Math.PI / 180, 120); for (int i = 0; i < lines.Total; i++) { CvLineSegmentPolar elem = lines.GetSeqElem(i).Value; float rho = elem.Rho; float theta = elem.Theta; double a = Math.Cos(theta); double b = Math.Sin(theta); double x0 = a * rho; double y0 = b * rho; CvPoint pt1 = new CvPoint(Cv.Round(x0 + 10000 * (-b)), Cv.Round(y0 + 10000 * (a))); CvPoint pt2 = new CvPoint(Cv.Round(x0 - 10000 * (-b)), Cv.Round(y0 - 10000 * (a))); dst.Line(pt1, pt2, CvColor.Red, 1, LineType.AntiAlias, 0); } lines.Dispose(); storage.Dispose(); pictureBox1.Invalidate(); } //描画処理 private void pictureBox1_Paint(object sender, PaintEventArgs e) { if (flg) { Bitmap bmp = new Bitmap(src.Width, src.Height); bmp = src.ToBitmap(); e.Graphics.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height); bmp.Dispose(); } } } }
実験結果 |
HoughLinesStandard 失敗 |
HoughLinesStandardメソッドの実験結果
ボタンクリック時の処理を
CvLineSegmentPolar[] lin = edge.HoughLinesStandard(1, Math.PI, 120); for (int i = 0; i < lin.Length; i++) { float rho = lin[i].Rho; float theta = lin[i].Theta; double a = Math.Cos(theta); double b = Math.Sin(theta); double x0 = a * rho; double y0 = b * rho; CvPoint pt1 = new CvPoint(Cv.Round(x0 + 1000 * (-b)), Cv.Round(y0 + 1000 * (a))); CvPoint pt2 = new CvPoint(Cv.Round(x0 - 1000 * (-b)), Cv.Round(y0 - 1000 * (a))); src.Line(pt1, pt2, CvColor.Red, 1, LineType.AntiAlias, 0); } pictureBox1.Invalidate();
とした結果は 以下のようになりました。
垂直な線は拾っているが それ以外は全く拾っていません。 HoughLinesStandardメソッドのバグでしょうか??
Android版直線描画処理 バグ発見 |
今回の結果にたどり着く前に Android版で実装したと 同様にロジックで直線の描画を行ったところ 下図のような結果になりました。Android版とまったく同じです。ところが正解版と比べると 水平に近いまたは 原点に近い点を通る直線が描画されていません。 rho cos(theta) sin(theta) の値により、場合分けを行い 求めた直線と上下左右4辺との交点を求め 直線を描画したつもりでいましたが ロジックが不十分で描画されない直線がありました。Android版の修正が必要なようです。
Windows7 Vs2008 OpenCv2.4 And OpenCvSharp |