使用OpenCV对工业相机进行视频录制

按照以往的做法,视频录制推荐FFMPEG、GStreamer等效率更高的方式,OpenCV在视频方面就显得不是那么专业了,但由于其较高的封装性和使用方法简单,小伙伴们有时候更愿意拿OpenCV去做一些专业度要求不高的简单录制。

OpenCV工程中给出了录制普通摄像头的示例代码,如下:

/**
  @file videowriter_basic.cpp
  @brief A very basic sample for using VideoWriter and VideoCapture
  @author PkLab.net
  @date Aug 24, 2016
*/

#include 
#include 
#include 
#include 
#include 

using namespace cv;
using namespace std;

int main(int, char**)
{
    Mat src;
    // use default camera as video source
    VideoCapture cap(0);
    // check if we succeeded
    if (!cap.isOpened()) {
        cerr << "ERROR! Unable to open camera\n";
        return -1;
    }
    // get one frame from camera to know frame size and type
    cap >> src;
    // check if we succeeded
    if (src.empty()) {
        cerr << "ERROR! blank frame grabbed\n";
        return -1;
    }
    bool isColor = (src.type() == CV_8UC3);

    //--- INITIALIZE VIDEOWRITER
    VideoWriter writer;
    int codec = VideoWriter::fourcc('M', 'J', 'P', 'G');  // select desired codec (must be available at runtime)
    double fps = 25.0;                          // framerate of the created video stream
    string filename = "./live.avi";             // name of the output video file
    writer.open(filename, codec, fps, src.size(), isColor);
    // check if we succeeded
    if (!writer.isOpened()) {
        cerr << "Could not open the output video file for write\n";
        return -1;
    }

    //--- GRAB AND WRITE LOOP
    cout << "Writing videofile: " << filename << endl
         << "Press any key to terminate" << endl;
    for (;;)
    {
        // check if we succeeded
        if (!cap.read(src)) {
            cerr << "ERROR! blank frame grabbed\n";
            break;
        }
        // encode the frame into the videofile stream
        writer.write(src);
        // show live and wait for a key with timeout long enough to show images
        imshow("Live", src);
        if (waitKey(5) >= 0)
            break;
    }
    // the videofile will be closed and released automatically in VideoWriter destructor
    return 0;
}

上面的注释已经非常清晰了,这里就不再做过多解释。如果只是简单的录制,我们把上例中VideoCapture定义的内容替换成工业相机相关的接口即可,但在实际工程中,录制只是一个辅助功能,主体功能是图像处理,例如检测、识别、分类等等。因此,从工业相机获取的图像一方面需要送给图像处理算法去做处理,另一方面要交给录制模块去做录制,甚至更多时候,还需要拿出一份拷贝去做展示,等等。也就是说,从工业相机获取到的一帧图像,需要根据具体的需求有多份拷贝和分流。因此,我们的程序实现起来,会比上面的示例程序略复杂一点。

最常用的方法是设置一个图像Buffer,将从相机获取的图像放到Buffer中,不同的模块(线程)都从该Buffer取图像。这就需要将获取图像的功能也独立成一个线程,相机图像的获取、处理、录制、显示等多个功能同步进行。本篇我们使用的是Basler工业相机,相机接口已经封装好,下面示例中是在接口的基础上来做调用。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "opencv/cv.h"
#include "opencv/cxcore.h"

#include "camera_devices_gige.h"   /* 工业相机接口头文件 */

using namespace cv;
using namespace std;

typedef class CameraDevices_GigE CameraDevices;

/* 定义一个最多包含10帧图像的Buffer,下面的ImgBufSize用来限制Buffer大小 */
list imageBuffer;   
const int ImgBufSize = 10;

/* 互斥量,用来保护不同线程对图像Buffer的操作 */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

/* 工业相机图像抓取的参数,作为线程的输入 */
struct GrabParam
{
	CameraDevices *CD;
	int fps;
};

/* 计算时间间隔的函数,用来控制图像获取和编码的时间间隔 */
// Calculate time interval between current and t_start
int64_t get_time_interval(struct timeval t_start)
{
	struct timeval t_curr;
	gettimeofday(&t_curr, NULL);
	return  (t_curr.tv_sec * 1000) + (t_curr.tv_usec / 1000) - ((t_start.tv_sec * 1000) + (t_start.tv_usec / 1000));
}


/* 定义从工业相机抓取图像的线程,抓取间隔与帧率相关 */
// Image grabing thread, camera 0
void *grabImage2Buffer(void *arg)
{
	GrabParam *grab_param;
	grab_param = (struct GrabParam *)arg;
    
        CameraDevices *CD = grab_param->CD;

	int fps = grab_param->fps;

	struct timeval t_start;
	int ret, cnt = 0;
	Mat Img;
	int grab_intval = 1000 / fps;   // ms, fps = 25, enc_interval = 40ms
	
	gettimeofday(&t_start, NULL);

	while(1)
	{
		gettimeofday(&t_start, NULL);   // Update t_start every time before grab an image
		
		ret = CD->Grab_cvImage(0, Img);   //相机类方法,抓取一帧图像
		if(ret < 0)
		{
			cout << "Grab image from camera failed, will abort!" << endl;
			exit(EXIT_FAILURE);
		}

		/* lock */
		if(pthread_mutex_lock(&mutex) != 0)
		{
			perror("pthread_mutex_lock");
			exit(EXIT_FAILURE);
		}

                 /* 图像Buffer未满时,直接将图像数据放入Buffer */
		if(imageBuffer.size() < ImgBufSize)  
		{
			imageBuffer.push_back(Img);
		}
                /* 若Buffer已满,则先将最老的图像数据清除,再将新的图像数据压入 */
		else
		{
			imageBuffer.pop_front();
			imageBuffer.push_back(Img);
		}

		/* Unlock */
		if(pthread_mutex_unlock(&mutex) != 0)
		{
			perror("pthread_mutex_unlock");
			exit(EXIT_FAILURE);
		}

                /* 判断当前时间是否已到采集时间,若未到,则等待 */
		int64_t intval = get_time_interval(t_start);   // current time and last grab time interval
		if(intval < grab_intval)
		{
			usleep((grab_intval - intval) * 1000);  // usecond
		}
		
	}

	cout << "Thread grabImage2Buffer exit" << endl;
	
	return ((void*)0);
}

/* 定义函数:从图像Buffer中取出一帧数据 */
void get_one_image_from_buffer(Mat &Img)
{
	Mat Img_tmp;  
	
	while(1)
	{
		if(!imageBuffer.empty())
		{
			if(pthread_mutex_lock(&mutex) != 0)
			{
				perror("pthread_mutex_lock");
				exit(EXIT_FAILURE);
			}
			
			/* Get the front image for video recording */
			Img_tmp = imageBuffer.front();
			Img_tmp.copyTo(Img);
			
			/* Delete the front image */
			imageBuffer.pop_front();
		
			if(pthread_mutex_unlock(&mutex) != 0)
			{
				perror("pthread_mutex_unlock");
				exit(EXIT_FAILURE);
			}

			break;
		}
	}

}


/* The main function */
int main()
{
	/* 定义相机实例、初始化并设置必要的参数 */
        CameraDevices camera_devices;
	bool is_cam_ok = camera_devices.initCameras();
	if(!is_cam_ok)
	{
		exit(EXIT_FAILURE);
	}

	for(int i = 0; i < camera_devices.deviceNum; i++)
	{
		camera_devices.setTriggerMode(i, 0, 1000000);
	}

	/* 从相机获取一帧图像,判断相机是否工作正常,并提供后续编码的参数,如图像宽高 */
        Mat Img;
	if(camera_devices.Grab_cvImage(0, Img) < 0)
	{
		cout << "Grab image from camera failed, will abort!" << endl;
		exit(EXIT_FAILURE);
	}
	
	/* 相机抓取线程的输入参数 */
        GrabParam gparam;
	gparam.CD = &camera_devices;
	gparam.fps = 25;   //FRAME_RATE;
	
	int fps = gparam.fps;    /* 编码帧率与图像抓取帧率一致 */
	
	/* 创建从工业相机抓取图像线程 */
        /* Create an image grabbing thread */
	pthread_t thread_grab;
	pthread_create(&thread_grab, NULL, grabImage2Buffer, (void*)&gparam);
	
	/* video record */
	
	/* 使用VideoWriter类进行录制,首先创建输出文件 */
        /* Output file creation */
	VideoWriter vRec;
	vRec.open("out.mp4", CV_FOURCC('D','I','V','X'), fps, Size(Img.cols, Img.rows));
	if(!vRec.isOpened())
	{
		cout << "VideoWriter open failed! \n" << endl;
		exit(EXIT_FAILURE);
	}
	
	struct timeval t_start;
	int ret, cnt = 0;
	int enc_intval = 1000 / fps;   // ms, fps = 25, enc_interval = 40ms
	
	/* After output file is created, get images and write to the file */
	while(1)
	{
		/* Record a recording time of one frame */
		gettimeofday(&t_start, NULL); 
		
		/* 调用从Buffer读取图像函数,获取一帧图像 */
                get_one_image_from_buffer(Img);
		
		/* 将获取到的一帧图像数据编码并写入输出文件 */
                if(!Img.empty())
		{
			vRec.write(Img);    /* Write one frame to the output file */
		}
		
		/* 通过时间控制帧率 */
                int64_t intval = get_time_interval(t_start);   // current time and last grab time interval
		if(intval < enc_intval)
		{
			usleep((enc_intval - intval) * 1000);  // usecond
		}
		
		/* 录制停止 */
                if(waitKey(5) == 'q')
		{
			break;
		}
		
	}
	
	pthread_join(thread_grab, NULL);
}

以上代码编译通过后,运行,可能出现如下错误,

就需要检查你的OpenCV编译时是否使能了FFMPEG/GStreamer等编码依赖项,如果未使能,需要安装FFMPEG/GStreamer(我因为对FFMPEG更熟悉一些,首选FFMPEG),并重编OpenCV,在cmake时加入编译参数 -D WITH_FFMPEG=ON,详见之前的文章:在CentOS系统上安装OpenCV-3

当然,你也可以选择自己喜欢或者熟悉的编码框架,感兴趣的同学可以看一下VideoWriter的定义,如下代码中,通过在VideoWriter(const String& filename, int apiPreference, int fourcc, double fps, Size frameSize, bool isColor = true)或open(const String& filename, int apiPreference, int fourcc, double fps, Size frameSize, bool isColor = true);中指定apiPreference来启用相应的后台编码框架。

class CV_EXPORTS_W VideoWriter
{
public:
    /** @brief Default constructors

    The constructors/functions initialize video writers.
    -   On Linux FFMPEG is used to write videos;
    -   On Windows FFMPEG or VFW is used;
    -   On MacOSX QTKit is used.
     */
    CV_WRAP VideoWriter();

    /** @overload
    @param filename Name of the output video file.
    @param fourcc 4-character code of codec used to compress the frames. For example,
    VideoWriter::fourcc('P','I','M','1') is a MPEG-1 codec, VideoWriter::fourcc('M','J','P','G') is a
    motion-jpeg codec etc. List of codes can be obtained at [Video Codecs by
    FOURCC](http://www.fourcc.org/codecs.php) page. FFMPEG backend with MP4 container natively uses
    other values as fourcc code: see [ObjectType](http://www.mp4ra.org/codecs.html),
    so you may receive a warning message from OpenCV about fourcc code conversion.
    @param fps Framerate of the created video stream.
    @param frameSize Size of the video frames.
    @param isColor If it is not zero, the encoder will expect and encode color frames, otherwise it
    will work with grayscale frames (the flag is currently supported on Windows only).

    @b Tips:
    - With some backends `fourcc=-1` pops up the codec selection dialog from the system.
    - To save image sequence use a proper filename (eg. `img_%02d.jpg`) and `fourcc=0`
      OR `fps=0`. Use uncompressed image format (eg. `img_%02d.BMP`) to save raw frames.
    - Most codecs are lossy. If you want lossless video file you need to use a lossless codecs
      (eg. FFMPEG FFV1, Huffman HFYU, Lagarith LAGS, etc...)
    - If FFMPEG is enabled, using `codec=0; fps=0;` you can create an uncompressed (raw) video file.
    */
    CV_WRAP VideoWriter(const String& filename, int fourcc, double fps,
                Size frameSize, bool isColor = true);

    /** @overload
    The `apiPreference` parameter allows to specify API backends to use. Can be used to enforce a specific reader implementation
    if multiple are available: e.g. cv::CAP_FFMPEG or cv::CAP_GSTREAMER.
     */
    CV_WRAP VideoWriter(const String& filename, int apiPreference, int fourcc, double fps,
                Size frameSize, bool isColor = true);

    /** @brief Default destructor

    The method first calls VideoWriter::release to close the already opened file.
    */
    virtual ~VideoWriter();

    /** @brief Initializes or reinitializes video writer.

    The method opens video writer. Parameters are the same as in the constructor
    VideoWriter::VideoWriter.
    @return `true` if video writer has been successfully initialized

    The method first calls VideoWriter::release to close the already opened file.
     */
    CV_WRAP virtual bool open(const String& filename, int fourcc, double fps,
                      Size frameSize, bool isColor = true);

    /** @overload
     */
    CV_WRAP bool open(const String& filename, int apiPreference, int fourcc, double fps,
                      Size frameSize, bool isColor = true);

    /** @brief Returns true if video writer has been successfully initialized.
    */
    CV_WRAP virtual bool isOpened() const;

    /** @brief Closes the video writer.

    The method is automatically called by subsequent VideoWriter::open and by the VideoWriter
    destructor.
     */
    CV_WRAP virtual void release();

    /** @brief Stream operator to write the next video frame.
    @sa write
    */
    virtual VideoWriter& operator << (const Mat& image);

    /** @brief Writes the next video frame

    @param image The written frame. In general, color images are expected in BGR format.

    The function/method writes the specified image to video file. It must have the same size as has
    been specified when opening the video writer.
     */
    CV_WRAP virtual void write(const Mat& image);

    /** @brief Sets a property in the VideoWriter.

     @param propId Property identifier from cv::VideoWriterProperties (eg. cv::VIDEOWRITER_PROP_QUALITY)
     or one of @ref videoio_flags_others

     @param value Value of the property.
     @return  `true` if the property is supported by the backend used by the VideoWriter instance.
     */
    CV_WRAP virtual bool set(int propId, double value);

    /** @brief Returns the specified VideoWriter property

     @param propId Property identifier from cv::VideoWriterProperties (eg. cv::VIDEOWRITER_PROP_QUALITY)
     or one of @ref videoio_flags_others

     @return Value for the specified property. Value 0 is returned when querying a property that is
     not supported by the backend used by the VideoWriter instance.
     */
    CV_WRAP virtual double get(int propId) const;

    /** @brief Concatenates 4 chars to a fourcc code

    @return a fourcc code

    This static method constructs the fourcc code of the codec to be used in the constructor
    VideoWriter::VideoWriter or VideoWriter::open.
     */
    CV_WRAP static int fourcc(char c1, char c2, char c3, char c4);

protected:
    Ptr writer;
    Ptr iwriter;

    static Ptr create(const String& filename, int fourcc, double fps,
                                    Size frameSize, bool isColor = true);
};

 

你可能感兴趣的:(OpenCV)