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

## 初めに

以下のPythonコードは、StApiライブラリを使用してカメラから画像をキャプチャし、OpenCVを使って表示する方法を示しています。オムロンセンテックが提供しているSDKのコードですが、この場を借りて説明を追加します。各部分の説明を含め、できる限りわかりやすく解説します。また、OMRONセンテック GigEカメラ専用です。

![line-sensor.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/c014dd50-d8e3-c282-2a2b-bc4e21dd329b.jpeg)
https://sentech.co.jp/products/GigE/line-sensor.html#cnt2nd


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

# キャプチャする画像の枚数
number_of_images_to_grab = 20

# OpenCVで表示する際の画像のスケール
DISPLAY_RESIZE_FACTOR = 0.3

try:
    # StApiライブラリの初期化
    st.initialize()

    # 最初のカメラをオープン
    st_system = st.create_system()
    st_device = st_system.create_first_device()

    # デバイス情報の取得と表示
    print("デバイス情報:")
    print(f"デバイス: {st_device.info.display_name}")
    print(f"シリアル番号: {st_device.info.serial_number}")
    print(f"ユーザー定義名: {st_device.info.user_defined_name}")

    # データストリームを開く
    st_datastream = st_device.create_datastream()

    # ホスト側の画像取得を開始
    st_datastream.start_acquisition(number_of_images_to_grab)
    # カメラ側の画像取得を開始
    st_device.acquisition_start()

    # 画像データを取得し、ステータスを確認するループ
    while st_datastream.is_grabbing:
        with st_datastream.retrieve_buffer() as st_buffer:
            if st_buffer.info.is_image_present:
                st_image = st_buffer.get_image()
                print("BlockID={0} サイズ={1} x {2} 最初のバイト={3}".format(
                    st_buffer.info.frame_id,
                    st_image.width, st_image.height,
                    st_image.get_image_data()[0]))

                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):
                    continue

                data = st_image.get_image_data()

                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)

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

                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)

                nparr = cv2.resize(nparr, None, fx=DISPLAY_RESIZE_FACTOR, fy=DISPLAY_RESIZE_FACTOR)
                cv2.imshow('image', nparr)
                cv2.waitKey(1)
            else:
                print("画像データが存在しません。")

    # 画像取得の停止
    st_device.acquisition_stop()
    st_datastream.stop_acquisition()

except Exception as e:
    print("エラーが発生しました:", e)

finally:
    # StApiライブラリの終了処理
    st.terminate()

```

## コードの解説

### 1. ライブラリーのインポート

```python
import stapipy as st
import numpy as np
import cv2
```
* stapipy：カメラとやり取りするためのライブラリ。
* numpy：配列操作のためのライブラリ。
* cv2：画像表示のためのOpenCVライブラリ。

### 2. 定数の設定

```python
number_of_images_to_grab = 20
DISPLAY_RESIZE_FACTOR = 0.3
```
* number_of_images_to_grab：キャプチャする画像の枚数。
* DISPLAY_RESIZE_FACTOR：画像を表示する際のスケール。

### 3. 初期化

```python
st.initialize()
st_system = st.create_system()
st_device = st_system.create_first_device()
```

* st.initialize()：StApiライブラリの初期化。
* st.create_system()：システムの作成。
* st_system.create_first_device()：最初のカメラデバイスを開く。

### 4. デバイス情報の取得
```python
print("デバイス情報:")
print(f"デバイス: {st_device.info.display_name}")
print(f"シリアル番号: {st_device.info.serial_number}")
print(f"ユーザー定義名: {st_device.info.user_defined_name}")
```
* カメラのデバイス情報を取得して表示。

### 5. データストリームの設定と開始:

```python
st_datastream = st_device.create_datastream()
st_datastream.start_acquisition(number_of_images_to_grab)
st_device.acquisition_start()
```
* データストリームの作成。
* ホスト側およびカメラ側での画像取得を開始。

### 6. 画像取得ループ:

```python
while st_datastream.is_grabbing:
    with st_datastream.retrieve_buffer() as st_buffer:
        if st_buffer.info.is_image_present:
            ...
            cv2.imshow('image', nparr)
            cv2.waitKey(1)
        else:
            print("画像データが存在しません。")
```
* データストリームが画像を取得している間、バッファを取得し画像データを処理。
* 画像データが存在する場合、情報を表示し、必要に応じてフォーマット変換やスケーリングを行い、画像を表示。
* 実行結果　（何も映っていませんが、動いています。）
![スクリーンショット 2024-05-23 093715.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/7dd1a108-7a6c-de6e-3406-6dd1d1b0aceb.png)

### 7. エラーハンドリング:
```python
except Exception as e:
    print("エラーが発生しました:", e)
```
エラーが発生した場合、エラーメッセージを表示。

### 8.終了処理

```python
finally:
    st.terminate()
```
* StApiライブラリの終了処理を行い、リソースを解放。

このコードを使うことで、StApiライブラリを利用したカメラ画像の取得とOpenCVによる表示が簡単に実現できます。是非、自分のプロジェクトに応用してみてください。

## 6.1 画像取得ループの詳細説明
特に「画像データを取得し、ステータスを確認するループ」の部分について詳しく説明します。
```python
# 画像データを取得し、ステータスを確認するループ
while st_datastream.is_grabbing:
    with st_datastream.retrieve_buffer() as st_buffer:
        if st_buffer.info.is_image_present:
            st_image = st_buffer.get_image()
            print("BlockID={0} サイズ={1} x {2} 最初のバイト={3}".format(
                st_buffer.info.frame_id,
                st_image.width, st_image.height,
                st_image.get_image_data()[0]))

            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):
                continue

            data = st_image.get_image_data()

            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)

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

            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)

            nparr = cv2.resize(nparr, None, fx=DISPLAY_RESIZE_FACTOR, fy=DISPLAY_RESIZE_FACTOR)
            cv2.imshow('image', nparr)
            cv2.waitKey(1)
        else:
            print("画像データが存在しません。")

```
### 1. ループの開始:
```python
while st_datastream.is_grabbing:
```
* st_datastream.is_grabbing が True である間、ループを継続します。このフラグは、画像取得が進行中であることを示します。
### 2.バッファの取得:
```python
with st_datastream.retrieve_buffer() as st_buffer:
```
* retrieve_buffer() メソッドを使用して、現在のバッファを取得します。このメソッドは、バッファが利用可能になるまでブロックします。
* with 文を使用することで、バッファの取得と解放を確実に行います。

### 3. 画像データの有無を確認:

```python
st_image = st_buffer.get_image()
print("BlockID={0} サイズ={1} x {2} 最初のバイト={3}".format(
    st_buffer.info.frame_id,
    st_image.width, st_image.height,
    st_image.get_image_data()[0]))
```
画像データを取得し、画像の幅、高さ、最初のバイトなどの情報を表示します。

### 5. ピクセルフォーマットの確認:
```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):
    continue

```
画像のピクセルフォーマットを取得し、それがモノクロまたはベイヤーフォーマットであるかを確認します。それ以外のフォーマットは処理しません。

###6. 画像データの取得と変換:
```python
data = st_image.get_image_data()

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)

```
画像データを取得し、ビット深度に応じてNumPy配列に変換します。8ビットより多い場合はスケーリングします。

###7.画像データのリシェイプ:
```python
nparr = nparr.reshape(st_image.height, st_image.width, 1)
```
NumPy配列を画像の高さと幅にリシェイプします。
###
```python
```

###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)
```
ベイヤーフォーマットの場合、適切なカラー変換を行います。


### 9.画像のリサイズと表示:
```python
nparr = cv2.resize(nparr, None, fx=DISPLAY_RESIZE_FACTOR, fy=DISPLAY_RESIZE_FACTOR)
cv2.imshow('image', nparr)
cv2.waitKey(1)
```
画像をリサイズし、OpenCVを使用して表示します。cv2.waitKey(1) により、画像が表示され続けます。`

### 10.画像データがない場合の処理:
```python
else:
    print("画像データが存在しません。")

```
* バッファに画像データが含まれていない場合の処理です。


## 参考資料
https://sentech.co.jp/products/GigE/line-sensor.html#cnt2nd