OpenCVSharp4でハフ変換による直線検出

ハフ変換(Hough Transform)について

直線を表す式としてよく知られるのが y=ax+b である。
このとき(a,b)が決まれば直線の式も求まるので、直線を
F(a,b)=y(ax+b)=0と考えることもできる。

しかし F(a,b)=0 の定義域は
<a<+<b<+であるので扱いづらい。

そこで、ハフ変換では次のようなH(ρ,θ)=0を考える。
H(ρ,θ)=ρ(xcosθ+ysinθ)=0

ここで ρ,θ の位置関係は次の通りである。

画像引用元

H(ρ,θ) の定義域は図より、画像のサイズを M×N とすれば、
M2+N2ρM2+N20θ<πである。

ここで、
xy平面上の直線は、ρθ平面上の点に対応する。

f:id:negizoku:20200920231308p:plain



さらにxy平面上の点を通りうる直線の集合は、ρθ平面上のサインカーブに対応する。

f:id:negizoku:20200920231323p:plain



ここで、xy平面上の点が一直線上に並んでいた場合、ρθ平面上のサインカーブは共通な1点で交わる。

f:id:negizoku:20200920231340p:plain



このようなρθ平面上で集中する場所を探すことで直線検出ができる。

方法

  1. 画像をグレースケールとして読み込む
  2. 読み込んだ画像の輪郭抽出を行う
  3. Hough変換

プログラム

NugetでOpenCVSharp4とOpenCVSharp4.runtime.winをインストールし、次のプログラムを実行

using System;  
using OpenCvSharp;  

namespace ハフ変換による直線抽出 {  
    class Program {  
        static void Main(string[] args) {  
            //①画像をグレースケールで読み込み  
            Mat src = new Mat(@"D:\icon.jpg", ImreadModes.Grayscale);  
            Mat dst = new Mat();  
            Cv2.CvtColor(src, dst, ColorConversionCodes.GRAY2BGR);  
            Cv2.ImShow("src", src);  


            //②輪郭抽出を行う  
            Mat edge = new Mat();  
            Cv2.Canny(src, edge, 50, 255);  
            Cv2.ImShow("edge", edge);  

            //③標準Hough変換を行い、(ρ,θ)を取得  
            LineSegmentPolar[] lines;  
            double rhoBunkai = 1;//ρθ平面におけるρ方向の分解能(今回は1ピクセル)  
            double thetaBunkai = Math.PI / 180;//ρθ平面におけるθ方向の分解能(今回は1 deg)  
            int  thresh = 72;//最小のライン交差数  
            lines = Cv2.HoughLines(edge, rhoBunkai, thetaBunkai, thresh);  

            //④直線の描画  
            for(int i = 0; i < lines.Length; i++) {  
                float rho = lines[i].Rho;  
                float theta = lines[i].Theta;  

                double a = Math.Cos(theta);  
                double b = Math.Sin(theta);  
                double x0 = a * rho;  
                double y0 = b * rho;  


                Point pt1 = new Point {  
                    X = (int)Math.Round(x0 + 1000 * (-b)),  
                    Y = (int)Math.Round(y0 + 1000 * (+a))  
                };  
                Point pt2 = new Point {  
                    X = (int)Math.Round(x0 - 1000 * (-b)),  
                    Y = (int)Math.Round(y0 - 1000 * (+a))  
                };  

                dst.Line(pt1, pt2, Scalar.Red, 1, LineTypes.AntiAlias);  
            }  
            Cv2.ImShow("dst", dst);  

            Cv2.WaitKey();  
        }  
    }  
}  

結果

f:id:negizoku:20200920231416p:plain



Hough変換のパラメータthreshによって得られる直線の数が変わるのでうまく調節してやる必要がある。
直線の長さで閾値を設けたい場合、確率的ハフ変換HoughLinesP()を使うと良い

参考

https://docs.opencv.org/4.0.0/d9/db0/tutorial_hough_lines.html
https://qiita.com/YSRKEN/items/ee94c7c22599c2374722