genicam / harvesters

Image Acquisition Library for GenICam-based Machine Vision System
Apache License 2.0
500 stars 86 forks source link

Corrupted buffer when acess to the buffer is not fast enough (related to #450) #452

Open jeffwitz opened 2 months ago

jeffwitz commented 2 months ago

Describe the Issue I make a simple class that allows one to init a cam, open it, the get image from it and finally close it when needed. You can fond the code here :

To Reproduce Steps to reproduce the behavior: Launch this code with a camera that bugs.

Sample Code I can show a piece of code that demonstrates the reported phenomenon:

You can change the : sleep_bug if it is over 0.01, then it start to bug for me.

If yes, please provide a sample code:

from harvesters.core import Harvester
import numpy as np
import cv2
import time

class GenicamHarvesterCamera():
  def __init__(self):
    self.h = Harvester()
    self.h.add_file('/opt/mvIMPACT_Acquire/lib/x86_64/mvGenTLProducer.cti')
    self.h.update()
  def open(self):
    self.ia = self.h.create()
    self.ia.start()

  def get_image(self):
    with self.ia.fetch() as buffer:
      component = buffer.payload.components[0]
      width = component.width
      height = component.height
      num_comp = component.num_components_per_pixel
      data_format = component.data_format
      img = np.frombuffer(
        component.data, np.uint8).reshape((int(height), int(width)))
      if data_format == 'BayerRG8':
        img_data = cv2.cvtColor(img, cv2.COLOR_BAYER_BG2BGR)
      img_data = img_data.copy()
      return time.time(),img_data

  def close(self):
    self.ia.stop()
    self.ia.destroy()
    self.h.reset()

if __name__=='__main__':
  cam = GenicamHarvesterCamera()
  cam.open()
  cv2.namedWindow('frame', cv2.WINDOW_NORMAL)
  sleep_bug = 0.02
  while(True):
    # do something with the image
    time.sleep(sleep_bug)
    cv2.imshow('frame', cam.get_image()[1])
    if cv2.waitKey(1) & 0xFF == ord('q'):
        cam.close()
        cv2.destroyAllWindows()
        break

If applicable, please paste the actual output (its whole traceback, etc) here: There is no errors, just corrupted buffers

Expected Behavior no corrupted buffers, as everything is done with with Configuration Computer Python 3.11.2 [GCC 12.2.0] harvesters : 1.4.2 OS: Debian GNU/Linux 12 (bookworm) x86_64 Kernel: 6.6.13+bpo-rt-amd64 CPU: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics (16) @ 5.132GHz GPU: AMD ATI c3:00.0 Phoenix1 GenTL Producer: /mvIMPACT_Acquire/lib/x86_64/mvGenTLProducer.cti Camera

{'access_status': 1,
 'display_name': 'The Imaging Source Europe GmbH VID199E_PID9481_48910488', 
'id_': 'VID199E_PID9481_48910488', 'model': 'DFK 37AUX250',
 'parent': <genicam.gentl.Interface; proxy of <Swig Object of type 'std::shared_ptr< GenTLCpp::TLInterface > *' at 0x7fa203665800> >, 
'serial_number': '48910488', 'tl_type': 'U3V', 
'user_defined_name': 'DFK 37AUX250',
 'vendor': 'The Imaging Source Europe GmbH',
 'version': 'IMX250_C.MX/2396/1070 USB3mx-IMX-GS/6'
}

Reproducibility

This phenomenon can be stably reproduced:

If applicable, please provide your observation about the reproducibility. Does not affect the other gige cam I have.

Actions You Have Taken Explain it to you.

If it doesn't work, we will launch a thread that will implement is own buffer management in order work safely, it is not the thing we want to to, but a lot of time without success has passed ...

Additional context

jeffwitz commented 2 months ago

Dear all,

I'm not sure you'll have time to deal with this problem in the Harvester and I still think it's a major problem for people who want to use Harvester to do image processing which can be a bit long between buffer acquisitions, I've made an example with a simplistic class that solves the problem, that you can find here :

import numpy as np
import time
from typing import Optional,Tuple, List, Dict, Any
import cv2
from harvesters.core import Harvester
from threading import Thread, RLock

class GenicamHarvesterCamera():
    def __init__(self) -> None:
        self.camera = None
        self.h = Harvester()
        self.h.add_file('/opt/mvIMPACT_Acquire/lib/x86_64/mvGenTLProducer.cti')
        self.h.update()
        self.num_image = 0
        self.channels = '1'
        self._frame_grabber = None
        self._lock = RLock()
        self._frame = None
        self._stop = False

    def open(self) -> None:
        self.ia = self.h.create()
        self.ia.start()
        self._frame_grabber = Thread(target=self._grab_frame)
        self._stop = False
        self._frame_grabber.start()

    def _grab_frame(self) -> None:
        while not self._stop:
            with self.ia.fetch() as buffer:
                component = buffer.payload.components[0]
                # Extraire les données nécessaires de component
                frame_data = {
                    'data': np.frombuffer(component.data, dtype=np.uint8).copy(),
                    'width': component.width,
                    'height': component.height,
                    'data_format': component.data_format
                }
            with self._lock:
                # Stocker les données extraites de manière thread-safe
                self._frame = frame_data

    def get_image(self) -> Tuple[Dict[str, Any], np.ndarray]:
        with self._lock:
            # Utiliser les données stockées pour créer l'image
            frame_data = self._frame
            img = frame_data['data'].reshape((frame_data['height'], frame_data['width']))
            img_data = self._process_image_data(img, frame_data['data_format'])

        metadata = {
            't(s)': time.time(),
            'ImageUniqueID': self.num_image,
        }
        self.num_image += 1
        return metadata, img_data

    def _process_image_data(self, img, data_format) -> np.ndarray:
        # Adapter cette méthode pour traiter les données d'image en fonction du format
        if data_format == 'BayerRG8':
            img_data = cv2.cvtColor(img, cv2.COLOR_BAYER_BG2BGR)
            if self.channels == '1':
                img_data = cv2.cvtColor(img_data, cv2.COLOR_BGR2GRAY)
            elif self.channels == '3':
                img_data = img_data.copy()
        return img_data

    def close(self):
        self._stop = True
        if self._frame_grabber is not None:
            self._frame_grabber.join()
        self.ia.stop()
        self.ia.destroy()
        self.h.reset()

if __name__=='__main__':
  cam = GenicamHarvesterCamera()
  cam.channels = '1'
  cam.open()
  cv2.namedWindow('frame', cv2.WINDOW_NORMAL)
  sleep_bug = 0.1
  while(True):
    # do something with the image
    time.sleep(sleep_bug) # you can change it with the Tread it never bugs anymore
    cv2.imshow('frame', cam.get_image()[1])
    if cv2.waitKey(1) & 0xFF == ord('q'):
        cam.close()
        cv2.destroyAllWindows()
        break

This remains a workaround that I find unsatisfactory, and for this reason I hope, if you agree, that the issue will remain open.

If someone finds this thread, then they'll have a solution until this problem is dealt with in the library itself. Regards

eli-osherovich commented 4 weeks ago

@jeffwitz Can you try to increase the number of buffers in the image acquirer? Something like this:

ia = h.create()
ia.num_buffers=10