Line Scan CameraからFrame IDを取得する方法

## はじめに

OMRON SETNECカメラのStAPiとOpenCVを利用した制御の進め方を説明します。

![line-sensor.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/022df349-2466-18f9-742b-a5ad48eba421.jpeg)
## Line Scan CameraからFrame IDを取得する方法

この記事では、Pythonを使用してLine Scan CameraからFrame IDを取得する方法について解説します。具体的には、`stapipy`ライブラリを用いたコールバック関数の設定と、取得した画像データの表示方法を説明します。このサンプルコードを通じて、Line Scan Cameraの操作と、画像データの管理方法を学びましょう。

### 使用するライブラリのインポート

まずは必要なライブラリをインポートします。`cv2`はOpenCV、`threading`はスレッド処理、`numpy`は数値計算、`stapipy`はカメラ操作に使用します。

```python
import cv2
import threading
import numpy as np
import stapipy as st
```

### 画像の表示サイズを調整するためのスケールファクター

画像の表示サイズを調整するために、スケールファクターを設定します。これは表示する画像を縮小するために使用されます。

```python
DISPLAY_RESIZE_FACTOR = 0.3
```

### コールバック関数を含むクラスの定義

ここでは、データストリームから取得した画像データを処理するためのコールバック関数を含むクラスを定義します。このクラスは、画像データとFrame IDを保持し、必要に応じてそれらを提供します。

```python
class CMyCallback:
    def __init__(self, max_frames):
        self._image = None
        self._frame_id = None
        self._lock = threading.Lock()
        self._frame_count = 0
        self._max_frames = max_frames

    @property
    def image(self):
        duplicate = None
        self._lock.acquire()
        if self._image is not None:
            duplicate = self._image.copy()
        self._lock.release()
        return duplicate

    @property
    def frame_id(self):
        fid = None
        self._lock.acquire()
        if self._frame_id is not None:
            fid = self._frame_id
        self._lock.release()
        return fid

    def datastream_callback(self, handle=None, context=None):
        st_datastream = handle.module
        if st_datastream:
            with st_datastream.retrieve_buffer() as st_buffer:
                if st_buffer.info.is_image_present:
                    st_image = st_buffer.get_image()
                    print("BlockID={0} Size={1} x {2} First Byte={3}".format(
                          st_buffer.info.frame_id,
                          st_image.width, st_image.height,
                          st_image.get_image_data()[0]))
                    data = st_image.get_image_data()
                    nparr = np.frombuffer(data, np.uint8)
                    nparr = nparr.reshape(st_image.height, st_image.width)
                    nparr = cv2.resize(nparr, None, fx=DISPLAY_RESIZE_FACTOR, fy=DISPLAY_RESIZE_FACTOR)
                    self._lock.acquire()
                    self._image = nparr
                    self._frame_id = st_buffer.info.frame_id
                    self._lock.release()
                    self._frame_count += 1
                    if self._frame_count >= self._max_frames:
                        st_datastream.stop_acquisition()
```

### メイン関数

ここでは、メイン関数内でカメラの初期化、データストリームの設定、コールバック関数の登録を行います。最後に、取得した画像データを表示します。

```python
if __name__ == "__main__":
    max_frames_to_grab = 100
    my_callback = CMyCallback(max_frames=max_frames_to_grab)
    cb_func = my_callback.datastream_callback
    last_frame_id = None
    images = []

    try:
        st.initialize()
        st_system = st.create_system()
        st_device = st_system.create_first_device()
        print('Device=', st_device.info.display_name)
        st_datastream = st_device.create_datastream()
        callback = st_datastream.register_callback(cb_func)
        st_datastream.start_acquisition(max_frames_to_grab)
        st_device.acquisition_start()

        print("To terminate, focus on the OpenCV window and press any key.")
        while st_datastream.is_grabbing:
            output_image = my_callback.image
            frame_id = my_callback.frame_id
            if output_image is not None:
                cv2.imshow('image', output_image)
                if frame_id is not None and frame_id != last_frame_id:
                    print(f'Frame ID: {frame_id}')
                    last_frame_id = frame_id
                    images.append(output_image)
            key_input = cv2.waitKey(1)
            if key_input != -1:
                break

        st_device.acquisition_stop()
        st_datastream.stop_acquisition()

        if images:
            stacked_images = np.vstack(images)
            filename = f'captured_images_frame_{last_frame_id}.png'
            cv2.imwrite(filename, stacked_images)
            print(f'Saved images to {filename}')

    except Exception as exception:
        print(exception)
```

### 解説

1. **初期化と接続**:
   - `st.initialize()`でStApiを初期化し、`st.create_system()`でシステムオブジェクトを作成します。
   - `st.create_first_device()`で最初に検出されたデバイスに接続します。

2. **データストリームとコールバックの設定**:
   - `st_device.create_datastream()`でデータストリームオブジェクトを作成します。
   - `st_datastream.register_callback(cb_func)`でコールバック関数をデータストリームに登録します。

3. **画像取得と表示**:
   - `st_datastream.start_acquisition(max_frames_to_grab)`でホスト側の画像取得を開始し、`st_device.acquisition_start()`でカメラ側の画像取得を開始します。
   - ループ内でコールバック関数から取得した画像とフレームIDを表示し、任意のキーが押されたら画像取得を終了します。

### 結果

```
Device= FS-B4KU7GES-F(22LB459)
To terminate, focus on the OpenCV window and press any key.
BlockID=1 Size=4096 x 1254 First Byte=7
Frame ID: 1
BlockID=2 Size=4096 x 4096 First Byte=4
Frame ID: 2
BlockID=3 Size=4096 x 4096 First Byte=4
Frame ID: 3
BlockID=4 Size=4096 x 4096 First Byte=4
Frame ID: 4
BlockID=5 Size=4096 x 4096 First Byte=4
Frame ID: 5
BlockID=6 Size=4096 x 4096 First Byte=4
Frame ID: 6
BlockID=7 Size=4096 x 567 First Byte=4
Saved images to captured_images_frame_6.png

```

### まとめ

このコードでは、Line Scan Cameraから取得した画像データとフレームIDをコールバック関数を通じて処理し、OpenCVを用いて表示しています。このサンプルを基に、自分のプロジェクトに応じたカスタマイズを行うことで、より高度な画像処理を実現することができます。ぜひ試してみてください。

# 関連資料
https://qiita.com/kotai2003/items/19d840553fc6d69ec17d

# 更新資料
コードをrefactoringしました。反応性が良くなります。

```python
import cv2
import threading
import numpy as np
import stapipy as st


class CMyCallback:

    def __init__(self,  display_resize_factor=1):
        self._image = None
        self._frame_id = None
        self._lock = threading.Lock()


        self._display_resize_factor = display_resize_factor  # DISPLAY_RESIZE_FACTORを引数で指定

    @property
    def image(self):
        with self._lock:
            if self._image is not None:
                return self._image.copy()
        return None

    @property
    def frame_id(self):
        with self._lock:
            return self._frame_id

    def datastream_callback(self, handle=None, context=None):

        st_datastream = handle.module
        if st_datastream:
            with st_datastream.retrieve_buffer() as st_buffer:
                # 取得したデータに画像データが含まれているか確認
                if st_buffer.info.is_image_present:
                    # 画像オブジェクトを作成
                    st_image = st_buffer.get_image()

                    # 取得した画像データの情報を表示
                    print("BlockID={0} Size={1} x {2} First Byte={3}".format(
                        st_buffer.info.frame_id,
                        st_image.width, st_image.height,
                        st_image.get_image_data()[0]))

                    # 画像データを取得
                    data = st_image.get_image_data()

                    # データをNumPy配列に変換
                    nparr = np.frombuffer(data, np.uint8)

                    # 画像を表示用に処理
                    nparr = nparr.reshape(st_image.height, st_image.width)

                    # インスタンス変数を用いてリサイズ
                    nparr = cv2.resize(nparr, None,
                                       fx=self._display_resize_factor,
                                       fy=self._display_resize_factor)

                    with self._lock:
                        self._image = nparr
                        self._frame_id = st_buffer.info.frame_id




if __name__ == "__main__":

    display_resize_factor = 1 # 任意の倍率を設定可能
    my_callback = CMyCallback(display_resize_factor=display_resize_factor)

    cb_func = my_callback.datastream_callback
    last_frame_id = None  # 前回のフレームIDを保存する変数

    try:
        # StApiを初期化
        st.initialize()

        # システムオブジェクトを作成してデバイスをスキャンし接続
        st_system = st.create_system()

        # 最初に検出されたデバイスに接続
        st_device = st_system.create_first_device()

        # デバイスの名前を表示
        print('Device=', st_device.info.display_name)

        # 画像ストリームデータを扱うデータストリームオブジェクトを作成
        st_datastream = st_device.create_datastream()

        # コールバックをデータストリームに登録
        callback = st_datastream.register_callback(cb_func)

        # ホスト側の画像取得を開始
        st_datastream.start_acquisition()

        # カメラ側の画像取得を開始
        st_device.acquisition_start()

        print("To terminate, focus on the OpenCV window and press any key.")
        while st_datastream.is_grabbing:
            output_image = my_callback.image
            frame_id = my_callback.frame_id
            if output_image is not None:
                # cv2.imshow('image', output_image)
                if frame_id is not None and frame_id != last_frame_id:
                    print(f'Frame ID: {frame_id}')
                    last_frame_id = frame_id  # 現在のフレームIDを更新
            key_input = cv2.waitKey(1)
            if key_input != -1:
                break

        # カメラ側の画像取得を停止
        st_device.acquisition_stop()

        # ホスト側の画像取得を停止
        st_datastream.stop_acquisition()

    except Exception as exception:
        print(exception)


```