进程间通信(内存映射)方式传输CV::Mat图像数据

关于内存映射的进程间通信,我直接一篇博客已经详细介绍过,这里只是再次补充一个传输图像的应用,因为OpenCV中Mat格式较为常用(并且也很方便传输),刚好用到,就做个笔记好了。应用的场景是B进程中的Mat传给A进程,A进程接受并恢复为Mat格式。其它也没什么好说的,有细节为题就参见我之前的博客,那么开始吧。

进程A创建映射空间

HANDLE d_hMutex是一个时间的句柄用于进程同步,d_hFileMapping是内存映射的句柄,LPTSTR d_pMsg是映射内存的地址指针

	d_hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, 640*480*3*20, _T("SVAF_ALG2GUI_DATA"));
	d_hMutex = CreateEvent(nullptr, false, false, _T("SVAF_ALG2GUI_DATA_MUTEX"));
	d_pMsg = (LPTSTR)MapViewOfFile(d_hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
	if (d_pMsg == NULL){
		MessageBox(_T("Create Inter Process Error!"));
		exit(-1);
	}

进程B打开映射空间

这里应该也是可以用OpenEvent的,但是我调的时候OpenEvent在A中总是无法触发,很纠结不知道为什么,算了,就CreateEvent好了,如果你知道原因,请下方留言告知博主,谢谢。

		d_mutex_ = CreateEvent(nullptr, false, false, "SVAF_ALG2GUI_DATA_MUTEX");
		d_fileMapping_ = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, "SVAF_ALG2GUI_DATA");
		d_pMsg_ = (LPTSTR)MapViewOfFile(d_fileMapping_, FILE_MAP_ALL_ACCESS, 0, 0, 0);

进程B发送图像

传送数据有三部分组成,一部分是帧头,另一部分是图像数据,还有一部分是电源数据,当然,我们只需要关注两部分,一个是帧头,定义了有多少图像,每幅图像的大小通道和指针偏移,第二部分是数据,指针转换直接往内存映射区填就可以了。点云部分时博主自己的,可以忽略。

这里强调下,在传输图像数据时,模式cv::Mat的图像数据是continue的,如果你接受到的数据有断行或者其他问题,那么也就是说图像数据在内存中可能不是连续的(需要通过Mat.isContinue()来判断),那么你把它拷贝一遍就连续了(深拷贝)。然后直接把数据的Mat.data指针放上来,拷贝到映射内存区,拷贝完激活一个事件,告诉A进程要接受数据了。

using Bucket = struct{
	char	head[4];
	char	message[10][128];
	int		msgCount;
	int		imgCount;
	int		cols[8];
	int		rows[8];
	int		chns[8];
	int		offs[8];
	int		PointSize[4];
	int		PointChns[4];// xyz(3) or xyzrgb(6)
	int		PointOffs[4];
	int		pclCount;
};

void Circuit::SendData(){

	if (!useMapping_ || !d_pMsg_){
		return;
	}

	LPTSTR p = d_pMsg_;
	char *pBuf = p;
	Bucket *pBucket = (Bucket*)pBuf;
	sprintf(pBucket->head, "pch");
	sprintf(pBucket->message[0], "123");
	pBucket->msgCount = 1;

	int frameCount = 0;
	int pointCount = 0;
	int offset = sizeof(Bucket);
	for (int i = 0; i < disp_.size(); ++i){
		if (disp_[i].isOutput && (!disp_[i].isOutput3DPoint) && (!disp_[i].image.empty()) && frameCount < 8){
			int cols = disp_[i].image.cols;
			int rows = disp_[i].image.rows;
			int chns = disp_[i].image.channels();
			int length = cols * rows * chns;
			memcpy(pBuf + offset, disp_[i].image.data, length);
			pBucket->cols[frameCount] = cols;
			pBucket->rows[frameCount] = rows;
			pBucket->chns[frameCount] = chns;
			pBucket->offs[frameCount] = offset;
			offset += (cols * rows * chns);
			frameCount++;
		}
		if (disp_[i].isOutput && disp_[i].isOutput3DPoint && pointCount < 4){
			int count = disp_[i].point3d.size();
			if (count <= 0){
				continue;
			}
			int chns = (disp_[i].color3d.size() == disp_[i].point3d.size()) ? 6 : 3;
			float *points = (float *)(pBuf + offset);
			if (chns == 3){
				for (int j = 0; j < count; ++j){
					points[j * 3 + 0] = disp_[i].point3d[j].x;
					points[j * 3 + 1] = disp_[i].point3d[j].y;
					points[j * 3 + 2] = disp_[i].point3d[j].z;
				}
			} else{
				for (int j = 0; j < count; ++j){
					points[j * 6 + 0] = disp_[i].point3d[j].x;
					points[j * 6 + 1] = disp_[i].point3d[j].y;
					points[j * 6 + 2] = disp_[i].point3d[j].z;
					points[j * 6 + 3] = disp_[i].color3d[j].r;
					points[j * 6 + 4] = disp_[i].color3d[j].g;
					points[j * 6 + 5] = disp_[i].color3d[j].b;
				}
			}
			memcpy(pBuf + offset, points, count * chns * sizeof(float));
			pBucket->PointSize[pointCount] = count;
			pBucket->PointChns[pointCount] = chns;
			pBucket->PointOffs[pointCount] = offset;
			offset += (count * chns * sizeof(float));
			pointCount++;
		}
	}
	pBucket->imgCount = frameCount;
	pBucket->pclCount = pointCount;
	
	SetEvent(d_mutex_);
}

进程A接受图像

进程A在WaitForSingleObject的地方阻塞等待,在B激活事件后,A就从WaitForSingleObject这里往后执行,然后打开内存映射区域,解析数据包,然后往cv::Mat里填就可以了。

using Bucket = struct{
	char	head[4];
	char	message[10][128];
	int		msgCount;
	int		imgCount;
	int		cols[8];
	int		rows[8];
	int		chns[8];
	int		offs[8];
	int		PointSize[4];
	int		PointChns[4];// xyz(3) or xyzrgb(6)
	int		PointOffs[4];
	int		pclCount;
};

void CRVAFGUIDlg::ReciveDataInterprocess(){

	while (true){
		WaitForSingleObject(d_hMutex, INFINITE);
		m_imgs.clear();
		pointclouds.clear();
		LPTSTR p = d_pMsg;
		Bucket* pBucket = (Bucket*)d_pMsg;
		char *pBuf = (char *)p;
		int offset = sizeof(Bucket);
		int frameCount = pBucket->imgCount;
		int pointCount = pBucket->pclCount;
		for (int i = 0; i < frameCount; ++i){
			int type = (pBucket->chns[i] == 1) ? CV_8UC1 : CV_8UC3;
			cv::Mat img(pBucket->rows[i], pBucket->cols[i], type, pBuf + pBucket->offs[i]);
			m_imgs.push_back(img.clone());
		}
		for (int i = 0; i < pointCount; ++i){
			int pnts = pBucket->PointSize[i];
			int chns = pBucket->PointChns[i];
			float *pPC = (float*)(pBuf + pBucket->PointOffs[i]);
			vector<Pointf> pointcloud;
			pointcloud.resize(pnts);
			if (chns == 3){
				for (int j = 0; j < pnts; ++j){
					pointcloud[j].x = pPC[j * chns];
					pointcloud[j].y = pPC[j * chns + 1];
					pointcloud[j].z = pPC[j * chns + 2];
					pointcloud[j].r = 1;
					pointcloud[j].g = 1;
					pointcloud[j].b = 0.5;
				}
			} else if(chns == 6){
				for (int j = 0; j < pnts; ++j){
					pointcloud[j].x = pPC[j * chns];
					pointcloud[j].y = pPC[j * chns + 1];
					pointcloud[j].z = pPC[j * chns + 2];
					pointcloud[j].r = pPC[j * chns + 3];
					pointcloud[j].g = pPC[j * chns + 4];
					pointcloud[j].b = pPC[j * chns + 5];
				}
			}
			pointclouds.push_back(pointcloud);
		}

		SendMessage(WM_PAINT);
		
		continue;
	}
	return;
}

进程A销毁资源

放在OnDestroy或者析构函数中,在退出时销毁。

	CloseHandle(d_hFileMapping);
	CloseHandle(d_hMutex);

进程B销毁资源

也是一样

		if (!UnmapViewOfFile(d_fileMapping_)){}
		CloseHandle(d_fileMapping_);
		CloseHandle(d_mutex_);

OK,See You Next Chapter!

发表评论