Linux下攝像頭應用程式設計
V4L2是Video for linux2的簡稱,為linux中關于視訊裝置的核心驅動。在Linux中,視訊裝置是裝置檔案,可以像通路普通檔案一樣對其進行讀寫,攝像頭在/dev/video*下,如果隻有一個視訊裝置,通常為/dev/video0。
v4L2是針對uvc免驅usb裝置的程式設計架構 ,主要用于采集usb攝像頭等。
1.攝像頭架構程式設計步驟
(1)打開攝像頭裝置(/dev/video0 、/dev/video1 )。
(2)設定圖像格式:VIDIOC_S_FMT(視訊捕獲格式、圖像顔色資料格式、圖像寬和高)。
(3)申請緩沖區:VIDIOC_REQBUFS(緩沖區數量、緩沖映射方式、視訊捕獲格式)。
(4)将緩沖區映射到程序空間:VIDIOC_QUERYBUF(要映射的緩沖區下标、緩沖映射方式、視訊捕獲格式)。
(5)将緩沖區添加到隊列中:VIDIOC_QBUF(映射的緩沖區下标、緩沖映射方式、視訊捕獲格式)。
(6)開啟攝像頭采集:VIDIOC_STREAMON (視訊捕獲格式)。
(7)從采集隊列中取出圖像資料VIDIOC_DQBUF,進行圖像渲染。
2.V4L2頭檔案資訊
V4L2是Linux下标準視訊驅動架構,相關頭檔案資訊在include/linux/videodev2.h中。
V4L2 驅動對使用者空間提供字元裝置,主裝置号為 81,對于視訊裝置,其次裝置号為 0-63。除此之外,次裝置号為 64-127 的 Radio 收音機裝置,次裝置号為 192-223 的是 Teletext 廣播裝置,次裝置号為 224-255 的是 VBI視訊消影裝置。
V4L2 驅動的 Video 裝置在使用者空間通過各種 ioctl 調用進行控制,并且可以使用 mmap 進行記憶體映射。
2.1 常用ioctl參數
設定視訊捕獲格式
#define VIDIOC_S_FMT _IOWR(‘V’, 5, struct v4l2_format)
向核心申請緩沖區
#define VIDIOC_REQBUFS _IOWR(‘V’, 8, struct v4l2_requestbuffers)
将緩沖區映射到程序空間
#define VIDIOC_QUERYBUF _IOWR(‘V’, 9, struct v4l2_buffer)
将緩沖區添加到采集隊列
#define VIDIOC_QBUF _IOWR(‘V’, 15, struct v4l2_buffer)
從隊列中擷取圖像資料
#define VIDIOC_DQBUF _IOWR(‘V’, 17, struct v4l2_buffer)
開啟攝像頭圖像采集
#define VIDIOC_STREAMON _IOW(‘V’, 18, int)
2.2 核心結構體資訊
- struct v4l2_format
struct v4l2_format {
__u32 type;
union {
struct v4l2_pix_format pix;
struct v4l2_pix_format_mplane pix_mp;
} fmt;
};
- struct v4l2_pix_format
struct v4l2_pix_format {
__u32 width;//圖像寬度
__u32 height;//圖像高度
__u32 pixelformat;//圖像資料格式
__u32 field;
__u32 bytesperline;
__u32 sizeimage;
__u32 colorspace;
__u32 priv;
};
- struct v4l2_requestbuffers
//記憶體映射緩沖區
struct v4l2_requestbuffers {
__u32 count; //申請緩沖區個數
__u32 type;
__u32 reserved[2];
};
- struct v4l2_buffer
//視訊緩沖區資訊
struct v4l2_buffer {
__u32 index;/數組下标/
__u32 type;/視訊捕獲格式/
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
__u32 memory;/映射格式/
union {
__u32 offset;/偏移量/
unsigned long userptr;
struct v4l2_plane *planes;
int fd;
} m;
__u32 length;/映射緩沖區大小/
__u32 input;
__u32 reserved;
};
2.3 圖像顔色編碼格式
關于YUV格式圖像參考:https://blog.csdn.net/sway913/article/details/120602052
-
YUV
YUV,是一種顔色編碼方法。常使用在各個視訊處理元件中。 YUV在對照片或視訊編碼時,考慮到人類的感覺能力,允許降低色度的帶寬。
YUV是編譯true-color顔色空間(color space)的種類,Y’UV, YUV, YCbCr,YPbPr等專有名詞都可以稱為YUV,彼此有重疊。“Y”表示明亮度(Luminance或Luma),也就是灰階值,“U”和“V”表示的則是色度(Chrominance或Chroma),作用是描述影像色彩及飽和度,用于指定像素的顔色。
YUV格式有兩大類:planar(平面)和packed(交錯)。
對于planar的YUV格式,先連續存儲所有像素點的Y,緊接着存儲所有像素點的U,随後是所有像素點的V。
對于packed的YUV格式,每個像素點的Y,U,V是連續交錯存儲的。
YUV的主要優勢在于可以相容之前的黑白電視,單獨隻有Y資料就可以顯示完整的黑白圖像,UV是後期加入的色彩參數。
-
YUYV格式:YUV422
YUV 4:2:2采樣,表示在每4個像素中,Y采集4份,U采集2份,V采集2份。每兩個 Y 分量共享一組 UV 分量。單個像素占用空間為:1byte(Y)+1/2byte(U)+1/2byte(V)=2位元組;一幀圖像占用的空間為:width * height * 2
Linux下攝像頭應用程式設計Linux下攝像頭應用程式設計 -
YUV420
YUV420 每四個Y分量公用一個UV分量,并不是沒有V分量,而是UV分量交替采樣,是以每個像素點占用1.5個位元組空間。根據planar(平面)和packed(交錯)方式存儲有4種存儲方式。下面舉其中一種示例說明:
每4個Y共用一組UV分量,單個像素占用空間為:1byte(Y)+1/4byte(U)+1/4byte(V)=1.5位元組;一幀圖像占用的空間為:width * height * 3/2 byte。
Linux下攝像頭應用程式設計Linux下攝像頭應用程式設計 -
RGB
RGB色彩模式是工業界的一種顔色标準,是通過對紅®、綠(G)、藍(B)三個顔色通道的變化以及它們互相之間的疊加來得到各式各樣的顔色的,RGB即是代表紅、綠、藍三個通道的顔色,這個标準幾乎包括了人類視力所能感覺的所有顔色,是運用最廣的顔色系統之一。
常用的RGB格式:
- RGB565:5位紅色+6位綠色+5位藍色=16位顔色值
- RGB888:8位紅色+8位綠色+8位藍色=24真彩色
- RGB32:RGB32是在RGB基礎上附加上Alpha(透明度)通道,RGB個占8位,剩餘8位用作Alpha通道。
3.攝像頭程式設計示例
3.1 攝像頭初始化
打開攝像頭裝置,設定視訊捕獲格式,設定圖像格式為YUYV422。
/*攝像頭初始化*/
int Camera_Init(void)
{
int i=0;
/*1.打開攝像頭裝置*/
int fd=open(VIDEO_DEV,2);
if(fd<0)return -1;//攝像頭打開失敗
/*2.設定攝像頭捕獲格式*/
struct v4l2_format v4l2fmt;
memset(&v4l2fmt,0,sizeof(v4l2fmt));//初始化結構體
v4l2fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//視訊捕獲格式
v4l2fmt.fmt.pix.width=1920;//圖像寬度
v4l2fmt.fmt.pix.height=1080;//圖像高度
v4l2fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;//YUYV顔色編碼格式
if(ioctl(fd,VIDIOC_S_FMT,&v4l2fmt))return -2;//設定格式失敗
printf("采集圖像大小:%d*%d\n",v4l2fmt.fmt.pix.width,v4l2fmt.fmt.pix.height);
imag_w=v4l2fmt.fmt.pix.width;
imag_h=v4l2fmt.fmt.pix.height;
/*3.申請緩沖區*/
struct v4l2_requestbuffers v4l2reqbuf;
memset(&v4l2reqbuf,0,sizeof(v4l2reqbuf));//初始化結構體
v4l2reqbuf.count=4;//申請的緩沖區個數
v4l2reqbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//視訊捕獲格式
v4l2reqbuf.memory=V4L2_MEMORY_MMAP;//記憶體映射
if(ioctl(fd,VIDIOC_REQBUFS,&v4l2reqbuf))return -3;//申請緩沖區失敗
printf("申請的緩沖區個數:%d\n",v4l2reqbuf.count);
/*4.将緩沖區映射到程序空間*/
struct v4l2_buffer v4l2buf;
memset(&v4l2buf,0,sizeof(v4l2buf));//初始化結構體
v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//視訊捕獲格式
v4l2buf.memory=V4L2_MEMORY_MMAP;//記憶體映射
for(i=0;i<v4l2reqbuf.count;i++)
{
v4l2buf.index=i;/*緩沖區下标*/
if(ioctl(fd,VIDIOC_QUERYBUF,&v4l2buf))return -4;//申請緩沖區失敗
video_buff[i]=mmap(NULL,v4l2buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,v4l2buf.m.offset);
printf("video_buff[%d]=%p\n",i,video_buff[i]);
}
/*5.将映射的空間添加到采集隊列*/
memset(&v4l2buf,0,sizeof(v4l2buf));//初始化結構體
v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//視訊捕獲格式
v4l2buf.memory=V4L2_MEMORY_MMAP;//記憶體映射
for(i=0;i<v4l2reqbuf.count;i++)
{
v4l2buf.index=i;/*緩沖區下标*/
if(ioctl(fd,VIDIOC_QBUF,&v4l2buf))return -5;//添加到采集隊列失敗
}
/*6.開啟攝像頭*/
int type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//視訊捕獲格式
if(ioctl(fd,VIDIOC_STREAMON,&type))return -6;//啟動攝像頭失敗
return fd;//成功傳回攝像頭檔案描述符
}
3.2 采集圖像資料
循環采集圖像資料,将一幀圖像資料儲存為BMP圖檔。
由于攝像頭圖像顔色格式為YUYV格式,BMP圖檔顔色格式為RGB,是以需要将YUYV轉RGB再寫入到檔案中。
- YUYV轉換RGB函數
/*YUYV轉RGB888*/
void yuv_to_rgb(unsigned char *yuv_buffer,unsigned char *rgb_buffer,int iWidth,int iHeight)
{
int x;
int z=0;
unsigned char *ptr = rgb_buffer;
unsigned char *yuyv= yuv_buffer;
for (x = 0; x < iWidth*iHeight; x++)
{
int r, g, b;
int y, u, v;
if (!z)
y = yuyv[0] << 8;
else
y = yuyv[2] << 8;
u = yuyv[1] - 128;
v = yuyv[3] - 128;
r = (y + (359 * v)) >> 8;
g = (y - (88 * u) - (183 * v)) >> 8;
b = (y + (454 * u)) >> 8;
*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
if(z++)
{
z = 0;
yuyv += 4;
}
}
}
- 圖像采集和BMP圖檔編碼
int main()
{
int fd=Camera_Init();
if(fd<0)
{
printf("初始化攝像頭失敗,res=%d\n",fd);
return 0;
}
printf("初始化攝像頭成功\n");
struct v4l2_buffer v4l2buf;
memset(&v4l2buf,0,sizeof(v4l2buf));//初始化結構體
v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//視訊捕獲格式
v4l2buf.memory=V4L2_MEMORY_MMAP;//記憶體映射
BMP_HEADER bmp_head;//bmp頭資料
BMP_INFO bmp_info;//位圖資料
memset(&bmp_head,0,sizeof(BMP_HEADER));
memset(&bmp_info,0,sizeof(BMP_INFO));
bmp_head.bfType='M'<<8|'B';//圖檔類型
bmp_head.bfSize=imag_w*imag_h*3+sizeof(BMP_HEADER)+sizeof(BMP_INFO);
bmp_head.bfOffBits=sizeof(BMP_INFO)+sizeof(BMP_HEADER);/*RGB顔色偏移量*/
bmp_info.biSize=sizeof(BMP_INFO);//目前結構體大小
bmp_info.biWidth=imag_w;//圖檔寬
bmp_info.biHeight=imag_h;//圖檔高
bmp_info.biPlanes=1;//固定為1
bmp_info.biBitCount=24;//24位真彩色
char *rgb=malloc(imag_w*imag_h*3);//存放rgb圖像資料
int count=1;
char buff[20];
FILE *fp;
while(1)
{
/*從采集隊列中取出圖像資料*/
if(ioctl(fd,VIDIOC_DQBUF,&v4l2buf))break;//取資料失敗
printf("v4l2buff[%d]=%p\n",v4l2buf.index,video_buff[v4l2buf.index]);
/*将頭資料和位圖資料寫入到檔案中*/
snprintf(buff,sizeof(buff),"./image/%d.bmp",count);//圖檔名字
fp=fopen(buff,"w+b");
if(fp==NULL)
{
printf("檔案建立失敗\n");
continue;
}
count++;
fwrite(&bmp_head,sizeof(BMP_HEADER),1,fp);//寫頭資料
fwrite(&bmp_info,sizeof(BMP_INFO),1,fp);//寫頭資料
/*将yuyv資料轉換RGB888*/
yuv_to_rgb(video_buff[v4l2buf.index],rgb,imag_w,imag_h);
/*将顔色資料寫入到檔案中*/
fwrite(rgb,imag_w*imag_h*3,1,fp);//寫頭資料
fclose(fp);
/*将緩沖區添加回采集隊列中*/
if(ioctl(fd,VIDIOC_QBUF,&v4l2buf))break;//添加到采集隊列失敗
}
close(fd);//關閉攝像頭
}