开源相机管理库Aravis例程学习(四)——multiple-acquisition-signal

目录

简介

本文针对官方例程中的:02-multiple-acquisition-signal做简单的讲解。并简单介绍其中调用的g_main_loop_newg_main_loop_rung_main_loop_quitg_signal_connectarv_stream_set_emit_signals

aravis版本:0.8.31

操作系统:ubuntu-20.04

gcc版本:9.4.0

例程代码

这段代码使用Aravis的API,控制相机连续采集,并通过GLib的事件循环机制和GObject的信号系统异步地获取10个图像,主要操作步骤如下:

  • 连接相机
  • 设置采集模式为连续采集
  • 创建流对象,并向流对象的buffer池中添加buffer
  • 设置流对象信号回调函数,并使能流对象信号发射
  • 开始采集
  • 启动事件循环
  • 获取10张图像后关闭事件循环
  • 关闭流对象信号发射,释放资源

连续采集multiple-acquisition-main-thread不同的是,本例中使用GMainLoop(GLib的事件循环)来处理异步事件,图像获取过程是异步进行的。

cpp 复制代码
/* SPDX-License-Identifier:Unlicense */

/* Aravis header */
#include <arv.h>
/* Standard headers */
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include "LogManager.h"

typedef struct {
	GMainLoop *main_loop;
	guint32 counter;
} AppData;

void new_buffer_cb (ArvStream *stream, void *user_data)
{
	ArvBuffer *buffer;
	AppData *app_data = static_cast<AppData*>(user_data);

	buffer = arv_stream_pop_buffer (stream);
	PAW_INFO("Acquired"<<arv_buffer_get_image_width(buffer)<<"x"<<arv_buffer_get_image_height(buffer)<< " buffer");

	arv_stream_push_buffer (stream, buffer);

	app_data->counter++;
	if (app_data->counter == 10)
		g_main_loop_quit (app_data->main_loop);
}

int main (int argc, char **argv)
{
	ArvCamera *camera;
	AppData app_data;
	GError *error = NULL;

	app_data.main_loop = g_main_loop_new (NULL, FALSE);
	app_data.counter = 0;

	//连接相机
	camera = arv_camera_new (NULL, &error);

	if (ARV_IS_CAMERA (camera)) {
		ArvStream *stream = NULL;

		printf ("Found camera '%s'\n", arv_camera_get_model_name (camera, NULL));
		//设置采集模式
		arv_camera_set_acquisition_mode (camera, ARV_ACQUISITION_MODE_CONTINUOUS, &error);
		//创建流对象
		if (error == NULL)
			stream = arv_camera_create_stream (camera, NULL, NULL, &error);

		if (ARV_IS_STREAM (stream)) {
			int i;
			size_t payload;

			//获取有效负载大小
			payload = arv_camera_get_payload (camera, &error);
			if (error == NULL) {
				//设置流对象的缓冲区数量
				for (i = 0; i < 5; i++)
					arv_stream_push_buffer (stream, arv_buffer_new (payload, NULL));
			}

			//设置流对象信号回调函数
			g_signal_connect (stream, "new-buffer", G_CALLBACK (new_buffer_cb), &app_data);
			//设置流对象发射信号
			//当流对象接收到新的缓冲区时,发射new-buffer信号
			arv_stream_set_emit_signals (stream, TRUE);

			//开始采集
			if (error == NULL)
				arv_camera_start_acquisition (camera, &error);
			
			//启动主循环
			PAW_INFO("start main loop");
			if (error == NULL)
				g_main_loop_run (app_data.main_loop);
			PAW_INFO("start main loop end");

			if (error == NULL)
				//停止采集
				arv_camera_stop_acquisition (camera, &error);

			arv_stream_set_emit_signals (stream, FALSE);
			g_clear_object (&stream);
		}
		
		g_clear_object (&camera);
	}
	
	g_main_loop_unref (app_data.main_loop);

	if (error != NULL) {
		/* En error happened, display the correspdonding message */
		printf ("Error: %s\n", error->message);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

注:PAW_INFO是我自定义的用于打印日志的宏

运行结果:

其中<>之间的是线程号。

函数说明

g_main_loop_new

简介:GLib的API,构造GMainLoop对象

cpp 复制代码
GMainLoop* g_main_loop_new(GMainContext* context, gboolean is_running)

其中:

in\]context:一个GMainContext,如果为NULL,将使用全局默认的main上下文 \[in\]is_running:设置为TRUE表示循环正在运行。这不是很重要,因为只要后面调用g_main_loop_run()就会将其设置为TRUE。 ## g_main_loop_run 简介:GLib的API,运行一个主循环,直到在循环中调用g_main_loop_quit() ```cpp void g_main_loop_run(GMainLoop* loop) ``` ## g_main_loop_quit 简介:GLib的API,停止GMainLoop的运行。任何使用g_main_loop_run()开启的循环都将返回。 ```cpp void g_main_loop_quit(GMainLoop* loop) ``` ## g_signal_connect 简介:GObject的宏,用于将信号处理器连接到特定对象的某个信号上。当一个信号被发出时,处理器将被同步调用。 ```cpp #define g_signal_connect(instance, detailed_signal, c_handler, data) ``` ## arv_stream_set_emit_signals 简介:控制流对象信号发射。默认情况下流对象发射信号是禁用的,因为信号发射在性能上有一定开销而且在某些应用场景下是不需要的。 ```cpp void arv_stream_set_emit_signals(ArvStream* stream, gboolean emit_signals) ``` Available since: 0.2.0 # Q\&A ## 回调函数的同步调用与异步调用 观察程序运行时的日志,可以发现new_buffer_cb的运行并不是在主线程中。 ![](https://file.jishuzhan.net/article/1783429455033667585/b0a33ddfff4a6fff342fa6d0cc92c799.webp) 但是按照`g_signal_connect`的描述,回调函数应该是被同步调用,也就是说`new_buffer_cb`理论上应该在主线程被调用。 后来查看文档发现,在GObject的信号系统中,处理器的调用是同步的。当信号发射时,其关联的所有处理器会都会在发射信号的线程中按照它们被连接的顺序依次执行。 所以正确的应该是:处理器是在信号发射的线程被调用,而不是在处理器被注册时的线程。 在本例中,预定义的信号`new-buffer`的处理器`new_buffer_cb`被绑定在流对象上,这意味着每当流对象有一个新的buffer可用时,这个信号就会被发射,随后`new_buffer_cb`就被调用。而官方文档钟提到,流对象内部是使用一个单独的线程来监听数据的到达,因此信号是在这个单独的线程被发射的,也就是说回调函数也是在这个单独的线程被调用的,而不是在主线程中。 ## 帧丢失问题 官方给出的例程中,先启动的相机采集,然后才开始事件循环。我认为这样的话会存在丢帧的问题,因为在事件循环启动并准备好处理接收到的图像之前,相机可能已经开始发送数据,如果数据流的缓冲不足或处理不及时,新的图像数据可能会覆盖还未处理的旧数据,或者直接被丢弃。 所以我对代码做了一些改动,改变调用顺序为先开启事件循环,然后再启动相机的采集,代码如下: ```cpp /* SPDX-License-Identifier:Unlicense */ /* Aravis header */ #include /* Standard headers */ #include #include #include #include "LogManager.h" typedef struct { GMainLoop *main_loop; guint32 counter; ArvCamera *camera; } AppData; gboolean start_acquisition_cb(gpointer user_data) { AppData *app_data = static_cast(user_data); GError *error = NULL; arv_camera_start_acquisition(app_data->camera, &error); if (error != NULL) { printf("Error: %s\n", error->message); g_main_loop_quit(app_data->main_loop); } //只调用一次 return FALSE; } ... int main (int argc, char **argv) { AppData app_data; GError *error = NULL; app_data.main_loop = g_main_loop_new (NULL, FALSE); app_data.counter = 0; app_data.camera = arv_camera_new (NULL, &error); if (ARV_IS_CAMERA (app_data.camera)) { ArvStream *stream = NULL; printf ("Found camera '%s'\n", arv_camera_get_model_name (app_data.camera, NULL)); arv_camera_set_acquisition_mode (app_data.camera, ARV_ACQUISITION_MODE_CONTINUOUS, &error); if (error == NULL) stream = arv_camera_create_stream (app_data.camera, NULL, NULL, &error); if (ARV_IS_STREAM (stream)) { int i; size_t payload; payload = arv_camera_get_payload (app_data.camera, &error); if (error == NULL) { for (i = 0; i < 5; i++) arv_stream_push_buffer (stream, arv_buffer_new (payload, NULL)); } g_signal_connect (stream, "new-buffer", G_CALLBACK (new_buffer_cb), &app_data); PAW_INFO("emit signals"); arv_stream_set_emit_signals (stream, TRUE); PAW_INFO("emit signals end"); /* if (error == NULL) arv_camera_start_acquisition (camera, &error); */ //在主循环开始后尽快执行一次start_acquisition_cb g_idle_add(start_acquisition_cb, &app_data); PAW_INFO("start main loop"); if (error == NULL) g_main_loop_run (app_data.main_loop); PAW_INFO("start main loop end"); if (error == NULL) arv_camera_stop_acquisition (app_data.camera, &error); arv_stream_set_emit_signals (stream, FALSE); g_clear_object (&stream); } g_clear_object (&app_data.camera); } g_main_loop_unref (app_data.main_loop); if (error != NULL) { printf ("Error: %s\n", error->message); return EXIT_FAILURE; } return EXIT_SUCCESS; } ```

相关推荐
tirvideo11 天前
TI DLP3010光机与相机触发使用指南
3d·工业相机·光机·dlp·光机控制板·投影机
幻想趾于现实24 天前
机器视觉之工业相机讲解
数码相机·工业相机
51camera1 个月前
如何选择合适的工业相机快门种类
工业相机·全局快门·卷帘快门
钱彬 (Qian Bin)3 个月前
一文掌握工业相机选型计算
机器视觉·工业·工业相机·相机选型·焦距·视场
罗迪尼亚的熔岩3 个月前
kuka, fanuc, abb机器人和移动相机的标定
机器人·工业相机·海康威视·标定·visonmaster
51camera4 个月前
在机器视觉检测中为何选择线阵工业相机?
视觉检测·工业相机
“抚琴”的人4 个月前
【机械视觉】C#+VisionPro联合编程———【六、visionPro连接工业相机设备】
c#·工业相机·visionpro·机械视觉
幻想趾于现实4 个月前
机器视觉调试——现场链接相机(解决各种相机链接问题)
数码相机·工业相机
51camera4 个月前
高光谱工业相机+LED光源系统助力材料分类和异物检测、实现高速在线检测
工业相机
CHHC18805 个月前
工业相机视频播放(RTSP)
ffmpeg·推流·工业相机·rtsp