海康工业相机SDK + OpenCV实例(5):

相机双线程读写缓存策略



文章目录

  • 海康工业相机SDK + OpenCV实例(5):
  • 相机双线程读写缓存策略
  • 前言
  • 一、双线程
  • 二、缓存区容量为2的生产消费思想
  • 三、相机双线程读写缓存策略



前言

本文讲解对海康相机的双线程读写缓存的策略,首先这里的双线程指的是,线程1进行相机的图像的调用并写入缓存区,线程2进行缓存区的读取,可以理解为生产者和消费者的关系。
由于线程1读入相机图像是持续不断,每当有图像,会刷新缓存区域,倘若缓存区容量为1,可能会产生生产者进程不断刷新缓存区,而消费者不能第一时间读到的情况,考虑设置缓存区容量为2。


一、双线程

对于缓存区域的写入和读取操作是并发进行的,因此需要设置双线程,代码如下:

#include <thread>
void WriteBuffer();
void ReadBuffer();
int main()
{
	thread cameraThread, serverThread;
    cameraThread = thread(WriteBuffer);
    serverThread = thread(ReadBuffer);
    cameraThread.join();
    serverThread.join();
	return 0;
}

二、缓存区容量为2的生产消费思想

生产者消费者模型是CS的核心课程《操作系统》的重要模型,缓存区作为临界资源,当缓存区容量为1,同一时刻,只能允许一个生产者进行生产或者一个消费者进行消费,对于缓存区的写入和读取操作是互斥的。我们可以设置互斥信号mutex,确保对于使用临界资源的互斥性。

#include <mutex>
mutex mutexBufferA, mutexBufferB;

互斥信号mutex控制对于某一缓存区同一时间只能有一个线程进行读取或者写入操作。

mutexBufferA.lock();
wirteBufferA or readBufferA
mutexBufferA.unlock();

当缓存区满的时候,才能进行读取操作,当缓存区空的时候,才能进行写入操作,需要设置缓存区当前的状态量bufferFull,但状态量的修改与进程或线程的执行顺序相关。例如:若进程A设置bufferFull=false,此时进程B读取bufferFull状态,读入与bufferFull何时写入内存是有关联性的。
考虑设置bufferFull为原子变量atomic,atomic变量类型对于变量的操作是原子操作,可以理解为是一步完成,即读入写回一气呵成。

#include <atomic>
atomic_bool bufferAFull = false;
atomic_bool bufferBFull = false;

鉴于上述阐述,尝试模拟缓存区容量为2的读写策略。

#include <atomic>
#include <thread>
#include <mutex>
#include <windows.h>
using namespace std;
int bufferAData,bufferBData ;
mutex mutexBufferA, mutexBufferB;
atomic_bool bufferAFull = false;
atomic_bool bufferBFull = false;
void writeBuffer()
{
    while (true)
    {
        if (bufferAFull == false)
        {
            mutexBufferA.lock();
            bufferAData = 1;
            bufferAFull = true;
            mutexBufferA.unlock();
        }
        else
        {
            if (bufferBFull == false)
            {
                mutexBufferB.lock();
                bufferBData = 2;
                bufferBFull = true;
                mutexBufferB.unlock();
            }
            else
            {
                bufferAFull == false;
                mutexBufferA.lock();
                bufferAData = 3;
                bufferAFull = true;
                mutexBufferA.unlock();
            }

        }
    }
}
void  readBuffer()
{
	while (true)
	{
		if (bufferAFull == true)
		{
			mutexBufferA.lock();
            cout<<bufferAData<<endl;
			bufferAFull = false;
			mutexBufferA.unlock();
		}
		else
		{
			if (bufferBFull == true)
			{
				mutexBufferB.lock();
                cout<<bufferBData<<endl;
                bufferBFull = false;
				mutexBufferB.unlock();
			}
			else
			{
				Sleep(10);
			}
		}
	}
    return true;
}

主要阐述两点
(1)当bufferA与bufferB都是满的状态,写入进程会默认刷新bufferA,其实这是不对的,倘若某时间段读取进程较慢,写入进程会持续刷新bufferA,导致bufferB保持过去的数据,当读入bufferB的时候,会是很久远的数据;考虑设置bufferRefreshA标志位,每当写入bufferA,设置bufferRefreshA=false,每当写入bufferB,设置bufferRefreshA=false,因此当读入进程堵塞时,写入进程会循环刷新。
(2)当bufferA与bufferB都是空的状态,读入进程会等待10ms,这会造成无限等待的情况,可以考虑设置计时器countTime,当计时器等待1s后,会跳出循环。


三、相机双线程读写缓存策略

上一节简述了读写缓存策略的主要思想,在这一节,结合OpenCV的Mat矩阵与相机SDK,使用两个线程分别进行相机的取图与缓存区的读出。实例如下:

//define buffer region
cv::Mat srcImageA, srcImageB;
mutex mutexBufferA, mutexBufferB;
atomic_bool bufferAFull = false;
atomic_bool bufferBFull = false;
atomic_bool bufferRefreshA = false;
bool CameraGrabImage()
{
    nRet = MV_CC_GetImageBuffer(handle, &stImageInfo, 20000);
    if (nRet == MV_OK)
    {
    }
    else
    {
        printf("No data[0x%x]\n", nRet);
        return false;
    }
    if (bufferAFull == false)
    {
        mutexBufferA.lock();
        srcImageA = cv::Mat(stImageInfo.stFrameInfo.nHeight, stImageInfo.stFrameInfo.nWidth, CV_8UC1, stImageInfo.pBufAddr).clone();
        bufferRefreshA = false;
        bufferAFull = true;
        mutexBufferA.unlock();
    }
    else
    {
        if (bufferBFull == false)
        {
            mutexBufferB.lock();
            srcImageB = cv::Mat(stImageInfo.stFrameInfo.nHeight, stImageInfo.stFrameInfo.nWidth, CV_8UC1, stImageInfo.pBufAddr).clone();
            bufferRefreshA = true;
            bufferBFull = true;
            mutexBufferB.unlock();
        }
        else
        {
            if (bufferRefreshA)
            {
                bufferAFull == false;
                mutexBufferA.lock();
                srcImageA = cv::Mat(stImageInfo.stFrameInfo.nHeight, stImageInfo.stFrameInfo.nWidth, CV_8UC1, stImageInfo.pBufAddr).clone();
                bufferRefreshA = false;
                bufferAFull = true;
                mutexBufferA.unlock();
            }
            else
            {
                bufferBFull == false;
                mutexBufferB.lock();
                srcImageB = cv::Mat(stImageInfo.stFrameInfo.nHeight, stImageInfo.stFrameInfo.nWidth, CV_8UC1, stImageInfo.pBufAddr).clone();
                bufferRefreshA = true;
                bufferBFull = true;
                mutexBufferB.unlock();
            }
        }
    }
    nRet = MV_CC_FreeImageBuffer(handle, &stImageInfo);
    return true;

}
bool  ReadBuffer(cv::Mat &srcImage)
{
    int waitTimeCount = 0;
	while (true)
	{
		if (bufferAFull == true)
		{
			mutexBufferA.lock();
            srcImage = srcImageA;
			bufferAFull = false;
			mutexBufferA.unlock();
            break;
		}
		else
		{
			if (bufferBFull == true)
			{
				mutexBufferB.lock();
                srcImage = srcImageB;
                bufferBFull = false;
				mutexBufferB.unlock();
                break;
			}
			else
			{
				Sleep(10);
                waitTimeCount++;
                if (waitTimeCount == 100)
                {
                    printf("ReadBuffer Over Time, waiting........\n");
                    return false;
                }
			}
		}
	}
    return true;
}

(1)MV_CC_GetImageBuffer从相机缓存取图。
(2)相机缓存节点获取图像后,首先转化为mat格式,并写入本地缓存区域。
(3)srcImageA与srcImageB是两个缓存区域,都是OpenCV的Mat格式。
(4)readBuffer会等待1s,读不到会报错。