pypylon icon indicating copy to clipboard operation
pypylon copied to clipboard

Frame skipping while recording frames observed intermittently

Open sabariMLIT18 opened this issue 4 years ago • 2 comments

Hi I have a basler machine vision camera acA2040-120uc.

Problem: While recording frames at high speed (100 fps) frame skip issue has been observed. in few videos (1 out of 5 approx).

Hardware setup: basler machine vision camera acA2040-120uc connected to usb3.0 slot directly to system. System also has arduino micro controller connected to another usb slot. All software in python language using pypylon api. USBFS memory increased to 1000 mb. OS is ubuntu 18.04 Code file attached: [MVRecordingModule_v5.txt](https://github.com/basler/pypylon/files/7311272/MVRecordingModule_v5.txt)

Dont know if it because of coding of hardware setup/os Regards Girish

sabariMLIT18 avatar Oct 08 '21 12:10 sabariMLIT18

import os
import xml.etree.ElementTree as ET
import logging
import cv2
import numpy as np
import datetime
import time
from pypylon import pylon
import serial
import queue
import traceback
import threading
from logging import handlers

image_Dict={}
img_number=-1

class SGNImageEventHandler(pylon.ImageEventHandler):
    def OnImageGrabbed(self, camera, grabResult):
        global image_Dict,img_number
        if grabResult.GrabSucceeded() and int(grabResult.ImageNumber)!=img_number:
            img_number=int(grabResult.ImageNumber)
            image_Dict[img_number]=grabResult.Array.copy()
            #print(img_number)



class MVRecordingModule:
    global image_Dict,img_number
    def __init__(self,mv_recording_config_fl):
        try:
                
            self.configRoot=ET.parse(mv_recording_config_fl).getroot()
            #LOGGING parameters
            self.mv_cam_log_file = self.configRoot[0][0].text
            self.log_level = int(self.configRoot[0][1].text)
            self.logger=logging.getLogger("MV_RECORDING")
            self.logger.setLevel(self.log_level)
            logFormat=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
            log_fh=handlers.RotatingFileHandler(self.mv_cam_log_file,maxBytes=1048576,backupCount=5)
            log_fh.setLevel(self.log_level)
            log_fh.setFormatter(logFormat)
            self.logger.addHandler(log_fh)
            self.logger.debug("MV Recording Module initilized")
            
            #Video recording initilizations
            self.TMP_VID_REC_LOC=self.configRoot[1][0].text
            self.SAVE_VID_LOC = self.configRoot[1][1].text
            self.FILESAVEPREFIX = self.configRoot[1][2].text
            dt = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
            self.video_fileName = self.FILESAVEPREFIX+"_"+dt+".avi"        
            self.recording_cutOff=int(self.configRoot[1][3].text)
            self.RECORDING_FPS = int(self.configRoot[1][4].text)
            self.record_start_time=-1
            #MV Camera settings
            self.CAM_EXPOSURE=float(self.configRoot[2][0].text)
            self.CAM_FPS=int(self.configRoot[2][1].text)
            self.init_CAM()
            self.MV_Q={}
            self.img_number=-1
            
            #TRIGGER settings
            self.TRIGGER_FILE_PATH = self.configRoot[3][0].text
            self.TRIGGER_FILE_NAME = self.configRoot[3][1].text
            try:
                self.ser = serial.Serial('/dev/ttyACM0',9600,timeout = 1)
                time.sleep(3)
                self.ser.write(b'0')
            except:
                self.ser = serial.Serial('/dev/ttyACM1',9600,timeout = 1)
                time.sleep(3)
                self.ser.write(b'0')
            self.ARDUINO_STATE="NT"
            self.grab_state=False
            self.img_number_logger="IMG_NUMber.txt"
        except Exception as e:
            self.logger.critical("Error in MvrecordingModule Initilization")
            self.logger.critical(str(e))
            self.logger.critical(str(traceback.format_exc()))
            
    
    def init_CAM(self):
        try:
                
            self.camera=pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateFirstDevice())
            self.camera.Open()
            #self.camera.SensorReadoutMode="Fast"
            self.camera.AcquisitionFrameRateEnable=True
            self.camera.AcquisitionFrameRate=self.CAM_FPS
            self.camera.ExposureTime=self.CAM_EXPOSURE
            self.camera.PixelFormat="BayerRG8"
        except Exception as e:
            self.logger.critical("Error in init_CAM ")
            self.logger.critical(str(e))
            self.logger.critical(str(traceback.format_exc()))      
        
    def grab_CAM(self):
        try:
                
            self.logger.debug("Grab Started")
            self.record_start_time=int(time.time())
            self.grab_state=True
            self.camera.RegisterImageEventHandler(SGNImageEventHandler(),pylon.RegistrationMode_Append, pylon.Cleanup_Delete)        
            self.camera.StartGrabbing(pylon.GrabStrategy_OneByOne, pylon.GrabLoop_ProvidedByInstantCamera)
            while self.grab_state:
                try:
                    if os.path.exists(os.path.join(self.TRIGGER_FILE_PATH,self.TRIGGER_FILE_NAME)) and (abs(int(time.time())-self.record_start_time)<self.recording_cutOff):
                        time.sleep(10)
                    else:
                        self.grab_state=False
                        self.logger.debug("Grabbing stopped, trigger removed or timeout")
                        time.sleep(1)
                        break
                except Exception as e:
                    self.logger.debug("Error in machine vision camera grab:")
                    self.logger.debug(str(e))
                    self.logger.debug(str(traceback.format_exc()))
                    continue
            self.camera.Close()
            self.logger.debug("Grab Ended")
            self.grab_state=False
        except Exception as e:
            self.logger.critical("Error in grab_CAM ")
            self.logger.critical(str(e))
            self.logger.critical(str(traceback.format_exc()))     
        
    def proc_q(self):
        try:
            while img_number==-1:
                pass
            self.logger.debug("Video rec Started")        
            flimgnumber=open(self.img_number_logger,"a+")
            
            frame_ctr=0
            imageList=list(image_Dict.keys())
            imageList.sort()
            img=cv2.cvtColor(image_Dict.get(imageList[0]),cv2.COLOR_BayerBG2RGB) #use with BayerBG format
            frame_width=img.shape[1]
            frame_height=img.shape[0]
            fourcc=cv2.VideoWriter_fourcc('H','2','6','4')
            fps=self.RECORDING_FPS
            dt = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
            self.video_fileName = self.FILESAVEPREFIX+"_"+dt+".avi"
            flimgnumber.write(str(self.video_fileName))
            flimgnumber.write("\n")
            
            video_flName=os.path.join(self.TMP_VID_REC_LOC,self.video_fileName)
            video_writer=cv2.VideoWriter(video_flName,fourcc,fps,(frame_width,frame_height))    
            video_writer.write(img)
            frame_ctr+=1
            while len(list(image_Dict.keys()))>0 or self.grab_state:
                try:
                    if len(list(image_Dict.keys()))>0:
                        img_kys=list(image_Dict.keys())
                        img_kys.sort()
                        img_tmp_ky=img_kys[0]
                        flimgnumber.write(str(img_tmp_ky)+",")
                        img=cv2.cvtColor(image_Dict.get(img_tmp_ky),cv2.COLOR_BayerRG2RGB) # use with Bayer format
                        video_writer.write(img)
                        flimgnumber.flush()
                        frame_ctr+=1
                        image_Dict.pop(img_tmp_ky)
                        if self.grab_state==True:
                            time.sleep(0.001)
                        
                    else:
                        time.sleep(0.001)
                        continue
                except Exception as e:
                    self.logger.debug("Error inside proc_q- video rec loop:")
                    self.logger.debug(str(e))
                    self.logger.debug(str(traceback.format_exc()))       
                    continue
            flimgnumber.write("\nEnd of video")
            flimgnumber.write("\n")
            flimgnumber.flush()
            flimgnumber.close()
            video_writer.release()
            self.logger.debug("Rec ended, frames-"+str(frame_ctr))        
        except Exception as e:
            self.logger.critical("Error in proc_q ")
            self.logger.critical(str(e))
            self.logger.critical(str(traceback.format_exc()) )         
    
    def record_vid(self):
        try:
            grab_thread=threading.Thread(target=self.grab_CAM,args=())
            grab_thread.start()
            time.sleep(1)
            q_proc_thread=threading.Thread(target=self.proc_q,args=())
            q_proc_thread.start()
            grab_thread.join()
            self.ser.write(b'0')
            self.logger.debug("Lights off")
            q_proc_thread.join()
            self.logger.debug("Video rec Ended")  
        except Exception as e:
            self.logger.debug("Error in record video")
            self.logger.debug(str(e))
            self.logger.debug(str(traceback.format_exc()))            
            self.ser.write(b'0')
            self.logger.debug("Lights off")
            
            
        
    def main(self):
        try:
            while True:
                if os.path.exists(os.path.join(self.TRIGGER_FILE_PATH,self.TRIGGER_FILE_NAME)):
                    if self.ARDUINO_STATE=="NT":
                        #Start recording
                        train_nm=""
                        with open(os.path.join(self.TRIGGER_FILE_PATH,self.TRIGGER_FILE_NAME)) as fl:
                            train_nm=str(fl.read()).replace(" ","").replace("\n","")
                            fl.close()
                        train_nm=train_nm+".avi"
                        
                        self.ser.write(b'1')
                        self.logger.debug("Lights on")
                        self.ARDUINO_STATE="T"
                        
                        self.record_vid()
                        #move video to proc location
                        #set train_name to video file
                       
                        os.rename(os.path.join(self.TMP_VID_REC_LOC,self.video_fileName),os.path.join(self.SAVE_VID_LOC,train_nm))
                        videoFLmoved="Video File Moved from {fromloc} to {toloc}".format(fromloc=str(os.path.join(self.TMP_VID_REC_LOC,self.video_fileName)),toloc=str(os.path.join(self.SAVE_VID_LOC,train_nm)))
                        self.logger.debug(videoFLmoved)
                        exit(1)
                else:
                    self.ARDUINO_STATE="NT"
                        
        except Exception as e:
            self.logger.debug("Error in Main loop")
            self.logger.debug(str(e))
            self.logger.debug(str(traceback.format_exc()))            
            self.ser.write(b'0')
            self.logger.debug("Lights off")            
            exit(1)
                    
                    
                    

if __name__ == '__main__':
    MVRecordingModuleObj = MVRecordingModule("MV_RECORDING_MODULE_CONFIG.xml") 
    MVRecordingModuleObj.main()

sabariMLIT18 avatar Oct 08 '21 12:10 sabariMLIT18

some first thoughts from looking through your code ( only enabled proper code formatting in your comment )

a) threading: you access global data structures without locking between multiple threads! b) efficiency: H.264 has yuv as input. you configure your camera to output BayerRaw ... convert to RGB which will be internally converted by ffmpeg to yuv before encoding.... if you have to run with BayerRaw because of framerate issues, only convert to yuv

c) for reference also a compact recording implementation in issue https://github.com/basler/pypylon/issues/113#issuecomment-543774545 Basic idea is, that you don't have to use multiple threads. Grabbing is running in background pylon threads and the encoding is also running multithreaded in ffmpeg background.

thiesmoeller avatar Oct 11 '21 21:10 thiesmoeller