OMRON SENTECカメラをPythonとOpenCVで制御する方法(3)

前回記事の続きです。

https://qiita.com/kotai2003/items/e64087031a2aa6357be4


### `CMyCallback` クラスの詳細解説

`CMyCallback` クラスは、カメラから取得した画像データを処理し、スレッドセーフな方法で画像データを保持するために設計されています。このクラスは、データストリームからのコールバックを処理するためのコールバック関数を含んでいます。

#### クラスの構造

```python
class CMyCallback:
    """
    Class that contains a callback function.
    """
```

このクラスは、コールバック関数を含むクラスであることを示しています。

#### 初期化 (`__init__`)

```python
    def __init__(self):
        self._image = None
        self._lock = threading.Lock()
```

- `self._image`: 取得した画像データを保持するための変数です。初期値は `None` に設定されています。
- `self._lock`: スレッドセーフなアクセスを保証するためのロックオブジェクトです。これにより、マルチスレッド環境でのデータ競合を防ぎます。

#### プロパティ (`image`)

```python
    @property
    def image(self):
        """Property: return PyIStImage of the grabbed image."""
        duplicate = None
        self._lock.acquire()
        if self._image is not None:
            duplicate = self._image.copy()
        self._lock.release()
        return duplicate
```

- `@property`: このデコレータにより、`image` メソッドをプロパティとしてアクセスできるようになります。
- `self._lock.acquire()`: ロックを取得し、他のスレッドが `self._image` にアクセスするのを防ぎます。
- `self._image.copy()`: 画像データのコピーを作成し、オリジナルデータが他のスレッドによって変更されるのを防ぎます。
- `self._lock.release()`: ロックを解放し、他のスレッドが `self._image` にアクセスできるようにします。
- `return duplicate`: コピーした画像データを返します。

#### コールバック関数 (`datastream_callback`)

```python
    def datastream_callback(self, handle=None, context=None):
        """
        Callback to handle events from DataStream.

        :param handle: handle that trigger the callback.
        :param context: user data passed on during callback registration.
        """
```

- `handle`: コールバックをトリガーするハンドル。
- `context`: コールバック登録時に渡されたユーザーデータ。

##### コールバック処理の流れ

1. **データストリームの取得**

```python
        st_datastream = handle.module
```

`handle.module` を使ってデータストリームオブジェクトを取得します。

2. **バッファの取得**

```python
        if st_datastream:
            with st_datastream.retrieve_buffer() as st_buffer:
```

データストリームからバッファを取得します。`with` 文を使うことで、バッファの自動解放を保証します。

3. **画像データの確認と取得**

```python
                if st_buffer.info.is_image_present:
                    st_image = st_buffer.get_image()
```

取得したバッファが画像データを含んでいるか確認し、画像オブジェクトを取得します。

4. **ピクセルフォーマットの確認**

```python
                    pixel_format = st_image.pixel_format
                    pixel_format_info = st.get_pixel_format_info(pixel_format)

                    if not(pixel_format_info.is_mono or pixel_format_info.is_bayer):
                        return
```

ピクセルフォーマットを取得し、それがモノクロまたはベイヤー形式であるかを確認します。それ以外の場合は処理を終了します。

5. **画像データの取得**

```python
                    data = st_image.get_image_data()
```

画像の生データを取得します。

6. **ピクセル値のスケーリング**

```python
                    if pixel_format_info.each_component_total_bit_count > 8:
                        nparr = np.frombuffer(data, np.uint16)
                        division = pow(2, pixel_format_info.each_component_valid_bit_count - 8)
                        nparr = (nparr / division).astype('uint8')
                    else:
                        nparr = np.frombuffer(data, np.uint8)
```

各ピクセルコンポーネントが8ビットを超える場合、スケーリングを行います。スケーリングしない場合はそのままのデータを使用します。

7. **画像データの整形**

```python
                    nparr = nparr.reshape(st_image.height, st_image.width, 1)
```

画像データを高さと幅に基づいて再整形します。

8. **ベイヤー変換**

```python
                    if pixel_format_info.is_bayer:
                        bayer_type = pixel_format_info.get_pixel_color_filter()
                        if bayer_type == st.EStPixelColorFilter.BayerRG:
                            nparr = cv2.cvtColor(nparr, cv2.COLOR_BAYER_RG2RGB)
                        elif bayer_type == st.EStPixelColorFilter.BayerGR:
                            nparr = cv2.cvtColor(nparr, cv2.COLOR_BAYER_GR2RGB)
                        elif bayer_type == st.EStPixelColorFilter.BayerGB:
                            nparr = cv2.cvtColor(nparr, cv2.COLOR_BAYER_GB2RGB)
                        elif bayer_type == st.EStPixelColorFilter.BayerBG:
                            nparr = cv2.cvtColor(nparr, cv2.COLOR_BAYER_BG2RGB)
```

ベイヤー形式の画像データをRGB形式に変換します。

9. **画像データのリサイズと保存**

```python
                    nparr = cv2.resize(nparr, None, fx=DISPLAY_RESIZE_FACTOR, fy=DISPLAY_RESIZE_FACTOR)
                    self._lock.acquire()
                    self._image = nparr
                    self._lock.release()
```

画像データをリサイズし、`self._image` に保存します。この時、スレッドセーフを保証するためにロックを取得してから画像データを保存し、保存後にロックを解放します。

### まとめ

`CMyCallback` クラスは、カメラから取得した画像データを安全に処理し保存するための重要なクラスです。スレッドセーフな方法で画像データを保持するためにロック機構を使用し、データストリームからのコールバックを適切に処理します。このクラスの設計により、リアルタイムで画像を処理し表示することが可能になります。

## 参考資料
https://qiita.com/kotai2003/items/5c14a3ba58d5f6ccd127