OpenCVSharp4でテンプレートマッチング

テンプレートマッチングとは

入力した画像から、テンプレートと似ている場所を抽出する方法。

方法

Cv2.MatchTemplate(src, templ, result, TemplateMatchModes)  

を使う。
TemplateMatchModesには以下の種類がある

TemplateMatchModes 和訳 説明
CCoeff 共分散 値が大きいほど類似
CCoeffNormed 相関係数 結果は-1 以上1以下。1で全く同じ画像
CCorr 相互相関(スライディング内積 値が大きいほど類似
CCorrNormed 正規化相互相関 結果は-1 以上1以下。1で全く同じ画像
SqDiff 誤差2乗和 値は0以上。0で全く同じ画像
SqDiffNormed 正規化誤差2乗和 値は0以上1以下。0で全く同じ画像

マッチング後にCv2.Normalize()で正規化するのもアリ

詳細な式はこちら

プログラム

OpenCVSharp4の導入はコチラ

using OpenCvSharp;  

namespace テンプレートマッチング {  
    class Program {  
        static void Main(string[] args) {  
            //ソース画像とテンプレートの読込  
            Mat src = new Mat(@"D:\20190321145626.png");  
            Mat templ = new Mat(@"D:\templ.png");  


            //テンプレートマッチング  
            Mat result = new Mat();  
            Cv2.MatchTemplate(src, templ, result, TemplateMatchModes.CCoeffNormed);  
            Cv2.ImShow("result", result);  

            //結果の描画  
            Mat dst = src.Clone();  
            for(int i =0; i<result.Cols;i++)  
                for(int j = 0; j < result.Rows; j++) {  

                    float thresh = (float)0.7;  

                    if(result.At<float>(j,i) >= thresh) {  
                        dst.Rectangle(new Point(i, j), new Point(i + templ.Cols, j + templ.Rows), Scalar.Red);  
                    }  
                }  


            Cv2.ImShow("dst", dst);  
            Cv2.WaitKey();  

        }  
    }  
}  

結果

入力画像

f:id:negizoku:20200920231626p:plain



テンプレート

f:id:negizoku:20200920231638p:plain



相関係数

f:id:negizoku:20200920231651p:plain



矩形描画

f:id:negizoku:20200920231704p:plain



参考

https://docs.opencv.org/4.0.0/de/da9/tutorial_template_matching.html
https://docs.opencv.org/4.0.0/df/dfb/group__imgproc__object.html#ga3a7850640f1fe1f58fe91a2d7583695d
https://en.wikipedia.org/wiki/Cross-correlation

OpenCVSharp4で逆投影

逆投影とは

2つの画像のヒストグラム中で近しいものを強調すること

プログラム

 

using System;  
using OpenCvSharp;  

namespace 逆投影 {  
    class Program {  
        static void Main(string[] args) {  
            //画像の読み込み  
            Mat src1 = new Mat(@"D:\Back_Projection_Theory0.jpg");  
            Mat src2 = new Mat(@"D:\Back_Projection_Theory2.jpg");  
            Cv2.ImShow("src1", src1);  
            Cv2.ImShow("src2", src2);  


            //src1をHSVに変換しHue(色相)のみを取り出す  
            src1.CvtColor(ColorConversionCodes.BGR2HSV);  
            Mat[] _hsv = src1.Split();  
            Mat hue = _hsv[0];  

            //src2をHSVに変換しHue(色相)のみを取り出す  
            src2.CvtColor(ColorConversionCodes.BGR2HSV);  
            Mat[] _hsv2 = src2.Split();  
            Mat hue2 = _hsv2[0];  

            //Hueヒストグラム作成  
            Mat hist = new Mat();  
            Cv2.CalcHist(new Mat[] { hue }, new int[] { 0 }, null, hist, 1, new int[] { 256 }, new Rangef[] { new Rangef(0, 256), });  
            hist.Normalize(0, 255, NormTypes.MinMax);  

            //hu2  
            Mat backProj = new Mat();  
            Cv2.CalcBackProject(new Mat[] { hue2 }, new int[] { 0 }, hist, backProj, new Rangef[] { new Rangef(0, 180)});  
            Cv2.ImShow("backProject", backProj);  



            Cv2.WaitKey();  
        }  
    }  
}  

参考

http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_histograms/py_histogram_backprojection/py_histogram_backprojection.html
https://docs.opencv.org/4.0.0/da/d7f/tutorial_back_projection.html

OpenCVSharp4で凹凸係数法による影の除去

はじめに

凹凸係数 rij とは、各画素 xij をその周辺の平均輝度 Rij で除することで得られる係数である。

カーネル(2n+1)×(2n+1)としたとき、凹凸係数 rij は次式で求まる。
rij=xijRijRij=1(2n+1)2p=nnq=nnxi+p,j+q=blurn×n(xij)
blurは平均値ぼかしである。
(※論文で触れられてないが Rij=0 のゼロ除算はどう定義しよう?)

方法

  1. 画像をグレースケールで読み込む
  2. 平均輝度Rijを求める
  3. 凹凸係数rijを求める
  4. 凹凸係数rijに二値化処理を行う

プログラム

using System;  
using OpenCvSharp;  

namespace 凹凸係数法 {  
    class Program {  
        static void Main(string[] args) {  
            //①画像をグレースケールで読み込み  
            Mat src = new Mat(@"D:\8f10.jpeg", ImreadModes.Grayscale);  
            //Cv2.ImShow("src", src);  
            src.ConvertTo(src, MatType.CV_32FC1);  

            //②平均輝度を算出  
            Mat R = new Mat();  
            Cv2.Blur(src, R, new Size(21, 21));  

            //③凹凸係数を算出  
            Mat r = new Mat();  
            Cv2.Divide(src, R, r);  
            r *= 127;  
            r.ConvertTo(r, MatType.CV_8UC1);  

            //④二値化する  
            Mat dst = new Mat();  
            Cv2.Threshold(r, dst, 0.98, 255, ThresholdTypes.Otsu);  
            dst.ConvertTo(dst, MatType.CV_8UC1);  

            dst.ImWrite(@"D:\dst.png");  
            Cv2.WaitKey();  
        }  
    }  
}  

結果


画像引用

f:id:negizoku:20200920231456p:plain



参考

https://www.jstage.jst.go.jp/article/iteac/2004/0/2004__2-5-1_/_article/-char/ja
https://www.jstage.jst.go.jp/article/iieej/36/3/36_3_204/_pdf/-char/ja
https://qiita.com/fallaf/items/1c5387a79027b2ec64b0

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

OpenCVSharp4で構造要素(structure elements)を使った処理

はじめに

OpenCVの公式サイトを見てstructure elments(≒カーネル)を使った水平線・鉛直線の抽出を勉強した。

方法

structure elementsと一致する箇所だけを残し、楽譜から、線と音符を切り分ける。

  1. 画像を読み込む
  2. 読み込んだ画像を二値化し反転する
  3. 鉛直(水平)線状のstructure elementsを作成
  4. structure elementsを用いてオープン処理し、鉛直(水平)でない領域を除去する。さらに、反転で白黒を元に戻す。
  5. (鉛直成分のみ)輪郭を滑らかにする。

プログラム

using System;  
using OpenCvSharp;  

namespace 垂直線_水平線の検出 {  
    class Program {  
        static void Main(string[] args) {  
            //① 画像をグレースケールとして読み込み  
            Mat src = new Mat(@"D:\src (1).png", ImreadModes.Grayscale);  
            Cv2.ImShow("src", src);  


            //② 二値化&反転を行う  
            Mat bw = new Mat();  
            Cv2.AdaptiveThreshold(src, bw, 255, AdaptiveThresholdTypes.MeanC, ThresholdTypes.BinaryInv, 15, 3);  
            Cv2.ImShow("bw", bw);  


            //③-a 水平な構造要素を作成  
            Mat horizontal = bw.Clone();  
            int horizontal_size = horizontal.Cols / 30;//構造要素の幅を定義  
            Mat horizontalStructure  
                = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(horizontal_size, 1));  


            //④-a 水平な構造要素を用いてオープン処理し、反転する  
            Cv2.MorphologyEx(horizontal, horizontal, MorphTypes.Open, horizontalStructure, new Point(-1, -1));  
            horizontal = 255 - horizontal;//反転  
            Cv2.ImShow("horizontal", horizontal);  


            //③-b 鉛直な構造要素を作成  
            Mat vertical = bw.Clone();  
            int vertical_size = vertical.Rows / 30;//構造要素の幅を定義  
            Mat verticalStructure  
                = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(1, vertical_size));  


            //④-b 鉛直な構造要素を用いてオープン処理し、反転する  
            Cv2.MorphologyEx(vertical, vertical, MorphTypes.Open, verticalStructure, new Point(-1, -1));  
            vertical = 255 - vertical;//反転  
            Cv2.ImShow("vertical", vertical);  


            //⑤-b 輪郭を滑らかにする  
            Mat edges = new Mat();//輪郭抽出  
            Cv2.AdaptiveThreshold(vertical, edges, 255, AdaptiveThresholdTypes.MeanC, ThresholdTypes.Binary, 3, -2);  
            edges.Dilate(Mat.Ones(2, 2, MatType.CV_8UC1));  
            Mat smooth = vertical.Clone();  

            smooth.Blur(new Size(5, 5));  
            smooth.CopyTo(vertical, edges);//輪郭をマスクとしてコピー              
            Cv2.ImShow("refined", vertical);  


            Cv2.WaitKey();  
        }  
    }  
}  

結果

f:id:negizoku:20200920231108p:plain



参考

https://docs.opencv.org/4.0.0/dd/dd7/tutorial_morph_lines_detection.html

OpenCVSharp4で射影変換

射影変換とは

画像平面を任意の3D視点から見直すこと

方法

  1. 画像の読み込み
  2. 入力画像の4点を指定
  3. 出力画像の4点を指定
  4. 指定した点を対応させる射影変換のパラメータを求める
  5. 射影変換を行う

射影変換の数式は以下の通り。全部で9個のパラメータがあるが、8自由度である。(分母と分子をc3で割ればよい)

(x,y)(a1x+b1y+c1a3x+b3y+c3a2x+b2y+c2a3x+b3y+c3)

対応させる4点が長方形の頂点である場合、長方形のアスペクト比(縦横比)を把握していないと正しく変換することができない。
また、今回対応させる4点の座標は、ペイントで求めた。

プログラム

using System;  
using OpenCvSharp;  

namespace 射影変換 {  
    class Program {  
        static void Main(string[] args) {  
            //①画像の読み込み  
            Mat src = new Mat(@"D:\kanban.jpg");  


            //②入力画像の4点を指定  
            float[,] srcSumi = new float[4,2];  
            srcSumi =new float[,] {  
                { 241,  89},  
                { 339, 108},  
                { 349, 334},  
                { 231, 340}  
                };  
            Mat srcPts = new Mat(4, 2, MatType.CV_32FC1, srcSumi);  


            //③出力画像の4点を指定  
            float[,] dstSumi = new float[4, 2];  
            dstSumi = new float[,] {  
                {   0,   0},  
                { 100,   0},  
                { 100, 200},  
                {   0, 200}  
                };  
            Mat dstPts = new Mat(4, 2, MatType.CV_32FC1, dstSumi);  

            //④指定した4点を対応させる射影変換のパラメータを求める  
            Mat parameters = Cv2.GetPerspectiveTransform(srcPts, dstPts);  


            //⑤射影変換を行う  
            Mat dst = new Mat();  
            Cv2.WarpPerspective(src, dst, parameters, src.Size());  


            //⑥画像の表示  
            Cv2.ImShow("射影変換", dst);  
            Cv2.WaitKey();  

        }  
    }  
}  

結果

入力画像

f:id:negizoku:20200920230950j:plain



出力画像

f:id:negizoku:20200920231007j:plain



有名なグリコの看板の四隅を正面向きにした。看板と同一平面上にない物は歪んでしまう。

参考

https://docs.opencv.org/4.0.0/d2/de8/group__core__array.html#gad327659ac03e5fd6894b90025e6900a7
https://fermiumbay13.hatenablog.com/entry/2018/08/14/032643

OpenCVSharp4の画像の読込、表示および保存

基本的な機能も備忘録代わりに書いておく

画像の読み込み

①Mat()で読み込む方法と②Cv2.imread()で読み込む方法がある。

//①Mat()で読み込み  
Mat mat1 = new Mat("D:\\src.png");  

//②Cv2.Imreadで読み込み  
Mat mat2 = Cv2.Imread("D:\\src.png");  

ちなみに両方とも第2引数に Imreadmodes.Grayscaleを追加することで、
グレースケールで読み込める。
バックスラッシュを使えないときは「\\」を代わりに使うか””の前に「@」を書く

画像の表示

①Cv2.Imshow()を使う方法と②ShowImage()を使う方法

//①Cv2.Imshow()を使う方法  
Cv2.Imshow("画像です", mat);  
Cv2.WaitKey();  

//②ShowImageを使う方法  
using(var window = new Window("画像だね")){  
    /*ここらへんに他の処理を  
    書いてもかまわない*/  
    window.ShowImage(mat);  
    //こっちにも書いていいし、2重にusingステートメントしてもいい  
    Cv2.WaitKey();  
}  

ちなみに②の方がメモリ効率がいいらしい。

画像の保存

ImWriteを使う

mat.Imwrite("D:\\dst.png");  
//あるいは  
Cv2.Imwrite("D:\\dst.png", mat);  

サンプルコード

using System;  
using OpenCvSharp;  

namespace 画像の読み込みと表示_保存 {  
    class Program {  
        static void Main(string[] args) {  
            //画像の読み込み  
            Mat mat1 = new Mat("D:\\src.png");  
            Mat mat2 = Cv2.ImRead("D:\\src.png", ImreadModes.Grayscale);  

            //画像の保存  
            Cv2.ImShow("画像だよ", mat1);  
            using(var window2 = new Window("画像だね")) {  
                window2.ShowImage(mat2);  

            }  
            Cv2.WaitKey();  


            //画像の保存  
            mat1.ImWrite("D:\\dst1.png");  
            Cv2.ImWrite("D:\\dst2.png", mat2);  

        }  
    }  
}