raspiでautofocusを実装してみる(2)

(1)は[こちら](https://qiita.com/kons_9/items/b68c9125d7e4c12da92c)です．商品だとかドキュメントの紹介があります．

## Autofocusの(AF)アルゴリズム
まずAFのアルゴリズムをいくつか紹介する前に基本の考え方を紹介します．
AFには2種類あって

1. passive autofocus(ソフトウェア，コントラストAF)
2. active autofocus(ハードウェア，位相差AF)

となっています．今回はソフトフェアでの実装なのでpassive autofocusです．

### 基本的な考え方
AFは画像のピントを合わせるために行われます．そのためピントが合っている状態を表現する必要があります．
そこで使われるのがコントラストとシャープネスです．

#### コントラスト
コントラストとは分布の広がりを定量化した値のことです．様々な定義が存在しますが例としてあげると

```math
I_{max} - I_{min}, \frac{I_{max}-I_{min}}{I_{max}+I_{min}},\frac{I_{max}}{I_{min}},\frac{\sqrt{E[(I-E[I])^2]}}{E[I]},\frac{\sqrt{E[(I^2-E[I^2])^2]}}{E[I^2 ]}

```
のようなものがあります．
この分布の広がりを最大化するような焦点をもっともピントが合っている状態として表現します．
このような方法で表現できる理由は，ピントが合わないことにより錯乱円が生じ，色が混ざり合います．その結果色の表現幅が小さくなることになり分布が狭くなるからです．

#### シャープネス
シャープネスは色の変わり目がより極端になる時に大きくなるような量のことで，直感的に分かりやすいAFの方法だと考えられます．
これを調べるには関数の微分の絶対値を調べてあげれば良いです．
画像における微分は1次ではsobelフィルタやprewittフィルタで，2次の微分はlaplacianフィルタで実装可能です．

## pythonでの実装
公式の実装ではsobelフィルタとlaplacianフィルタを実装しているため，ここではコントラストを最大化する方向で進めます．
今回は分散で近似しました．

```python
import os
import time
import picamera
from ctypes import CDLL
import sys
import cv2
import numpy as np
from picamera.array import PiRGBArray

class Arducam():
    def __init__(self, display=True, preview=True):
        self.arducam_vcm = CDLL('./lib/libarducam_vcm.so')
        self.arducam_vcm.vcm_init()
        self._display = display
        self.camera = picamera.PiCamera()
        self.focusing(0)
        self.max_val = 1023
        self.min_val = 0
        self._preview = preview
        if preview:
            self.camera.start_preview()

    def focusing(self, val):
        self.arducam_vcm.vcm_write(val)
        # if self._display:
        #     print("focus value: {}".format(val))

    def autofocus(self):
        """
        コントラストAFを実装する
        愚直に探索

        """
        if self._display:
            print("start focusing...")

        max_c_val = 0
        max_f_val = 0

        f_val = 0
        while self.is_valid_focus_val(f_val):
            self.focusing(f_val)
            c_val = self.calculate_camera_val()
            if c_val > max_c_val:
                max_c_val = c_val
                max_f_val = f_val
            f_val += 3
        self.focusing(max_f_val)
        time.sleep(5)
        print("max focus value =", max_f_val,
              "max contrast value =", max_c_val)

    def capture(self, file):
        return self.camera.capture(file)

    def set_resolution(self, size):
        self.camera.resolution = size

    @staticmethod
    def contrast(image):
        img_gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
        ret = np.var(img_gray.ravel())
        return ret

    def get_image(self):
        rawCapture = PiRGBArray(self.camera)
        # use_video_port: 高速化されるが質が落ちる
        self.camera.capture(rawCapture, format="bgr", use_video_port=True)
        image = rawCapture.array
        rawCapture.truncate(0)
        return image

    def calculate_camera_val(self):
        image = self.get_image()
        return self.contrast(image)

    def terminate(self):
        if self._preview:
            self.camera.stop_preview()
        self.camera.close()
        if self._display:
            print("END")

    @staticmethod
    def is_valid_focus_val(val):
        return 0 <= val and val <= 1023

```

これを

```python
def main():
    """
    sample code
    """
    arducam = Arducam()
    arducam.set_resolution((640, 480))
    arducam.autofocus()
    arducam.capture("test.jpg")
    arducam.terminate()

if __name__ == "__main__":
    main()
```
このような形で使ってあげると無事オートフォーカスができると思います．

### おわりに
ノイズ処理や探索の効率化など色々まだできそうなので是非やってみてください