Obsługa kamer przemysłowych Ximea z poziomu IronPythona

Kamery przemysłowe Ximea obsługiwane są przez XiAPI. API zaimplementowane jest w bibliotekach dla platformy .NET. W tym artykule zaprezentuję wykorzystanie IronPythona do obsługi kamery xiQ za pomocą tego API. IronPython to implementacja Pythona na platformie .NET i z poziomów skryptów IronPythona można wykorzystywać biblioteki dostępne dla tej platformy.

Mając kamerkę (w moim przypadku xiQ MQ013MG-E2) należy zainstalować software package zawierające sterowniki i biblioteki. Całość zainstalowana zostanie w katalogu "Ximea" na głównym dysku (C:). W podkatalogu "libraries" znajdziemy pliki xiApi.NET.dll i m3api.dll. Oczywiście potrzebny będzie też IronPython. Do pustego katalogu skopiowałem wspomniane pliki DLL i plik ipy.exe IronPythona by mieć prostsze wykonywanie pisanych skryptów.

Najprostszy skrypt zapisujący jedną klatkę wygląda tak:
#-*- coding: utf-8 -*-
import clr
import System

clr.AddReference("xiApi.NET.dll")
clr.AddReference("System.Drawing")

import System.Drawing
from System.Drawing.Imaging import PixelFormat

from xiApi.NET import *

cam = xiCam()
cam.OpenDevice(0)
cam.SetParam(PRM.BUFFER_POLICY, BUFF_POLICY.SAFE)
cam.SetParam(PRM.EXPOSURE, 9000)
cam.SetParam(PRM.GAIN, 10.0)
cam.SetParam(PRM.IMAGE_DATA_FORMAT, IMG_FORMAT.MONO8)

fileobj = System.Drawing.Bitmap(1280, 1024, PixelFormat.Format8bppIndexed)
cam.StartAcquisition()
cam.GetImage(fileobj, 1000)
fileobj.Save("a.bmp")
cam.StopAcquisition()

cam.CloseDevice()

Na początku ładujemy bibliotekę XiAPI oraz System.Drawing potrzebne do obsługi plików BMP, z których korzysta kamera (w 8-bitowym trybie obrazowania). Następnie inicjalizuję kamerę poprzez klasę xiCam. Ustawiam czas ekspozycji (9 ms), gain w dB, ustawiam format danych na monochromatyczne 8-bitów (kamera posiada monochromatyczną matrycę) i mogę już rozpoczynać "nagrywanie". GetImage potrzebuje instancję Bitmap o odpowiednich wymiarach i typie. Wszystkie te metody są opisane w dokumentacji. W odróżnieniu od Pythona musimy tutaj używać klas i typów takich jakie zostały przewidziane w xiAPI dla .NET.

Można także pobrać informacje z kamery za pomogą GetParam.

#-*- coding: utf-8 -*-

import clr
import System

clr.AddReference("xiApi.NET.dll")

from xiApi.NET import *

strongBox = clr.Reference[str]()

cam = xiCam()
cam.OpenDevice(0)
cam.GetParam(PRM.DEVICE_NAME, strongBox)
print strongBox
cam.GetParam(PRM.DEVICE_TYPE, strongBox)
print strongBox
cam.GetParam(PRM.WIDTH, strongBox)
print strongBox
cam.GetParam(PRM.HEIGHT, strongBox)
print strongBox
cam.GetParam(PRM.AVAILABLE_BANDWIDTH, strongBox)
print strongBox
cam.GetParam(PRM.CHIP_TEMP, strongBox)
print strongBox
cam.GetParam(PRM.API_VERSION, strongBox)
print strongBox
cam.GetParam(PRM.DRV_VERSION, strongBox)
print strongBox

cam.CloseDevice()

GetParam jako drugi parametr oczekuje instancji klasy System.Runtime.CompilerServices.StrongBox - która to przechowuje wskaźnik do jakiejś wartości.

Zapisywanie jednej klatki jest raczej mało przydatne. Na potrzeby testów kamery napisałem bardziej rozbudowaną klasę zapisującą klatki do podkatalogów:

#-*- coding: utf-8 -*-
import clr
import System

clr.AddReference("xiApi.NET.dll")
clr.AddReference("System.Drawing")

import System.Drawing
from System.Drawing.Imaging import PixelFormat
from System.IO import Directory, Path
from xiApi.NET import *

class XimeaImager(object):
    def __init__(self, exposure_time, gain, frames_count, hdr, folder):
        self.exposure_time = exposure_time
        self.gain = gain
        self.hdr = hdr
        self.frames_count = frames_count
        self.folder = folder
        self._perform()

    def _perform(self):
        self._setup_capture_folder()
        self._setup_device()
        if self.hdr:
            self._set_hdr_exposure()
        else:
            self._set_exposure()
        self._capture_frames()
        self._finish_acquisition()

    def _setup_capture_folder(self):
        current_folder = Directory.GetCurrentDirectory()
        capture_folder = Path.Combine(current_folder, self.folder)
        if not Directory.Exists(capture_folder):
            Directory.CreateDirectory(capture_folder)
        self.capture_folder = capture_folder

    def _setup_device(self):
        self.camera = xiCam()
        self.camera.OpenDevice(0)
        self.camera.SetParam(PRM.BUFFER_POLICY, BUFF_POLICY.SAFE)
        self.camera.SetParam(PRM.IMAGE_DATA_FORMAT, IMG_FORMAT.MONO8)
        self.camera.SetParam(PRM.GAIN, self.gain)

    def _set_exposure(self):
        self.camera.SetParam(PRM.EXPOSURE, self.exposure_time)

    def _set_hdr_exposure(self):
        self.camera.SetParam(PRM.HDR, 1)
        self.camera.SetParam(PRM.HDR_T1, self.exposure_time * 10)
        self.camera.SetParam(PRM.HDR_T2, self.exposure_time * 5)
        self.camera.SetParam(PRM.HDR_T3, self.exposure_time)

    def _capture_frames(self):
        self.camera.StartAcquisition()
        for frame in range(0, self.frames_count):
            bmp = self._get_bmp_frame()
            self.camera.GetImage(bmp, 1000)
            self._save_frame(bmp, frame)
        self.camera.StopAcquisition()

    def _get_bmp_frame(self):
        return System.Drawing.Bitmap(1280, 1024, PixelFormat.Format8bppIndexed)

    def _save_frame(self, bmp, frame_index):
        filename = 'frame%04d.bmp' % frame_index
        filepath = Path.Combine(self.capture_folder, filename)
        bmp.Save(filepath)

    def _finish_acquisition(self):
        self.camera.CloseDevice()

Wystarczy stworzyć instancję klasy, np. XimeaImager(9000, 10, 100, False, 'test') i gotowe. Dodałem też obsługę trybu HDR, które z trzech ekspozycji składa zdjęcie o większym zakresie dynamicznym. Zastosowałem te same stosunki czasów ekspozycji jak dla domyślnych ustawień. Także obsługę katalogów i ścieżek obsługiwane jest przez .NETowskie API ("os" nie istnieje).

Skryptowanie obsługi sprzętu w IronPythonie może być przydatne przy testach, czy szybkim sprawdzaniu różnych ustawień, koncepcji fotograficznych. Zastosowanie IronPythona wymaga mimo wszystko znajomości API platformy .NET.

Co do "fotografowania" za pomocą samej kamery - zapraszam do oddzielnego artykułu.

RkBlog

Elektronika i Python, 22 September 2012

Comment article
Comment article RkBlog main page Search RSS Contact