¿Cómo guardar los datos de dos cámaras pero no influir en su velocidad de adquisición de imágenes?

Estoy usando una cámara multiespectral para recostackr datos. Uno es infrarrojo cercano y otro es colorido. No dos cámaras, pero una cámara puede obtener dos tipos diferentes de imágenes al mismo tiempo. Hay algunas funciones de API que podría usar como J_Image_OpenStream. Dos partes de los códigos básicos se muestran de la siguiente manera. Uno se usa para abrir dos flujos (en realidad están en una muestra y tengo que usarlos, pero no estoy muy claro con sus significados) y establecer los dos caminos de guardar archivos de avi y comenzar la adquisición.

// Open stream retval0 = J_Image_OpenStream(m_hCam[0], 0, reinterpret_cast(this), reinterpret_cast(&COpenCVSample1Dlg::StreamCBFunc0), &m_hThread[0], (ViewSize0.cx*ViewSize0.cy*bpp0)/8); if (retval0 != J_ST_SUCCESS) { AfxMessageBox(CString("Could not open stream0!"), MB_OK | MB_ICONEXCLAMATION); return; } TRACE("Opening stream0 succeeded\n"); retval1 = J_Image_OpenStream(m_hCam[1], 0, reinterpret_cast(this), reinterpret_cast(&COpenCVSample1Dlg::StreamCBFunc1), &m_hThread[1], (ViewSize1.cx*ViewSize1.cy*bpp1)/8); if (retval1 != J_ST_SUCCESS) { AfxMessageBox(CString("Could not open stream1!"), MB_OK | MB_ICONEXCLAMATION); return; } TRACE("Opening stream1 succeeded\n"); const char *filename0 = "C:\\Users\\shenyang\\Desktop\\test0.avi"; const char *filename1 = "C:\\Users\\shenyang\\Desktop\\test1.avi"; int fps = 10; //frame per second int codec = -1;//choose the compression method writer0 = cvCreateVideoWriter(filename0, codec, fps, CvSize(1296,966), 1); writer1 = cvCreateVideoWriter(filename1, codec, fps, CvSize(1296,964), 1); // Start Acquision retval0 = J_Camera_ExecuteCommand(m_hCam[0], NODE_NAME_ACQSTART); retval1 = J_Camera_ExecuteCommand(m_hCam[1], NODE_NAME_ACQSTART); // Create two OpenCV named Windows used for displaying "BGR" and "INFRARED" images cvNamedWindow("BGR"); cvNamedWindow("INFRARED"); 

Otra es las dos funciones de flujo, se ven muy similares.

 void COpenCVSample1Dlg::StreamCBFunc0(J_tIMAGE_INFO * pAqImageInfo) { if (m_pImg0 == NULL) { // Create the Image: // We assume this is a 8-bit monochrome image in this sample m_pImg0 = cvCreateImage(cvSize(pAqImageInfo->iSizeX, pAqImageInfo->iSizeY), IPL_DEPTH_8U, 1); } // Copy the data from the Acquisition engine image buffer into the OpenCV Image obejct memcpy(m_pImg0->imageData, pAqImageInfo->pImageBuffer, m_pImg0->imageSize); // Display in the "BGR" window cvShowImage("INFRARED", m_pImg0); frame0 = m_pImg0; cvWriteFrame(writer0, frame0); } void COpenCVSample1Dlg::StreamCBFunc1(J_tIMAGE_INFO * pAqImageInfo) { if (m_pImg1 == NULL) { // Create the Image: // We assume this is a 8-bit monochrome image in this sample m_pImg1 = cvCreateImage(cvSize(pAqImageInfo->iSizeX, pAqImageInfo->iSizeY), IPL_DEPTH_8U, 1); } // Copy the data from the Acquisition engine image buffer into the OpenCV Image obejct memcpy(m_pImg1->imageData, pAqImageInfo->pImageBuffer, m_pImg1->imageSize); // Display in the "BGR" window cvShowImage("BGR", m_pImg1); frame1 = m_pImg1; cvWriteFrame(writer1, frame1); } 

La pregunta es si no guardo los archivos avi, como

 /*writer0 = cvCreateVideoWriter(filename0, codec, fps, CvSize(1296,966), 1); writer1 = cvCreateVideoWriter(filename1, codec, fps, CvSize(1296,964), 1);*/ //cvWriteFrame(writer0, frame0); //cvWriteFrame(writer0, frame0); 

En las dos ventanas de visualización, las imágenes se capturan de forma similar, lo que significa que son sincrónicas. Pero si tengo que escribir datos en los archivos avi, debido al diferente tamaño de dos tipos de imágenes y su gran tamaño, resulta que esta influencia de la cámara adquiere la velocidad y las imágenes capturadas no son sincrónicas. Pero no pude crear un buffer tan grande para almacenar toda la información en la memoria y el dispositivo de E / S es bastante lento. ¿Que debería hacer? Muchas muchas gracias.

algunas variables de clase son:

  public: FACTORY_HANDLE m_hFactory; // Factory Handle CAM_HANDLE m_hCam[MAX_CAMERAS]; // Camera Handles THRD_HANDLE m_hThread[MAX_CAMERAS]; // Stream handles char m_sCameraId[MAX_CAMERAS][J_CAMERA_ID_SIZE]; // Camera IDs IplImage *m_pImg0 = NULL; // OpenCV Images IplImage *m_pImg1 = NULL; // OpenCV Images CvVideoWriter* writer0; IplImage *frame0; CvVideoWriter* writer1; IplImage *frame1; BOOL OpenFactoryAndCamera(); void CloseFactoryAndCamera(); void StreamCBFunc0(J_tIMAGE_INFO * pAqImageInfo); void StreamCBFunc1(J_tIMAGE_INFO * pAqImageInfo); void InitializeControls(); void EnableControls(BOOL bIsCameraReady, BOOL bIsImageAcquiring); 

El enfoque correcto para registrar el video sin interrupciones de cuadros es aislar las dos tareas (adquisición de marcos y serialización de cuadros) de modo que no se influyan entre sí (específicamente para que las fluctuaciones en la serialización no le quiten tiempo a la captura de los fotogtwigs , que tiene que suceder sin retrasos para evitar la pérdida de fotogtwigs).

Esto se puede lograr delegando la serialización (encoding de los cuadros y escribiéndolos en un archivo de video) para separar los hilos, y usando algún tipo de cola sincronizada para alimentar los datos a los hilos de trabajo.

A continuación se muestra un ejemplo simple que muestra cómo se podría hacer esto. Como solo tengo una cámara y no del tipo que tienes, simplemente usaré una cámara web y duplicaré los marcos, pero el principio general también se aplica a tu escenario.


Código de muestra

Al principio tenemos algunos incluye:

 #include  #include  #include  #include  #include  #include  #include  // ============================================================================ using std::chrono::high_resolution_clock; using std::chrono::duration_cast; using std::chrono::microseconds; // ============================================================================ 

Cola sincronizada

El primer paso es definir nuestra cola sincronizada, que usaremos para comunicarnos con los hilos de trabajo que escriben el video.

Las funciones principales que necesitamos es la capacidad de:

  • Insertar nuevas imágenes en la cola
  • Pop imágenes de la cola, esperando cuando está vacío.
  • Posibilidad de cancelar todas las apariciones pendientes, cuando hayamos terminado.

Usamos std::queue para contener las instancias cv::Mat , y std::mutex para proporcionar sincronización. Una std::condition_variable se utiliza para notificar al consumidor cuando la imagen se ha insertado en la cola (o el indicador de cancelación establecido), y se usa un indicador booleano simple para notificar la cancelación.

Finalmente, utilizamos la struct cancelled vacía struct cancelled como una excepción lanzada desde pop() , por lo que podemos terminar de manera limpia al trabajador cancelando la cola.

 // ============================================================================ class frame_queue { public: struct cancelled {}; public: frame_queue(); void push(cv::Mat const& image); cv::Mat pop(); void cancel(); private: std::queue queue_; std::mutex mutex_; std::condition_variable cond_; bool cancelled_; }; // ---------------------------------------------------------------------------- frame_queue::frame_queue() : cancelled_(false) { } // ---------------------------------------------------------------------------- void frame_queue::cancel() { std::unique_lock mlock(mutex_); cancelled_ = true; cond_.notify_all(); } // ---------------------------------------------------------------------------- void frame_queue::push(cv::Mat const& image) { std::unique_lock mlock(mutex_); queue_.push(image); cond_.notify_one(); } // ---------------------------------------------------------------------------- cv::Mat frame_queue::pop() { std::unique_lock mlock(mutex_); while (queue_.empty()) { if (cancelled_) { throw cancelled(); } cond_.wait(mlock); if (cancelled_) { throw cancelled(); } } cv::Mat image(queue_.front()); queue_.pop(); return image; } // ============================================================================ 

Trabajador de almacenamiento

El siguiente paso es definir un simple storage_worker , que será responsable de tomar los fotogtwigs de la cola sincronizada y codificarlos en un archivo de video hasta que la cola se haya cancelado.

Agregué el tiempo simple, así que tenemos alguna idea sobre cuánto tiempo se dedica a codificar los fotogtwigs, así como el inicio de sesión simple en la consola, por lo que tenemos una idea de lo que está sucediendo en el progtwig.

 // ============================================================================ class storage_worker { public: storage_worker(frame_queue& queue , int32_t id , std::string const& file_name , int32_t fourcc , double fps , cv::Size frame_size , bool is_color = true); void run(); double total_time_ms() const { return total_time_ / 1000.0; } private: frame_queue& queue_; int32_t id_; std::string file_name_; int32_t fourcc_; double fps_; cv::Size frame_size_; bool is_color_; double total_time_; }; // ---------------------------------------------------------------------------- storage_worker::storage_worker(frame_queue& queue , int32_t id , std::string const& file_name , int32_t fourcc , double fps , cv::Size frame_size , bool is_color) : queue_(queue) , id_(id) , file_name_(file_name) , fourcc_(fourcc) , fps_(fps) , frame_size_(frame_size) , is_color_(is_color) , total_time_(0.0) { } // ---------------------------------------------------------------------------- void storage_worker::run() { cv::VideoWriter writer(file_name_, fourcc_, fps_, frame_size_, is_color_); try { int32_t frame_count(0); for (;;) { cv::Mat image(queue_.pop()); if (!image.empty()) { high_resolution_clock::time_point t1(high_resolution_clock::now()); ++frame_count; writer.write(image); high_resolution_clock::time_point t2(high_resolution_clock::now()); double dt_us(static_cast(duration_cast(t2 - t1).count())); total_time_ += dt_us; std::cout << "Worker " << id_ << " stored image #" << frame_count << " in " << (dt_us / 1000.0) << " ms" << std::endl; } } } catch (frame_queue::cancelled& /*e*/) { // Nothing more to process, we're done std::cout << "Queue " << id_ << " cancelled, worker finished." << std::endl; } } // ============================================================================ 

Tratamiento

Finalmente, podemos poner todo esto junto.

Comenzamos inicializando y configurando nuestra fuente de video. Luego creamos dos instancias frame_queue , una para cada flujo de imágenes. Seguimos esto creando dos instancias de storage_worker , una para cada cola. Para mantener las cosas interesantes, he configurado un códec diferente para cada uno.

El siguiente paso es crear e iniciar subprocesos de trabajo, que ejecutarán el método run() de cada storage_worker . Con nuestros consumidores listos, podemos comenzar a capturar fotogtwigs de la cámara y alimentarlos a las instancias frame_queue . Como mencioné anteriormente, tengo solo una fuente única, así que inserto copias del mismo marco en ambas colas.

NB: Necesito usar el método clone() de cv::Mat para hacer una copia profunda, de lo contrario estaría insertando referencias al buffer único que utiliza OpenCV VideoCapture por razones de rendimiento. Eso significaría que los hilos de trabajo estarían obteniendo referencias a esta única imagen, y no habría sincronización para acceder a este búfer de imagen compartido. Debe asegurarse de que esto no suceda también en su escenario.

Una vez que hayamos leído la cantidad adecuada de fotogtwigs (puede implementar cualquier otro tipo de condición stop que desee), cancelaremos las colas de espera de trabajo y esperamos a que se completen los subprocesos de trabajo.

Finalmente, escribimos algunas estadísticas sobre el tiempo requerido para las diferentes tareas.

 // ============================================================================ int main() { // The video source -- for me this is a webcam, you use your specific camera API instead // I only have one camera, so I will just duplicate the frames to simulate your scenario cv::VideoCapture capture(0); // Let's make it decent sized, since my camera defaults to 640x480 capture.set(CV_CAP_PROP_FRAME_WIDTH, 1920); capture.set(CV_CAP_PROP_FRAME_HEIGHT, 1080); capture.set(CV_CAP_PROP_FPS, 20.0); // And fetch the actual values, so we can create our video correctly int32_t frame_width(static_cast(capture.get(CV_CAP_PROP_FRAME_WIDTH))); int32_t frame_height(static_cast(capture.get(CV_CAP_PROP_FRAME_HEIGHT))); double video_fps(std::max(10.0, capture.get(CV_CAP_PROP_FPS))); // Some default in case it's 0 std::cout << "Capturing images (" << frame_width << "x" << frame_height << ") at " << video_fps << " FPS." << std::endl; // The synchronized queues, one per video source/storage worker pair std::vector queue(2); // Let's create our storage workers -- let's have two, to simulate your scenario // and to keep it interesting, have each one write a different format std::vector  storage; storage.emplace_back(std::ref(queue[0]), 0 , std::string("foo_0.avi") , CV_FOURCC('I', 'Y', 'U', 'V') , video_fps , cv::Size(frame_width, frame_height) , true); storage.emplace_back(std::ref(queue[1]), 1 , std::string("foo_1.avi") , CV_FOURCC('D', 'I', 'V', 'X') , video_fps , cv::Size(frame_width, frame_height) , true); // And start the worker threads for each storage worker std::vector storage_thread; for (auto& s : storage) { storage_thread.emplace_back(&storage_worker::run, &s); } // Now the main capture loop int32_t const MAX_FRAME_COUNT(10); double total_read_time(0.0); int32_t frame_count(0); for (; frame_count < MAX_FRAME_COUNT; ++frame_count) { high_resolution_clock::time_point t1(high_resolution_clock::now()); // Try to read a frame cv::Mat image; if (!capture.read(image)) { std::cerr << "Failed to capture image.\n"; break; } // Insert a copy into all queues for (auto& q : queue) { q.push(image.clone()); } high_resolution_clock::time_point t2(high_resolution_clock::now()); double dt_us(static_cast(duration_cast(t2 - t1).count())); total_read_time += dt_us; std::cout << "Captured image #" << frame_count << " in " << (dt_us / 1000.0) << " ms" << std::endl; } // We're done reading, cancel all the queues for (auto& q : queue) { q.cancel(); } // And join all the worker threads, waiting for them to finish for (auto& st : storage_thread) { st.join(); } if (frame_count == 0) { std::cerr << "No frames captured.\n"; return -1; } // Report the timings total_read_time /= 1000.0; double total_write_time_a(storage[0].total_time_ms()); double total_write_time_b(storage[1].total_time_ms()); std::cout << "Completed processing " << frame_count << " images:\n" << " average capture time = " << (total_read_time / frame_count) << " ms\n" << " average write time A = " << (total_write_time_a / frame_count) << " ms\n" << " average write time B = " << (total_write_time_b / frame_count) << " ms\n"; return 0; } // ============================================================================ 

Salida de consola

Al ejecutar esta pequeña muestra, obtenemos la siguiente salida de registro en la consola, así como los dos archivos de video en el disco.

NB: Como esto realmente codificaba mucho más rápido que la captura, he agregado un poco de espera en storage_worker para mostrar mejor la separación.

 Capturing images (1920x1080) at 20 FPS. Captured image #0 in 111.009 ms Captured image #1 in 67.066 ms Worker 0 stored image #1 in 94.087 ms Captured image #2 in 62.059 ms Worker 1 stored image #1 in 193.186 ms Captured image #3 in 60.059 ms Worker 0 stored image #2 in 100.097 ms Captured image #4 in 78.075 ms Worker 0 stored image #3 in 87.085 ms Captured image #5 in 62.061 ms Worker 0 stored image #4 in 95.092 ms Worker 1 stored image #2 in 193.187 ms Captured image #6 in 75.074 ms Worker 0 stored image #5 in 95.093 ms Captured image #7 in 63.061 ms Captured image #8 in 64.061 ms Worker 0 stored image #6 in 102.098 ms Worker 1 stored image #3 in 201.195 ms Captured image #9 in 76.074 ms Worker 0 stored image #7 in 90.089 ms Worker 0 stored image #8 in 91.087 ms Worker 1 stored image #4 in 185.18 ms Worker 0 stored image #9 in 82.08 ms Worker 0 stored image #10 in 94.092 ms Queue 0 cancelled, worker finished. Worker 1 stored image #5 in 179.174 ms Worker 1 stored image #6 in 106.102 ms Worker 1 stored image #7 in 105.104 ms Worker 1 stored image #8 in 103.101 ms Worker 1 stored image #9 in 104.102 ms Worker 1 stored image #10 in 104.1 ms Queue 1 cancelled, worker finished. Completed processing 10 images: average capture time = 71.8599 ms average write time A = 93.09 ms average write time B = 147.443 ms average write time B = 176.673 ms 

Posibles mejoras

Actualmente no hay protección contra la cola demasiado llena en la situación cuando la serialización simplemente no puede seguir el ritmo de la cámara genera nuevas imágenes. Establezca un límite superior para el tamaño de la cola, y controle en el productor antes de empujar el marco. Tendrá que decidir cómo exactamente quiere manejar esta situación.