Advertisement

使用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 <opencv2/core.hpp>
    
 #include <opencv2/videoio.hpp>
    
 #include <opencv2/highgui.hpp>
    
 #include <iostream>
    
 #include <stdio.h>
    
  
    
 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;
    
 }
    
    
    
    
    cpp
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/jzMn8GhLHlgqifIuZCx5AO7RTwXD.png)

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

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

复制代码
 #include <iostream>

    
 #include <vector>
    
 #include <cstring>
    
 #include <unistd.h>
    
 #include <sys/time.h>
    
 #include <ctime>
    
 #include <opencv2/opencv.hpp>
    
 #include <opencv2/video/video.hpp>
    
 #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<Mat> 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);
    
 }
    
    
    
    
    cpp
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/cFAftu14iBRwobHWNG50hLjSIe9D.png)

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

就需要检查你的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
    
   6.     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).
    
   26.     @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
    
   46.     The method first calls VideoWriter::release to close the already opened file.
    
     */
    
     virtual ~VideoWriter();
    
  
    
     /** @brief Initializes or reinitializes video writer.
    
   52.     The method opens video writer. Parameters are the same as in the constructor
    
     VideoWriter::VideoWriter.
    
     @return `true` if video writer has been successfully initialized
    
   56.     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.
    
   72.     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
    
   84.     @param image The written frame. In general, color images are expected in BGR format.
    
   86.     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.
    
   93.      @param propId Property identifier from cv::VideoWriterProperties (eg. cv::VIDEOWRITER_PROP_QUALITY)
    
      or one of @ref videoio_flags_others
    
   96.      @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
    
   103.      @param propId Property identifier from cv::VideoWriterProperties (eg. cv::VIDEOWRITER_PROP_QUALITY)
    
      or one of @ref videoio_flags_others
    
   106.      @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
    
   113.     @return a fourcc code
    
   115.     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<CvVideoWriter> writer;
    
     Ptr<IVideoWriter> iwriter;
    
  
    
     static Ptr<IVideoWriter> create(const String& filename, int fourcc, double fps,
    
                                 Size frameSize, bool isColor = true);
    
 };
    
    
    
    
    cpp
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-16/1RcHiOjmrxtyU4gVd0nIPlowTzsq.png)

全部评论 (0)

还没有任何评论哟~