In ROS diamondback, they disabled the video interface options. That means you can not capture video from a file or a Web Camera using the standard OpenCV function calls. For more details, please visit: http://answers.ros.org/question/1128/opencv-videocapture-not-working-in-diamondback.
This frustrated me to no end, so i decided to write my own class based on FFMPEG to read a video File. This program shows how to capture frames from a video file using FFMPEG, store them in OpenCV cv::Mat template and display the result in a HighGUI Window.
Way to run it
- Download both the packages in to a folder: DOWNLOAD LINK , and make sure to add the folder to the ROS_PACKAGE_PATH. I have it in my HOME directory, so i added the following two lines at the end of my .bashrc:
export ROS_PACKAGE_PATH=~/testROSOpenCVFFMPEG:$ROS_PACKAGE_PATH
Make sure to restart the terminal window for it to take effect.
- I have included a sample video located in the samples sub-folder. Make sure to change the path of the input Video file on the line 350: fileName=”/home/sid/testROSOpenCVFFMPEG/samples/testVideo.avi”; to the full path and name of the file for your case.
- If you would like to save all the frames of the video as *.ppm files, make sure to comment out the code from line 302 to 308.
- Open a new terminal window and type the following:
$ sudo bash
Provide your password, and you will then have root access.
# roscd testROSOpenCVFFMPEG/
# rosmake testROSOpenCVFFMPEG
Make sure that the package built with no errors.
To run it, type:# rosrun testROSOpenCVFFMPEG testROSOpenCVFFMPEG
- You should now see an OpenCV Window. The Window is waiting for you to press a key, so that it can grab the next frame from the video file. So to step, keep pressing “ANY” key. Have FUN with it! If you break it, let me know how you managed to do it, and i will try and fix……maybe
testROSOpenCVFFMPEG Source Code:
/*
Copyright (c) 2011, Siddhant Ahuja (Sid)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
This program shows how to capture frames from a video file using FFMPEG, store
them in OpenCV cv::Mat template and display the result in a HighGUI Window.
*/
//Includes all the headers necessary to use the most common public pieces of the ROS system.
#include <ros/ros.h>
//Include all the FFMPEG header files.
extern "C" {
//Library containing decoders and encoders for audio/video codecs.
#include <libavcodec/avcodec.h>
//Library containing demuxers and muxers for multimedia container formats.
#include <libavformat/avformat.h>
//Library performing highly optimized image scaling and color space/pixel format conversion operations.
#include <libswscale/swscale.h>
}
//Include headers for OpenCV Image processing.
#include <opencv2/imgproc/imgproc.hpp>
//Include headers for OpenCV GUI handling.
#include <opencv2/highgui/highgui.hpp>
using namespace std;
//Declare a string with the name of the window that we will create using OpenCV where processed images will be displayed.
static const char WINDOW[] = "Test ROS + OpenCV + FFMPEG";
//Create a Structure containing the OpenCV cv::Mat format and the status of error.
struct openCVFrameContext
{
cv::Mat cvFrameBGR;
bool errorStatus;
};
//This is the main class that contains all public and protected members.
class Capture_FFMPEG
{
public:
//Constructor.
Capture_FFMPEG() {init();}
//Deconstructor.
~Capture_FFMPEG() { close();}
//Function to open a Video File.
virtual bool openVideoFile(const char* fileName);
//Function to grab a frame and retrieve the data. This returns a structure that contains a matrix of type cv::Mat and the error status.
virtual openCVFrameContext queryFrame();
//Function to save the frame data as *.ppm file.
virtual int saveFrame(AVFrame *pFrame, int width, int height, int iFrame);
protected:
//Function to initialize all protected variables.
virtual void init();
//Function to destroy all protected variables, structures and free up memory.
virtual void close();
char * fileName; //Variable to store the file name.
int videoStream; //Variable to store the index of the Video Stream.
int frameFinished; //Variable to determine if the frame data was fetched properly.
AVFormatContext *pVFormatCtx;
AVCodecContext *pVCodecCtx;
AVCodec *pVCodec;
AVFrame *pVFrame;
AVFrame *pVFrameBGR;
uint8_t *bufferBGR;
AVPacket pVPacket;
openCVFrameContext cvFrameContext;
struct SwsContext *pVImgConvertCtx;
};
//Function to initialize all protected variables.
void Capture_FFMPEG::init()
{
fileName=NULL;
videoStream = -1;
frameFinished = 0;
};
//Function to destroy all protected variables, structures and free up memory.
void Capture_FFMPEG::close()
{
printf("close:Destroying Variables. \n");
// Free the AVFrame
if(pVFrame)
{
printf("close:Freeing AVFrame. \n");
av_free(pVFrame);
}
// Free the AVFrame
if(pVFrameBGR)
{
printf("close:Freeing AVFrameBGR. \n");
av_free(pVFrameBGR);
}
// Free the packet that was allocated by av_read_frame.
if(&pVPacket)
{
printf("close:Freeing Packet. \n");
av_free_packet(&pVPacket);
}
// Free SwsContext.
if(pVImgConvertCtx)
{
printf("close:Freeing SWSContext. \n");
sws_freeContext(pVImgConvertCtx);
}
// Close the codec.
if(pVCodecCtx)
{
printf("close:Closing Codec. \n");
avcodec_close(pVCodecCtx);
}
// Close the video file.
if(pVFormatCtx)
{
printf("close:Closing Video File. \n");
av_close_input_file(pVFormatCtx);
}
};
//Function to open a Video File.
bool Capture_FFMPEG::openVideoFile(const char* fileName)
{
bool errorStatus=false; //true: error.
//Open the input file, and if unable to then return true. This function reads the file header and stores information about the file format in the AVFormatContext structure. The last three arguments are used to specify the file format, buffer size, and format options, but by setting this to NULL or 0, libavformat will auto-detect these.
if(av_open_input_file(&pVFormatCtx, fileName, NULL, 0, NULL)!=0)
{
fprintf(stderr, "ERROR:openVideoFile:Could not open the video file\n");
return errorStatus=true;
}
// Retrieve stream information. Populates pVFormatCtx->streams with the proper information.
if(av_find_stream_info(pVFormatCtx)<0)
{
fprintf(stderr, "ERROR:openVideoFile:Could not open the stream\n");
return errorStatus=true;
}
// Dump information about file onto standard error.
dump_format(pVFormatCtx, 0, fileName, 0);
videoStream=-1;
// Here we are only concerned with video streams, so find the index of the first video stream.
for(int i=0; i< pVFormatCtx->nb_streams; i++)
{
if(pVFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO)
{
videoStream=i;
break;
}
}
//Check videoStream variable and if none found, throw error.
if(videoStream<0)
{
fprintf(stderr, "ERROR:openVideoFile:Could not find a valid Video stream\n");
return errorStatus=true;
}
//Get a pointer to the codec context for the video stream. The stream's information about the codec is in what we call the "codec context." This contains all the information about the codec that the stream is using, and now we have a pointer to it.
pVCodecCtx=pVFormatCtx->streams[videoStream]->codec;
// Find the actual codec and open it. Find the decoder for the video stream.
pVCodec=avcodec_find_decoder(pVCodecCtx->codec_id);
if(pVCodec==NULL) {
fprintf(stderr, "ERROR:openVideoFile:Unsupported codec or codec not found!\n");
return errorStatus=true;
}
printf("openVideoFile:Decoder: %s\n", pVCodec->name);
// Open the codec.
if(avcodec_open(pVCodecCtx, pVCodec)<0)
{
fprintf(stderr, "ERROR:openVideoFile:Could not open codec!\n");
return errorStatus=true;
}
//Initialize a place to actually store the frame.
pVFrame=avcodec_alloc_frame();
// Allocate an AVFrame structure.
pVFrameBGR=avcodec_alloc_frame();
if(pVFrameBGR==NULL) {
fprintf(stderr, "ERROR:openVideoFile:Could Not Allocate the frame!\n");
return errorStatus=true;
}
//Allocate the place to put the raw data when we convert it. We use avpicture_get_size to get the size we need, and allocate the space manually.
//Determine required buffer size.
int numBytes=avpicture_get_size(PIX_FMT_BGR24, pVCodecCtx->width, pVCodecCtx->height);
//printf("openVideoFile:Buffer Size: %d bytes\n", numBytes);
//Allocate the Buffer.
bufferBGR=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
// Assign appropriate parts of buffer to image planes in pVFrameBGR. Note that pVFrameBGR is an AVFrame, but AVFrame is a superset of AVPicture.
avpicture_fill((AVPicture *)pVFrameBGR, bufferBGR, PIX_FMT_BGR24, pVCodecCtx->width, pVCodecCtx->height);
// Create an OpenCV Matrix with 3 channels.
cvFrameContext.cvFrameBGR.create(pVCodecCtx->height, pVCodecCtx->width ,CV_8UC(3));
//Final return of Error Status.
return errorStatus;
};
//Function to grab a frame and retrieve the data. This returns a structure that contains a matrix of type cv::Mat and the error status.
openCVFrameContext Capture_FFMPEG::queryFrame()
{
cvFrameContext.errorStatus=false; //true: error.
//Return the next frame of a stream. The information is stored as a packet in pkt.
if(av_read_frame(pVFormatCtx, &pVPacket)<0)
{
fprintf(stderr, "ERROR:queryFrame:Could not Read Frame!\n");
cvFrameContext.errorStatus=true;
return cvFrameContext; // Codec not found.
}
//Check if the packet belonged to the video stream.
if(pVPacket.stream_index==videoStream)
{
//printf("queryFrame:Decoding Video.\n");
// Decode the video frame from the input buffer buf of size buf_size. To decode it, it makes use of the videocodec which was coupled with avctx using avcodec_open(). The resulting decoded frame is stored in picture.
if(avcodec_decode_video(pVCodecCtx, pVFrame, &frameFinished, pVPacket.data, pVPacket.size)<0)
{
fprintf(stderr, "ERROR:queryFrame:Could Not Decode Video!\n");
cvFrameContext.errorStatus=true;
return cvFrameContext;
}
// Did we get a video frame?
if(frameFinished)
{
// printf("queryFrame:Querying Frame Number %d Finished. Now Converting Color Space and Scaling\n", pVCodecCtx->frame_number);
// Convert the image from its native format to BGR.
// Return an SwsContext to be used in sws_scale.
if(pVImgConvertCtx==NULL)
{
pVImgConvertCtx = sws_getContext(pVCodecCtx->width, pVCodecCtx->height, pVCodecCtx->pix_fmt, pVCodecCtx->width, pVCodecCtx->height, PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
}
if(pVImgConvertCtx==NULL)
{
fprintf(stderr, "ERROR:queryFrame:Cannot initialize the conversion context!\n");
cvFrameContext.errorStatus=true;
return cvFrameContext;
}
// Scales the data in src according to our settings in our SwsContext.
sws_scale(pVImgConvertCtx, pVFrame->data, pVFrame->linesize, 0, pVCodecCtx->height, pVFrameBGR->data, pVFrameBGR->linesize);
// Populate the OpenCV Matrix.
for(int y=0; y<pVCodecCtx->height; y++)
{
for (int x=0; x<pVCodecCtx->width; x++)
{
cvFrameContext.cvFrameBGR.at<cv::Vec3b>(y,x)[0]=pVFrameBGR->data[0][y * pVFrameBGR->linesize[0] + x * 3 + 0];
cvFrameContext.cvFrameBGR.at<cv::Vec3b>(y,x)[1]=pVFrameBGR->data[0][y * pVFrameBGR->linesize[0] + x * 3 + 1];
cvFrameContext.cvFrameBGR.at<cv::Vec3b>(y,x)[2]=pVFrameBGR->data[0][y * pVFrameBGR->linesize[0] + x * 3 + 2];
}
}
// Save Frame as *.ppm file.
/*if(saveFrame(pVFrameBGR, pVCodecCtx->width, pVCodecCtx->height, pVCodecCtx->frame_number)<0)
{
fprintf(stderr, "ERROR:queryFrame:Could not Save Frame!\n");
cvFrameContext.errorStatus=true;
return cvFrameContext;
}*/
}
}
//Final return.
return cvFrameContext;
};
//Function to save the frame data as *.ppm file.
int Capture_FFMPEG::saveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
FILE *pFile;
char szFilename[32];
int y;
// Open file.
sprintf(szFilename, "frame%d.ppm", iFrame);
pFile=fopen(szFilename, "wb");
if(pFile==NULL)
return -1;
// Write header.
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
printf("saveFrame: %s of size %d x %d \n", szFilename, width, height);
// Write pixel data.
for(y=0; y<height; y++)
fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
// Close file.
fclose(pFile);
};
int main(int argc, char **argv)
{
//Registers all available file formats and codecs with the library so they will be used automatically when a file with the corresponding format/codec is opened.
av_register_all();
//Declate the Name of the File.
string fileName;
fileName="/home/sid/testROSOpenCVFFMPEG/samples/testVideo.avi";
//Create and initialize an object of class Capture_FFMPEG.
Capture_FFMPEG *capture = new Capture_FFMPEG;
//Create a Sturcture that stores the return output of queryFrame();
openCVFrameContext testCVFrameContext;
//Initialize the errorStatus to false.
testCVFrameContext.errorStatus=false;
//Open the stream capture.
testCVFrameContext.errorStatus=capture->openVideoFile(fileName.c_str());
if(testCVFrameContext.errorStatus==true)
{
return 1;
}
//OpenCV HighGUI call to create a display window on start-up.
cv::namedWindow(WINDOW, CV_WINDOW_AUTOSIZE);
cvMoveWindow(WINDOW, 50, 50);
while(!testCVFrameContext.errorStatus)
{
//Query Frame
testCVFrameContext=capture->queryFrame();
//Display the Data in the OpenCV HighGUI Window.
cv::imshow(WINDOW, testCVFrameContext.cvFrameBGR);
//Wait for Keyboard input. The function only works if there is at least one HighGUI window created and the window is active. If there are several HighGUI windows, any of them can be active.
cv::waitKey(0);
}
//Destroy the object
delete capture;
//Final Return
return 0;
}

