OpenCVのコーナー検出結果から、誤検出したエッジ点を除くフィルターを考えてみた

# はじめに

- OpenCVには、画像のコーナー点を検出するcornerHarrisやgoodFeaturesToTrackなどの関数があります。
- しかし、これらの関数には癖があるようで、長方形の形（左）だとコーナーをきれいに見つけましたが菱形の形（右）だと苦手でした。赤点が検出点です。

![before.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2449065/c8069818-97f6-80ba-fe27-1a7e98fbd586.jpeg)

- 感度を良くすればコーナーを見つけますが、エッジ上の点を誤検出するケースが増えます。感度を下げれば誤検出は減りますが、コーナーを見落としやすくなります。
- そこで、感度良く検出したあとから、誤検出したエッジ点を除く方法を考えて、試しました。

### 工夫したこと
1. goodFeaturesToTrackの結果からスタート
　画像の全各要素をフィルターすると時間がかかるので、途中から始めて処理量削減。

2. 白図形の凸コーナーのみの検出に限定
　限定することで、フィルター精度向上。

3. フィルターは対照点の周囲の明るさをチェックして判断
　Fastアルゴリズムのアイデアを借用しました。

# 内容
### 1. PC環境
　実施したPC環境は以下のとおりです。

| | |
|:--:|:--:|
|CPU|Celeron N4100|
|メモリ|8GB LPDDR4|

### 2. 前準備
　Windowsで使えるようにするため、以下のツール / システムをインストールしました。
インストール時に参考にしたサイトも記載します。

1. python（使用したのはVer.3.7 ）
　実行時のベースシステムです。
（参考）https://qiita.com/ssbb/items/b55ca899e0d5ce6ce963
2. pip（使用したのはVer.21.2.4 ）
　他のツールをダウンロードする際に使うツールです。
（python3系ではバージョン3.4以降であれば、pythonのインストールと共にpipもインストールされます。）
（参考）https://gammasoft.jp/python/python-library-install/

3. OpenCV（使用したのはVer.4.5.3.56 ）
　画像系処理するためのライブラリです。
（参考）https://qiita.com/ideagear/items/3f0807b7bde05aa18240

4. numpy（使用したのVer.4.7.0.72）
　行列計算が得意な数値計算モジュールです。
（参考）https://qiita.com/butako/items/15d7cb5aaef90b09ccd8

### 3. pyファイルの作成
 組んだコードは次のとおりです。
~~~python
import numpy as np
import cv2 as cv

camera = cv.VideoCapture(0)                # カメラCh.(ここでは0)を指定

#周囲のリスト（中心点からの相対）#
l_2d_a = [ \
    [ 0, 3], \
    [ 1, 3], \
    [ 2, 2], \
    [ 3, 1], \
    [ 3, 0], \
    [ 3,-1], \
    [ 2,-2], \
    [ 1,-3], \
    [ 0,-3], \
    [-1,-3], \
    [-2,-2], \
    [-3,-1], \
    [-3, 0], \
    [-3, 1], \
    [-2, 2], \
    [-1, 3], \
    ]

ret, img = camera.read()              # フレームを取得
h, w, _ = img.shape              #フレームの高さと幅を取得

#フィルタリングのためのリスト変数
up_lst_a = list()              #周囲で黒→白に変わった場所のリスト変数
dwn_lst_a = list()              #周囲で白→黒に変わった場所のリスト変数
l_a = list()              #周囲の明るさのリスト変数

#フィルタリングのパラメータ
th3 = 15              #白と判断する下限値（中心点の明るさからの相対）
th5 = 0.40              #コーナーと判断する白エリアの最大割合
th6 = 0.6              #コーナーと判断する中心点の明るさ割合（周囲の明るさに対する相対）

try:
    while True:
        ret, img = camera.read()              #フレームを取得
        gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)              #モノクロ化
        gray = np.float32(gray)              #float32に型変換

#コーナー候補検出#
        corners= cv.goodFeaturesToTrack(gray, 300, 0.01, 1)

#各コーナー候補についてチェック#
        for corner in corners:
            x,y= corner[0]              #中心点座標を入手
            x= int(x)
            y= int(y)


#フィルタリング#
            if x > 2 and x < w-3 and y > 2 and y < h-3 :
                #初期化#
                up_lst_a.clear()
                dwn_lst_a.clear()
                l_a.clear()
                cnt = 0
                if gray[y,x] > gray[y + l_2d_a[-1][0], x + l_2d_a[-1][1]] + th3:
                    sg= -1
                else:
                    sg = 1

                #周囲をチェック#
                for pt in l_2d_a:
                    if gray[y,x] > gray[y + pt[0], x + pt[1]] + th3:
                        if sg > 0:
                            dwn_lst_a.append(cnt)              #白→黒に変わってたらリストに追加
                        sg = -1
                    else:
                        if sg < 0:
                            up_lst_a.append(cnt)              #黒→白に変わってたらリストに追加
                        sg = 1
                    l_a.append(gray[y + pt[0], x + pt[1]])              #周囲の明るさをリストに追加
                    cnt = cnt + 1

                if len(up_lst_a) == 1 and len(dwn_lst_a) == 1 :              #黒→白、白→黒が1回だけ。つまり白エリアが１つ
                            #白エリアの割合を計算#
                            siro_a = (dwn_lst_a[0]-up_lst_a[0])/len(l_2d_a)
                            if siro_a<0:
                                siro_a = siro_a + 1

                            if siro_a<th5:              #白エリアの割合がth5より小さい
                                if (gray[y,x]-min(l_a))/(max(l_a)-min(l_a))>th6:              #中心点の明るさが周囲の明るさの幅に対し、下限からth6より明るい
                                    cv.rectangle(img, (x-1,y-1),(x+1,y+1),(0,255,0),-1)             #フィルタリングをクリアした点を■マーク

        cv.imshow('frame',img)             #チェック結果を表示

        if cv.waitKey(1) & 0xFF == ord('q'):             #’q’キーを押したら、LOOPから抜ける
            break
    cv.destroyWindow('frame')
    camera.release()
except KeyboardInterrupt:
    cv.destroyWindow('frame')
    camera.release()
~~~

### 4. 説明

#### 1. フィルターで使用する周囲の点
　対象点の周囲半径3ピクセルのあたりの点、16点を使いました。
 
![FilterArea.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2449065/57c64201-a499-f95b-dcae-391b9b4db1ad.png)

#### 2. フィルターの３つの条件
**１）明るい範囲（白エリア）と暗い範囲（黒エリア）が１つずつ**
　周囲の点を順に並べたときに白→黒と黒→白の変化点の数がそれぞれ１つなこと。
　白と黒の境目は対照点の明るさより`th3`だけ暗いところとしました。
　境目を越えたら、その点をlistに追加して、数をカウントして調べてます。
~~~python
#周囲をチェック#
for pt in l_2d_a:
    if gray[y,x] > gray[y + pt[0], x + pt[1]] + th3:
        if sg > 0:
            dwn_lst_a.append(cnt)              #白→黒に変わってたらリストに追加
        sg = -1
    else:
        if sg < 0:
            up_lst_a.append(cnt)              #黒→白に変わってたらリストに追加
        sg = 1
    cnt = cnt + 1

if len(up_lst_a) == 1 and len(dwn_lst_a) == 1 :              #黒→白、白→黒が1回だけ。つまり白エリアが１つ
~~~
**２）白エリアの方が小さい**
　白の凸角のみ探すため、この条件をつけました。
　周囲の点を順に並べたときに白→黒と黒→白の変化点の位置で白エリアの幅を計算してます。
　周囲の点数（＝16）に対する白エリアの幅の割合が`th5`より小さいならOKとしてます。
~~~python
#白エリアの割合を計算#
siro_a = (dwn_lst_a[0]-up_lst_a[0])/len(l_2d_a)
if siro_a<0:
    siro_a = siro_a + 1

if siro_a<th5:              #白エリアの割合がth5より小さい
~~~
**３）対象点の明るさは白エリアに近い**
　調べてみると、エッジ点は暗くなりがちなことがわかりました。
　周囲の点の明るさの最大値-最小値の間のどのあたりに対象点があるかで判断してます。
　明るさの最大値-最小値の幅に対して対象点の明るさ-最小値の割合が`th6`より大きいならOKとしてます。
~~~python
if (gray[y,x]-min(l_a))/(max(l_a)-min(l_a))>th6:              #中心点の明るさが周囲の明るさの幅に対し、下限からth6より明るい
~~~

### 5. 結果
　環境によって適切値は変わると思いますが、パラメータを
　　`th3=15、th5=0.40、th6=0.6`
　に調整しました。
　100%ではないです（除けてないエッジ点、除かれたコーナー点がでます）が、向きによらず、かなり精度よくコーナー点だけ抜き出せるようになりました。緑点が検出点です。

![after.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2449065/9811acc5-45cf-f6ac-e493-f90cd374da1a.jpeg)


# 参考

参考記事
goodFeaturesToTrack）
https://qiita.com/hoshianaaa/items/d22fcb67d940e08b242b

FASTアルゴリズム）
http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_feature2d/py_fast/py_fast.html

参考書籍
画像認識 (機械学習プロフェッショナルシリーズ) 原田達也 (著) 講談社