【开源】基于Gige的GVCP协议与mjpg-streamer的物联网AI智能相机

项目背景:

        本人一直在寻找一种可以不知道具体设备信息的情况下,可以跨网快速发现设备,修改IP和远程控制的网络协议。比较了WS-Discovery,onvif,gvcp几种协议后,发现GigeVision的gvcp协议还是最简洁高效的,毕竟所有工业相机都在用,既然有这么好的协议,所以就全网搜索了一下,除了发现几篇比较好的介绍文章,竟然没有一个可以用的代码,所以就花了一周时间自己码了一个,不足之处请大家指正。

方案简介:

        网口工业相机的基本方案是采用Gige-Vision的标准,采用gvcp协议来发现、配置和控制相机,采用gvsp协议来传输未压缩的图像数据,一般需要千兆网速。我的嵌入式设备运行ubuntu core系统,只有百兆网速,所以图像传输采用mjpg-streamer,可以支持uvc和mipi多种摄像头。我只要再实现gvcp的协议就可以实现工业相机的大部分功能,岂不乐哉。

 

 硬件组成图

    硬件采用100万全局曝光的OV9281作为图像采集芯片,主板采用H3的4核A7核心板,具有网口/WIFI/USB/GPIO等接口,大小40x40mm。 已测试通过的开源硬件链接为 https://item.taobao.com/item.htm?id=652142910541

   

 

  64位升级板硬件已经发布,采用200万彩色摄像头作为图像采集芯片,主板采用4核A53处理器,内置Python和OpenCV,算力更强,支持深度学习推理,具有网口/WIFI/USB/GPIO等接口,大小40x40mm。 已测试通过的开源硬件链接为 https://item.taobao.com/item.htm?id=684106700771

 软件架构示意图

        本项目实现了一种基于GVCP协议和mjpeg-steamer的网口工业相机。使用GigeVision中的gvcp协议来实现对相机的发现,修改IP和远程控制功能,使用mjpeg-steamer来传输图像数据.配合PC端的OpenMVS软件使用,可以方便的查找相机、管理相机、获取图像,保存图片等功能。

GvcpServer的实现


        在GvcpServer中,我主要实现了常用的几种协议

#define GVCP_DISCOVERY_CMD	2
#define GVCP_DISCOVERY_ACK	3
#define GVCP_FORCEIP_CMD	4
#define GVCP_FORCEIP_ACK	5
#define GVCP_READREG_CMD	0x80
#define GVCP_READREG_ACK	0x81
#define GVCP_WRITEREG_CMD	0x82
#define GVCP_WRITEREG_ACK	0x83
#define GVCP_READMEM_CMD	0x84
#define GVCP_READMEM_ACK	0x85

gvcp的发现命令代码如下

int gvcp_cmd_discover(int iFd)
{
	//char rgMessage[128] = "I am sending message to you!";
	//int iFd;
	int iSendbytes;
	
	struct sockaddr_in Addr;
	int bNeedClose = 0;
	if (iFd < 0)
	{
		if ((iFd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
		{
			printf("socket fail\n");
			return -1;
		}
		bNeedClose = 1;
	}
	int iOptval = 1;
#ifdef _WINDOWS_
	if (setsockopt(iFd, SOL_SOCKET, SO_REUSEADDR, (CHAR*)&iOptval, sizeof(int)) < 0)
	{
		printf("setsockopt SO_REUSEADDR failed!");
	}
	BOOL	bBroadcast = true;
	if (setsockopt(iFd, SOL_SOCKET, SO_BROADCAST, (char*)&bBroadcast, sizeof(BOOL)) < 0)
	{
		printf("setsockopt SO_BROADCAST failed!");
	}
#else
	if (setsockopt(iFd, SOL_SOCKET, SO_BROADCAST | SO_REUSEADDR, &iOptval, sizeof(int)) < 0)
	{
		printf("setsockopt failed!");
	}
#endif
	memset(&Addr, 0, sizeof(struct sockaddr_in));
	Addr.sin_family = AF_INET;
	Addr.sin_addr.s_addr = inet_addr("255.255.255.255");
	Addr.sin_port = htons(3956);

	static struct gvcp_discover_cmd cmd_msg;
	memset(&cmd_msg, 0, sizeof(struct gvcp_discover_ack));
	cmd_msg.header.cMsgKeyCode = 0x42;
	cmd_msg.header.cFlag=0x11;//0x11 allow broadcast ack;ack required
	cmd_msg.header.wCmd= htons(GVCP_DISCOVERY_CMD);//discovery_cmd=2;FORCEIP_CMD = 4;READREG_CMD=0x80
	cmd_msg.header.wLen = htons(0);//payload length
	cmd_msg.header.wReqID = htons(1);// request id = 1;READREG id=12345

	char* rgMessage = (char*)&cmd_msg;
	uint32 dwMsgLen = sizeof(struct gvcp_discover_cmd);
	//while (1)
	{
		if ((iSendbytes = sendto(iFd, rgMessage, dwMsgLen, 0, (struct sockaddr*)&Addr, sizeof(struct sockaddr))) == -1)
		{
			printf("sendto fail, errno=%d,%s\n", errno, strerror(errno));
			return -1;
		}
		printf("gvcp_cmd_discover=%s, rgMessageLen=%d,iSendbytes=%d\n", rgMessage, dwMsgLen, iSendbytes);
		//sleep(1);
	}
	if (bNeedClose > 0)
	{
#ifdef _WINDOWS_
		closesocket(iFd);
#else
		close(iFd);
#endif
	}

	return 0;
}

gvcp的发现应答命令如下:

int gvcp_ack_discover(int iFd,char* szIp,char* szMask,char* szGateway, uint16 wReqID,uint32 dwPort,uint8* pMac)
{
    //char rgMessage[128] = "I am sending message to you!";
    //int iFd;
    int iSendbytes;
    int iOptval = 1;
    struct sockaddr_in Addr;
	int bNeedClose=0;
	if(iFd<0)
	{
		if ((iFd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
		{
		    printf("socket fail\n");
		    return -1;
		}
		bNeedClose=1;
	}
    if (setsockopt(iFd, SOL_SOCKET, SO_BROADCAST | SO_REUSEADDR, (char*)&iOptval, sizeof(int)) < 0)
    {
        printf("setsockopt failed!");
    }
    memset(&Addr, 0, sizeof(struct sockaddr_in));
    Addr.sin_family = AF_INET;
    Addr.sin_addr.s_addr = inet_addr("255.255.255.255");
    Addr.sin_port = htons(dwPort);
	
	struct gvcp_discover_ack ack_msg;
	memset(&ack_msg, 0, sizeof(struct gvcp_discover_ack));
	ack_msg.header.wStatus=htons(0);
	ack_msg.header.wAck=htons(GVCP_DISCOVERY_ACK);
	ack_msg.header.wLen=htons(sizeof(struct gvcp_ack_payload));
	ack_msg.header.wReqID=htons(1);
	ack_msg.payload.dwSpecVer=htonl(0x010002);;
	ack_msg.payload.dwDevMode=htonl(1);
	//uint8 MyMac[6]={0xc4,0x2f,0x90,0xf1,0x71,0x3e};
	memcpy(&ack_msg.payload.Mac[2],m_LocalMacAddr,6);
	ack_msg.payload.dwSupIpSet=htonl(0x80000007);
	ack_msg.payload.dwCurIpSet=htonl(0x00005);
	//uint8 unused1[12];
	*((uint32*)&ack_msg.payload.CurIP[12])=inet_addr(m_szLocalIp);//last 4 byte
	*((uint32*)&ack_msg.payload.SubMask[12])=inet_addr(m_szLocalMask);//last 4 byte
	*((uint32*)&ack_msg.payload.Gateway[12])=inet_addr(m_szLocalGateway);//last 4 byte
	strcpy(ack_msg.payload.szFacName,"GEV");//first
	strcpy(ack_msg.payload.szModelName,"MV-CA010-GM");//first
	strcpy(ack_msg.payload.szDevVer,"V2.8.6 180210 143913");
	strcpy(ack_msg.payload.szFacInfo,"GEV");
	strcpy(ack_msg.payload.szSerial,"00C31976084");
	strcpy(ack_msg.payload.szUserName,"");
	char* rgMessage=(char*)&ack_msg;
	uint32 dwMsgLen = sizeof(struct gvcp_discover_ack);
    //while (1)
    {
        if ((iSendbytes = sendto(iFd, rgMessage, dwMsgLen, 0, (struct sockaddr*)&Addr, sizeof(struct sockaddr))) == -1)
        {
            printf("sendto fail, errno=%d,%s\n", errno,strerror(errno));
            return -1;
        }
        printf("gvcp_ack_discover=%s, rgMessageLen=%d,iSendbytes=%d\n", rgMessage, dwMsgLen, iSendbytes);
        //sleep(1);
    }
	if(bNeedClose>0)
	{
#ifdef _WINDOWS_
		closesocket(iFd);
#else
		close(iFd);
#endif
	}

    return 0;
}

其他完整代码的安装步骤如下:

在ubuntu系统下,切换到su权限

git clone https://github.com/BigSensor/GvcpServer
cd GvcpServer
make
./GvcpServer

mjpg-streamer代码的实现

        mjpg-streamer的使用网上有很多介绍了,我不在赘述,有些板子里已经内置了mjpg-streamer的代码,直接运行就可,如果还没有的朋友,我付一个下载地址

git clone https://github.com/BigSensor/mjpg-streamer
cd mjpg-streamer
make
./start

PC端管理软件OpenMVS的使用

        本软件暂时叫OpenMVS,实现了一种工业相机的PC管理控制与播放软件,实现了GVCP的Client端和mjpeg-steamer的客户端。使用GigeVision中的gvcp协议来实现对相机的发现,修改IP和远程控制功能,使用http协议来读取并解码mjpeg-steamer传输图像数据。可以方便的查找相机、管理相机、获取图像,保存图片等功能。

        目前只实现了windows端的功能,运行时只需要安装vc运行库即可。安装方式

git clone https://github.com/BigSensor/OpenMVS

软件已经实现了查询所有网卡下的gige设备,预览mjpg-streamer中的图片,保存图片等功能

 后记:

        本文的代码和程序已经上传到github中,大家可以下载、测试、批评、指正,有各种建议和意见请留言。

  • 7
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
以下是通过GigE协议接收GigE相机图像数据的示例代码,使用了GigE Vision SDK: ```c++ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <GevApi.h> #include <SapX11Util.h> #include <X11/Xlib.h> #include <X11/Xutil.h> #define MAX_NETIF 8 #define MAX_CAMERAS_PER_NETIF 32 #define MAX_CAMERAS (MAX_NETIF * MAX_CAMERAS_PER_NETIF) #define NUM_BUF 4 #define TIMEOUT 1000 int main(int argc, char* argv[]) { // Initialize the GigE Vision API if (GevApiInitialize() != GEVLIB_OK) { printf("Error: GevApiInitialize() failed!\n"); exit(EXIT_FAILURE); } // Get all available network interfaces GEV_DEVICE_INTERFACE pNetIF[MAX_NETIF]; uint32_t numInterfaces = 0; if (GevGetInterfaces(pNetIF, MAX_NETIF, &numInterfaces) != GEVLIB_OK) { printf("Error: GevGetInterfaces() failed!\n"); exit(EXIT_FAILURE); } // Get all available cameras on the network GEV_CAMERA pCamera[MAX_CAMERAS]; uint32_t numCameras = 0; for (uint32_t i = 0; i < numInterfaces; i++) { if (GevGetCameraList(pNetIF[i].szInterfaceName, GEV_PROTOCOL_FORCEIP, pCamera + numCameras, MAX_CAMERAS_PER_NETIF, &numCameras) != GEVLIB_OK) { printf("Error: GevGetCameraList() failed on interface %s!\n", pNetIF[i].szInterfaceName); exit(EXIT_FAILURE); } } // Open a connection to the first camera found GEV_CAMERA_HANDLE hCamera = NULL; if (numCameras > 0) { if (GevOpenCamera(&pCamera[0], GEV_EXCLUSIVE_MODE, &hCamera) != GEVLIB_OK) { printf("Error: GevOpenCamera() failed!\n"); exit(EXIT_FAILURE); } } else { printf("Error: no camera found on the network!\n"); exit(EXIT_FAILURE); } // Start the acquisition engine if (GevStartImageTransfer(hCamera, NUM_BUF) != GEVLIB_OK) { printf("Error: GevStartImageTransfer() failed!\n"); exit(EXIT_FAILURE); } // Allocate buffers for incoming images uint32_t payloadSize = 0; if (GevGetPayloadSize(hCamera, &payloadSize) != GEVLIB_OK) { printf("Error: GevGetPayloadSize() failed!\n"); exit(EXIT_FAILURE); } void* pBuffer[NUM_BUF]; for (uint32_t i = 0; i < NUM_BUF; i++) { pBuffer[i] = malloc(payloadSize); if (pBuffer[i] == NULL) { printf("Error: failed to allocate buffer!\n"); exit(EXIT_FAILURE); } if (GevRegisterImageBuffer(hCamera, pBuffer[i], payloadSize, i) != GEVLIB_OK) { printf("Error: GevRegisterImageBuffer() failed!\n"); exit(EXIT_FAILURE); } } // Create a window to display the image Display* pDisplay = XOpenDisplay(NULL); if (pDisplay == NULL) { printf("Error: XOpenDisplay() failed!\n"); exit(EXIT_FAILURE); } Window window = XCreateSimpleWindow(pDisplay, DefaultRootWindow(pDisplay), 0, 0, 640, 480, 0, 0, 0); XSelectInput(pDisplay, window, ExposureMask | KeyPressMask); XMapWindow(pDisplay, window); while (true) { // Wait for an image to be received GEV_BUFFER_OBJECT pBufferObj = { 0 }; if (GevWaitForNextImage(hCamera, &pBufferObj, TIMEOUT) == GEVLIB_OK) { // Get the image data and display it char* pData = (char*)pBuffer[pBufferObj.context]; int width = pBufferObj.w; int height = pBufferObj.h; XImage* pImage = XCreateImage(pDisplay, DefaultVisual(pDisplay, 0), DefaultDepth(pDisplay, 0), ZPixmap, 0, pData, width, height, 32, 0); XEvent event; while (XPending(pDisplay)) { XNextEvent(pDisplay, &event); if (event.type == Expose) { XPutImage(pDisplay, window, DefaultGC(pDisplay, 0), pImage, 0, 0, 0, 0, width, height); } } } } // Cleanup for (uint32_t i = 0; i < NUM_BUF; i++) { GevFreeImageBuffer(hCamera, pBuffer[i]); free(pBuffer[i]); } XCloseDisplay(pDisplay); GevStopImageTransfer(hCamera); GevCloseCamera(&hCamera); GevApiUninitialize(); return EXIT_SUCCESS; } ``` 注意:这只是一个示例代码,需要根据具体的相机型号和网络环境进行适当的修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值