作者: Sam (甄峰)
sam_code@hotmail.com
OpenCV提供接口,可以对Camera进行操作。
0.
OpenCV对Camera的操作所属目录文件:
OpenCV对Camera的操作,有多种方式。从VideoCapture class开始。
在modules/videoio/include/opencv2/videoio.hpp中,声明了一个class:
class CV_EXPORTS_W VideoCapture
{
};
它利用__attribute__ ((visibility ("default"))) 把class作为可见。
1. Camera读取接口:
OpenCV读取Camera的方法随OpenCV版本升级而提供了多种方式,我们先从VideoCapture
class的成员函数看起:
1.1: VideoCapture的构造函数:
VideoCapture::VideoCapture(int index)
{
open(index);
}
1.2:VideoCapture的open()函数:
1.2.1:打开/dev/videoX:
bool VideoCapture::open(int index)
{
if (isOpened())
release();
icap =
IVideoCapture_create(index);
if (!icap.empty())
return true;
cap.reset(cvCreateCameraCapture(index));
return isOpened();
}
这里会调用cap.reset(cvCreateCameraCapture(index));
其中cvCreateCameraCapture(index)会创建capture.
它会根据编译OpenCV时的设置, 依次判断采用哪个硬件或架构:
HAVE_MSMF,Microsoft Media
Foundation
HAVE_VFW, platform native
HAVE_CAMERAVL2, HAVE_LIBV4L, 采用libv4l,或者v4l2.
HAVE_GSTREAMER, GStreamer,
CV_CAP_FIREWIRE, 1394等等。
1.2.2: 打开文件:
bool VideoCapture::open(const String& filename, int
apiPreference)
{
if (isOpened())
release();
icap =
IVideoCapture_create(filename);
if (!icap.empty())
return true;
cap.reset(cvCreateFileCaptureWithPreference(filename.c_str(),
apiPreference));
return isOpened();
}
cvCreateFileCaptureWithPreference()
这创建capture时,可以选择ffmpeg等。
1.3: open()的内部细节:
当选中了某个设置时,则capture由这个设置的函数具体构建,例如,我们选择在Linux下使用v4l2做OpenCV
Camera接口的实现。则:
CvCapture* cvCreateCameraCapture_V4L( int index )
{
cv::CvCaptureCAM_V4L*
capture = new cv::CvCaptureCAM_V4L();
if(capture->open(index))
return capture;
delete capture;
return NULL;
}
其中:
struct CvCaptureCAM_V4L : public CvCapture,
CvCaptureCAM_V4L结构体继承自CvCapture。
这里会打开/dev/videoX设备(try_init_v4l2();),
并利用V4L2--ioctl()设置Camera。
获取Camera 各设置Range(v4l2_scan_controls())
设置帧数 (v4l2_set_fps())
设置Buffer, mmap等。
1.3:VideoCapture的read()函数:
bool VideoCapture::read(OutputArray image)
{
if(grab())
retrieve(image);
else
image.release();
return
!image.empty();
}
依次调用grab(), retrieve()函数。
bool VideoCapture::grab()
{
if (!icap.empty())
return icap->grabFrame();
return cvGrabFrame(cap)
!= 0;
}
CV_IMPL int cvGrabFrame( CvCapture* capture )
{
return capture ?
capture->grabFrame() : 0;
}
这里,capture根据不同设置,由不同模块创建,我们使用V4L2.所以看V4L2(cap_v4l.cpp)的对应函数。
bool CvCaptureCAM_V4L::grabFrame()
{
return
icvGrabFrameCAM_V4L( this );
}
最终拿数据的是:
static void mainloop_v4l2(CvCaptureCAM_V4L* capture) {
unsigned int
count;
count = 1;
while (count-- > 0)
{
for (;;) {
fd_set
fds;
struct
timeval tv;
int
r;
FD_ZERO
(&fds);
FD_SET
(capture->deviceHandle, &fds);
tv.tv_sec
= 10;
tv.tv_usec
= 0;
r = select
(capture->deviceHandle+1, &fds, NULL, NULL, &tv);
if (-1 ==
r) {
if (EINTR == errno)
continue;
perror ("select");
}
if (0 ==
r) {
fprintf (stderr, "select
timeout\n");
break;
}
if
(read_frame_v4l2 (capture))
break;
}
}
}
这个函数很有玄机,它并不是直接DQBUF, 而是使用一个循环,这样就可以保证,每次获取的数据都是最新数据。
static int read_frame_v4l2(CvCaptureCAM_V4L* capture) {
v4l2_buffer buf =
v4l2_buffer();
buf.type =
V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory =
V4L2_MEMORY_MMAP;
if (-1 == ioctl
(capture->deviceHandle, VIDIOC_DQBUF, &buf)) {
switch (errno) {
case EAGAIN:
return
0;
case EIO:
if (!(buf.flags & (V4L2_BUF_FLAG_QUEUED |
V4L2_BUF_FLAG_DONE)))
{
if
(ioctl(capture->deviceHandle, VIDIOC_QBUF, &buf) ==
-1)
{
return
0;
}
}
return 0;
default:
perror
("VIDIOC_DQBUF");
return
1;
}
}
assert(buf.index <
capture->req.count);
memcpy(capture->buffers[MAX_V4L_BUFFERS].start,
capture->buffers[buf.index].start,
capture->buffers[MAX_V4L_BUFFERS].length );
capture->bufferIndex =
MAX_V4L_BUFFERS;
//printf("got data in
buff %d, len=%d, flags=0x%X, seq=%d, used=%d)\n",
//
buf.index, buf.length, buf.flags, buf.sequence,
buf.bytesused);
if (-1 == ioctl
(capture->deviceHandle, VIDIOC_QBUF, &buf))
perror ("VIDIOC_QBUF");
//set timestamp in
capture struct to be timestamp of most recent frame
capture->timestamp =
buf.timestamp;
return 1;
}