opencvのopticalflowで特徴点が探し出せないときと見失ったときにやったこと

# opencvのopticalflowで特徴点が探し出せないときと見失ったときにやったこと

以下の内容で進めていきます
- opticalflowを使う上で
- 使う上での問題点1 欲しい場所に特徴点がつかない
- 解決策1
- 使う上での問題点2 早く動くと見失う
- 解決策()
- 開発環境
###### 画像とかはその内入れる

## opticalflowを使う上で
opticalflowというのは画像中の特徴的な箇所を検出して追跡する技術。
かなり有用な技術なのだがいざ使ってみると様々な問題に直面する。
ここでは私が使用するうえでちょっと面倒だったことを書いていこうと思う。
特に特徴点を見出す方法と見失ったときの対処法について紹介する。


## 使う上での問題点1
### 欲しい場所に特徴点がつかない
画像中の特徴的な部分を見つける手法であるために特徴的な場所があると、本当に欲しい場所に特徴が付かないことがある。
一例を挙げると強い照明等だ。

## 解決策
人が最初に特徴点の箇所を指定してそこを追いかけるようにする。
たとえば以下のソースコードのような感じで画像の一部を切り取って、その画像の特徴点を検出するといい感じに出てくれる。
アルゴリズム例は以下に示す。
1.動画の1フレーム目だけを表示し、特徴点が欲しい場所を範囲指定する。
2.指定した範囲内の特徴点を検出する。
3.検出した特徴点の重心の座標を計算する（このときのフレームをnフレーム目とする）
4.n+1フレーム目で3で計算した重心の半径何pixel内で追跡する特徴点を探す
5.4のn+1フレームが動画の最後のフレームなら終了。そうでないなら3へ

こんな感じで特徴点を検出すると動画内に強い照明がある場合でも追跡してくれた。
以下にソースを示すがコードを抜き出してきているだけなのでこのままだと何かしらのエラーが出る。

```py
def extractFeatures(self, gray, rect, features):
    featureList = cv2.goodFeaturesToTrack(gray,100,0.01,10)
    for feature in featureList:
        if rect[0] <= feature[0][0] <= rect[2] and rect[1] <= feature[0][1] <= rect[3]:
            features = self.addList(features, feature[0][0], feature[0][1])
    return features

def featureMouseClicked(self, event, x, y, flags, param):
    if event != cv2.EVENT_LBUTTONDOWN and event != cv2.EVENT_LBUTTONUP:
        return
    if event == cv2.EVENT_LBUTTONDOWN:
        self.rect[0]=x
        self.rect[1]=y
    if event == cv2.EVENT_LBUTTONUP:
        self.rect[2]=x
        self.rect[3]=y
        self.featureRectSet=True

def addList(self,lis,x,y):
    if lis == None:
        lis = np.array([[[x,y]]], np.float32)
    else:
        lis = np.append(lis, [[[x, y]]], axis = 0).astype(np.float32)
    return lis

def cut(img,x,y,width,height):
    ux=0
    uy=0
    dx=0
    dy=0
    if img is None:
        return None,dy,uy,dx,ux
    img_height, img_width = img.shape[:2]
    

    if y+height/2 > img_height:
        uy = img_height
    else:
        uy = y+height/2
    if y-height/2 < 0:
        dy = 0
    else:
        dy = y-height/2

    if x+width/2 > img_width:
        ux = img_width
    else:
        ux = x+width/2
    if x-width/2 < 0:
        dx = 0
    else:
        dx = x-width/2

    if not(dx<ux and dy<uy):
        return None,dy,uy,dx,ux
    if not(0<=ux<=img_width or 0<=dx<=img_width or 0<=uy<=img_height or 0<=dy<=img_height):
        return None,dy,uy,dx,ux
    return img[dy:uy,dx:ux],dy,uy,dx,ux

def nextFrame(self):
    end_flag, Movieframe = self.Moviecap.read()
    #終了判定
    if( Movieframe is None):
        return None

    #現在のフレーム保存
    self.nowMovieFrame = Movieframe

    # オプティカルフロー検出
    # 前回の特徴点の位置を中心として指定した横幅と縦幅の矩形で動画を切り出す、ここでは最初に範囲指定した矩形の縦幅。横幅と同じにしている
    mask = np.zeros_like(Movieframe)
    cutFrame,dy,uy,dx,ux= cut(Movieframe,
        int(self.points[len(self.points)-1 - i][0]),#x
        int(self.points[len(self.points)-1 - i][1]),#y
        2*abs(self.rect[1]-self.rect[3]),2*abs(self.rect[0]-self.rect[2]))#
    mask[dy:uy,dx:ux] = cutFrame

    self.grayNext = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
    self.featureNext, status, err = cv2.calcOpticalFlowPyrLK(self.grayPrev, self.grayNext, self.featurePrev, None,
             (dict( winSize  = (15,15),
              maxLevel = 2,
              criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))))
    
    # オプティカルフローを検出した特徴点を選別（0：検出せず、1：検出した）
    if status != None:
        goodPrev = self.featurePrev[status == 1]
        goodNext = self.featureNext[status == 1]
        for i, (nextPoint, prevPoint) in enumerate(zip(goodNext, goodPrev)):
                prev_x, prev_y = prevPoint.ravel()
                next_x, next_y = nextPoint.ravel()

    if self.featureNext is None:
        return 0

    #次のオプティカルフローの準備
    self.grayPrev = self.grayNext.copy()
    self.featurePrev = goodNext.reshape(-1, 1, 2)

    #成功したら1を返す
    return 1


``` 

## 使う上での問題点2 
### 特徴点が付いた物体が早く動くと特徴点を見失う
ゆっくりな動きならopticalflowはよく追跡してくるが少しでも早い動きになるとすぐ特徴点を見失ってしまう。
ちなみに「解決策1で範囲指定してるせいで早く動くと特徴点のある場所が範囲外に出てしまって特徴点を見失ってしまうんじゃないか」という意見もあるだろうが範囲を指定しない状態でも特徴点を見失う現象が見られている。

## 解決策
対応策としては動画を録画する際にfpsをなるべく上げる等がある。しかし、金銭面で不可能なことも往々にしてある。
今回は特徴点を見失った場合にやむ無く動画を止めて範囲指定からやり直すという方法を採った。
上記のソースコードのnextFrame関数を呼び出し、0が帰ってきたときに動画の再生を一時停止してfeatureMouseClickedとextractFeaturesを呼び出して範囲指定を再び行って動画の再生を行うというものだ。
おそらく中間フレームを生成してfpsを上げるとかもっとやりようはあると思うがそこまで実装できる力はなかった。

## 開発環境
opencv 2.4.13
python 2.7.11