1四、USB攝像頭(V4L2接口)的圖片採集

參考網站http://www.cnblogs.com/surpassal/archive/2012/12/19/zed_webcam_lab1.htmlhtml

1、一些知識linux

 一、V4L和V4L2。web

V4L是Linux環境下開發視頻採集設備驅動程序的一套規範(API),它爲驅動程序的編寫提供統一的接口,並將全部的視頻採集設備的驅動程序都歸入其的管理之中。V4L不只給驅動程序編寫者帶來極大的方便,同時也方便了應用程序的編寫和移植。V4L2是V4L的升級版,因爲咱們使用的OOB是3.3的內核,再也不支持V4L,於是編程再也不考慮V4L的api和參數定義。編程

二、YUYV與RGB24api

RGB是一種顏色的表示法,計算機中通常採用24位來存儲,每一個顏色佔8位。YUV也是一種顏色空間,爲何要出現YUV,主要有兩個緣由,一個是爲了讓彩色信號兼容黑白電視機,另一個緣由是爲了減小傳輸的帶寬。YUV中,Y表示亮度,U和V表示色度,總之它是將RGB信號進行了一種處理,根據人對亮度更敏感些,增長亮度的信號,減小顏色的信號,以這樣「欺騙」人的眼睛的手段來節省空間。YUV到RGB顏色空間轉換關係是:緩存

R = Y + 1.042*(V-128);
G = Y - 0.34414*(U-128) - 0.71414*(V-128);
B = Y + 1.772*(U-128);

YUV的格式也不少,不過常見的就是42二、420等。YUYV就是422形式,簡單來講就是,兩個像素點P一、P2本應該有Y一、U一、V1和Y二、U二、V2這六個份量,可是實際只保留Y一、U一、Y二、V2。ide

圖1 YUYV像素函數

 

2、應用程序設計測試

先定義一些宏和結構體,方便後續編程網站

 1 #define  TRUE    1
 2 #define  FALSE    0
 3 
 4 #define FILE_VIDEO     "/dev/video0"
 5 #define BMP          "/usr/image_bmp.bmp"
 6 #define YUV            "/usr/image_yuv.yuv"
 7 
 8 #define  IMAGEWIDTH    640
 9 #define  IMAGEHEIGHT   480
10 
11 static   int fd; 12 static   struct v4l2_capability cap; 13 struct v4l2_fmtdesc fmtdesc; 14 struct v4l2_format fmt,fmtack; 15 struct v4l2_streamparm setfps; 16 struct v4l2_requestbuffers req; 17 struct v4l2_buffer buf; 18 enum v4l2_buf_type type; 19 unsigned char frame_buffer[IMAGEWIDTH*IMAGEHEIGHT*3];

其中

#define FILE_VIDEO     "/dev/video0"

是要訪問的攝像頭設備,默人都是/dev/video0

#define BMP          "/usr/image_bmp.bmp"
#define YUV          "/usr/image_yuv.yuv"

是採集後存儲的圖片,爲了方便測試,這裏將直接獲取的yuv格式數據也保存成文件,能夠經過yuvviewer等查看器查看。

static   int fd; static   struct v4l2_capability cap; struct v4l2_fmtdesc fmtdesc; struct v4l2_format fmt,fmtack; struct v4l2_streamparm setfps; struct v4l2_requestbuffers req; struct v4l2_buffer buf; enum v4l2_buf_type type;

這些結構體的定義均可以從/usr/include/linux/videodev2.h中找到定義,具體含義在後續編程會作相應解釋。

#define  IMAGEWIDTH    640
#define  IMAGEHEIGHT   480

爲採集圖像的大小。

定義一個frame_buffer,用來緩存RGB顏色數據

unsigned char frame_buffer[IMAGEWIDTH*IMAGEHEIGHT*3]

 這些宏和定義結束後,就能夠開始編程配置攝像頭並採集圖像了。通常來講V4L2採集視頻數據分爲五個步驟:首先,打開視頻設備文件,進行視頻採集的參數初始化,經過V4L2接口設置視頻圖像的採集窗口、採集的點陣大小和格式;其次,申請若干視頻採集的幀緩衝區,並將這些幀緩衝區從內核空間映射到用戶空間,便於應用程序讀取/處理視頻數據;第三,將申請到的幀緩衝區在視頻採集輸入隊列排隊,並啓動視頻採集;第四,驅動開始視頻數據的採集,應用程序從視頻採集輸出隊列取出幀緩衝區,處理完後,將幀緩衝區從新放入視頻採集輸入隊列,循環往復採集連續的視頻數據;第五,中止視頻採集。在本次設計中,定義了三個函數實現對攝像頭的配置和採集。

int init_v4l2(void);
int v4l2_grab(void);
int close_v4l2(void);

同時因爲採集到的圖像數據是YUYV格式,須要進行顏色空間轉換,定義了轉換函數。

int yuyv_2_rgb888(void);

下面就詳細介紹這幾個函數的實現。

一、初始化V4l2

(1)打開視頻。linux對攝像頭的訪問和普通設備同樣,使用open函數就能夠,返回值是設備的id。

1 if ((fd = open(FILE_VIDEO, O_RDWR)) == -1) 
2 {
3     printf("Error opening V4L interface\n");
4     return (FALSE);
5 }

(2)讀video_capability中信息。經過調用IOCTL函數和接口命令VIDIOC_QUERYCAP查詢攝像頭的信息,結構體v4l2_capability中有包括驅動名稱driver、card、bus_info、version以及屬性capabilities。這裏咱們須要檢查一下是不是爲視頻採集設備V4L2_CAP_VIDEO_CAPTURE以及是否支持流IO操做V4L2_CAP_STREAMING。

 1 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)  2 {  3     printf("Error opening device %s: unable to query device.\n",FILE_VIDEO);  4     return (FALSE);  5 }  6 else
 7 {  8      printf("driver:\t\t%s\n",cap.driver);  9      printf("card:\t\t%s\n",cap.card); 10      printf("bus_info:\t%s\n",cap.bus_info); 11      printf("version:\t%d\n",cap.version); 12      printf("capabilities:\t%x\n",cap.capabilities); 13      
14      if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) 15  { 16         printf("Device %s: supports capture.\n",FILE_VIDEO); 17  } 18 
19     if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING) 20  { 21         printf("Device %s: supports streaming.\n",FILE_VIDEO); 22  } 23 }

(3)列舉攝像頭所支持像素格式。使用命令VIDIOC_ENUM_FMT,獲取到的信息經過結構體v4l2_fmtdesc查詢。這步很關鍵,不一樣的攝像頭可能支持的格式不同,V4L2能夠支持的格式不少,/usr/include/linux/videodev2.h文件中能夠看到。

1 fmtdesc.index=0; 2 fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; 3 printf("Support format:\n"); 4 while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1) 5 { 6     printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description); 7     fmtdesc.index++; 8 }

(4)設置像素格式。通常的USB攝像頭都會支持YUYV,有些還支持其餘的格式。經過前一步對攝像頭所支持像素格式查詢,下面須要對格式進行設置。命令爲VIDIOC_S_FMT,經過結構體v4l2_format把圖像的像素格式設置爲V4L2_PIX_FMT_YUYV,高度和寬度設置爲IMAGEHEIGHT和IMAGEWIDTH。通常狀況下一個攝像頭所支持的格式是不能夠隨便更改的,我嘗試把把一個只支持YUYV和MJPEG的攝像頭格式改成RGB24或者JPEG,都沒有成功。

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; fmt.fmt.pix.height = IMAGEHEIGHT; fmt.fmt.pix.width = IMAGEWIDTH; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) { printf("Unable to set format\n"); return FALSE; }


爲了確保設置的格式做用到攝像頭上,再經過命令VIDIOC_G_FMT將攝像頭設置讀取回來。

 1 if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1)  2 {  3     printf("Unable to get format\n");  4     return FALSE;  5 }  6 {  7      printf("fmt.type:\t\t%d\n",fmt.type);  8      printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);  9      printf("pix.height:\t\t%d\n",fmt.fmt.pix.height); 10      printf("pix.width:\t\t%d\n",fmt.fmt.pix.width); 11      printf("pix.field:\t\t%d\n",fmt.fmt.pix.field); 12 }

完整的初始化代碼以下:

 1 int init_v4l2(void)  2 {  3     int i;  4     int ret = 0;  5     
 6     //opendev
 7     if ((fd = open(FILE_VIDEO, O_RDWR)) == -1)  8  {  9         printf("Error opening V4L interface\n"); 10         return (FALSE); 11  } 12 
13     //query cap
14     if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) 15  { 16         printf("Error opening device %s: unable to query device.\n",FILE_VIDEO); 17         return (FALSE); 18  } 19     else
20  { 21          printf("driver:\t\t%s\n",cap.driver); 22          printf("card:\t\t%s\n",cap.card); 23          printf("bus_info:\t%s\n",cap.bus_info); 24          printf("version:\t%d\n",cap.version); 25          printf("capabilities:\t%x\n",cap.capabilities); 26          
27          if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) 28  { 29             printf("Device %s: supports capture.\n",FILE_VIDEO); 30  } 31 
32         if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING) 33  { 34             printf("Device %s: supports streaming.\n",FILE_VIDEO); 35  } 36  } 37     
38     //emu all support fmt
39     fmtdesc.index=0; 40     fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; 41     printf("Support format:\n"); 42     while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1) 43  { 44         printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description); 45         fmtdesc.index++; 46  } 47     
48     //set fmt
49     fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 50     fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; 51     fmt.fmt.pix.height = IMAGEHEIGHT; 52     fmt.fmt.pix.width = IMAGEWIDTH; 53     fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; 54     
55     if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) 56  { 57         printf("Unable to set format\n"); 58         return FALSE; 59  } 60     if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) 61  { 62         printf("Unable to get format\n"); 63         return FALSE; 64  } 65  { 66          printf("fmt.type:\t\t%d\n",fmt.type); 67          printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF); 68          printf("pix.height:\t\t%d\n",fmt.fmt.pix.height); 69          printf("pix.width:\t\t%d\n",fmt.fmt.pix.width); 70          printf("pix.field:\t\t%d\n",fmt.fmt.pix.field); 71  } 72     //set fps
73     setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 74     setfps.parm.capture.timeperframe.numerator = 10; 75     setfps.parm.capture.timeperframe.denominator = 10; 76     
77     printf("init %s \t[OK]\n",FILE_VIDEO); 78         
79     return TRUE; 80 }

二、圖像採集

(1)申請緩存區。使用參數VIDIOC_REQBUFS和結構體v4l2_requestbuffers。v4l2_requestbuffers結構中定義了緩存的數量,系統會據此申請對應數量的視頻緩存。

req.count=4; req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory=V4L2_MEMORY_MMAP; if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1) { printf("request for buffers error\n"); }

(2)獲取每一個緩存的信息,並mmap到用戶空間。定義結構體

struct buffer
{
    void * start;
    unsigned int length;
} * buffers;

來存儲mmap後的地址信息。須要說明的是因爲mmap函數定義時返回的地址是個void *,於是這裏面的start也是個 void *。實際地址在運行的時候會自動分配。

 1 for (n_buffers = 0; n_buffers < req.count; n_buffers++)  2 {  3     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  4     buf.memory = V4L2_MEMORY_MMAP;  5     buf.index = n_buffers;  6     //query buffers
 7     if (ioctl (fd, VIDIOC_QUERYBUF, &buf) == -1)  8  {  9         printf("query buffer error\n"); 10         return(FALSE); 11  } 12 
13     buffers[n_buffers].length = buf.length; 14     //map
15     buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ |PROT_WRITE, MAP_SHARED, fd, buf.m.offset); 16     if (buffers[n_buffers].start == MAP_FAILED) 17  { 18         printf("buffer map error\n"); 19         return(FALSE); 20  } 21 }

(3) 以後就能夠開始採集視頻了。使用命令VIDIOC_STREAMON。

1 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
2 ioctl (fd, VIDIOC_STREAMON, &type);

(4)取出緩存中已經採樣的緩存。使用命令VIDIOC_DQBUF。視頻數據存放的位置是buffers[n_buffers].start的地址處。

1 ioctl(fd, VIDIOC_DQBUF, &buf);

完整的採集代碼:

 1 int v4l2_grab(void)  2 {  3     unsigned int n_buffers;  4     
 5     //request for 4 buffers 
 6     req.count=4;  7     req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;  8     req.memory=V4L2_MEMORY_MMAP;  9     if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1) 10  { 11         printf("request for buffers error\n"); 12  } 13 
14     //mmap for buffers
15     buffers = malloc(req.count*sizeof (*buffers)); 16     if (!buffers) 17  { 18         printf ("Out of memory\n"); 19         return(FALSE); 20  } 21     
22     for (n_buffers = 0; n_buffers < req.count; n_buffers++) 23  { 24         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 25         buf.memory = V4L2_MEMORY_MMAP; 26         buf.index = n_buffers; 27         //query buffers
28         if (ioctl (fd, VIDIOC_QUERYBUF, &buf) == -1) 29  { 30             printf("query buffer error\n"); 31             return(FALSE); 32  } 33 
34         buffers[n_buffers].length = buf.length; 35         //map
36         buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ |PROT_WRITE, MAP_SHARED, fd, buf.m.offset); 37         if (buffers[n_buffers].start == MAP_FAILED) 38  { 39             printf("buffer map error\n"); 40             return(FALSE); 41  } 42  } 43         
44     //queue
45     for (n_buffers = 0; n_buffers < req.count; n_buffers++) 46  { 47         buf.index = n_buffers; 48         ioctl(fd, VIDIOC_QBUF, &buf); 49  } 50     
51     type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 52     ioctl (fd, VIDIOC_STREAMON, &type); 53     
54     ioctl(fd, VIDIOC_DQBUF, &buf); 55 
56     printf("grab yuyv OK\n"); 57     return(TRUE); 58 }

三、YUYV轉RGB24

因爲攝像頭採集的數據格式爲YUYV,爲了方便後續設計,須要轉變爲RGB24,並將轉換完成的數據存儲到frame_buffer中。值得一提的是,因爲定義的時候buffers[index].start是個void *,沒有辦法進行+1這樣的操做,須要強制轉換爲

char * pointer
pointer = buffers[0].start

因爲後續RGB的數據要存儲到BMP中,而BMP文件中顏色數據是「倒序」,即從下到上,從左到右,於是在向frame_buffer寫數據時是從最後一行最左測開始寫,每寫滿一行行數減一。

 1 int yuyv_2_rgb888(void)  2 {  3     int i,j;  4     unsigned char y1,y2,u,v;  5     int r1,g1,b1,r2,g2,b2;  6     char * pointer;  7     
 8     pointer = buffers[0].start;  9     
10     for(i=0;i<480;i++) 11  { 12         for(j=0;j<320;j++) 13  { 14             y1 = *( pointer + (i*320+j)*4); 15             u  = *( pointer + (i*320+j)*4 + 1); 16             y2 = *( pointer + (i*320+j)*4 + 2); 17             v  = *( pointer + (i*320+j)*4 + 3); 18             
19             r1 = y1 + 1.042*(v-128); 20             g1 = y1 - 0.34414*(u-128) - 0.71414*(v-128); 21             b1 = y1 + 1.772*(u-128); 22             
23             r2 = y2 + 1.042*(v-128); 24             g2 = y2 - 0.34414*(u-128) - 0.71414*(v-128); 25             b2 = y2 + 1.772*(u-128); 26             
27             if(r1>255) 28                 r1 = 255; 29             else if(r1<0) 30                 r1 = 0; 31             
32             if(b1>255) 33                 b1 = 255; 34             else if(b1<0) 35                 b1 = 0; 36             
37             if(g1>255) 38                 g1 = 255; 39             else if(g1<0) 40                 g1 = 0; 41                 
42             if(r2>255) 43                 r2 = 255; 44             else if(r2<0) 45                 r2 = 0; 46             
47             if(b2>255) 48                 b2 = 255; 49             else if(b2<0) 50                 b2 = 0; 51             
52             if(g2>255) 53                 g2 = 255; 54             else if(g2<0) 55                 g2 = 0; 56                 
57             *(frame_buffer + ((480-1-i)*320+j)*6    ) = (unsigned char)b1; 58             *(frame_buffer + ((480-1-i)*320+j)*6 + 1) = (unsigned char)g1; 59             *(frame_buffer + ((480-1-i)*320+j)*6 + 2) = (unsigned char)r1; 60             *(frame_buffer + ((480-1-i)*320+j)*6 + 3) = (unsigned char)b2; 61             *(frame_buffer + ((480-1-i)*320+j)*6 + 4) = (unsigned char)g2; 62             *(frame_buffer + ((480-1-i)*320+j)*6 + 5) = (unsigned char)r2; 63  } 64  } 65     printf("change to RGB OK \n"); 66 }

四、中止採集和關閉設備

使用命令VIDIOC_STREAMOFF中止視頻採集,並關閉設備。

 1 int close_v4l2(void)  2 {  3     ioctl(fd, VIDIOC_STREAMOFF, &buf_type);  4     if(fd != -1)  5  {  6  close(fd);  7          return (TRUE);  8  }  9      return (FALSE); 10 }

五、主函數

須要把咱們採集到圖像數據存儲成圖片,爲了方便調試,先將原始的數據存儲爲yuv格式文件,再將轉換成RGB後的數據存儲爲BMP。定義BMP頭結構體

 1 typedef struct tagBITMAPFILEHEADER{  2      WORD    bfType;                // the flag of bmp, value is "BM"
 3      DWORD    bfSize;                // size BMP file ,unit is bytes
 4      DWORD    bfReserved;            // 0
 5      DWORD    bfOffBits;             // must be 54
 6 
 7 }BITMAPFILEHEADER;  8 
 9  
10 typedef struct tagBITMAPINFOHEADER{ 11      DWORD    biSize;                // must be 0x28
12      DWORD    biWidth;           // 13      DWORD    biHeight;          // 14      WORD        biPlanes;          // must be 1
15      WORD        biBitCount;            // 16      DWORD    biCompression;         // 17      DWORD    biSizeImage;       // 18      DWORD    biXPelsPerMeter;   // 19      DWORD    biYPelsPerMeter;   // 20      DWORD    biClrUsed;             // 21      DWORD    biClrImportant;        // 22 }BITMAPINFOHEADER;

完整的主函數

{ FILE * fp1,* fp2; BITMAPFILEHEADER bf; BITMAPINFOHEADER bi; fp1 = fopen(BMP, "wb"); if(!fp1) { printf("open "BMP"error\n"); return(FALSE); } fp2 = fopen(YUV, "wb"); if(!fp2) { printf("open "YUV"error\n"); return(FALSE); } if(init_v4l2() == FALSE) { return(FALSE); } //Set BITMAPINFOHEADER
    bi.biSize = 40; bi.biWidth = IMAGEWIDTH; bi.biHeight = IMAGEHEIGHT; bi.biPlanes = 1; bi.biBitCount = 24; bi.biCompression = 0; bi.biSizeImage = IMAGEWIDTH*IMAGEHEIGHT*3; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; //Set BITMAPFILEHEADER
    bf.bfType = 0x4d42; bf.bfSize = 54 + bi.biSizeImage; bf.bfReserved = 0; bf.bfOffBits = 54; v4l2_grab(); fwrite(buffers[0].start, 640*480*2, 1, fp2); printf("save "YUV"OK\n"); yuyv_2_rgb888(); fwrite(&bf, 14, 1, fp1); fwrite(&bi, 40, 1, fp1); fwrite(frame_buffer, bi.biSizeImage, 1, fp1); printf("save "BMP"OK\n"); fclose(fp1); fclose(fp2); close_v4l2(); return(TRUE); }

3、ARM板測試

PC上測試OK後,能夠「挪」到ZedBoard上了。使用arm-xilinx-linux交叉編譯環境對源文件進行交叉編譯,將生成的可執行文件拷貝到ZedBoard上運行便可。

使用命令

arm-xilinx-linux-gnueabi-gcc v4l2grab.c -o zed-camera

對程序進行編譯,編譯經過後將生成的可執行文件zed-camera拷貝到到ZedBoard上,並將USB攝像頭鏈接到ZedBoard上,經過命令

ls /dev

 查看dev目錄下的是否有video0設備。若是有,能夠運行可執行文件了。在運行前我比較習慣得到可執行文件的權限,使用命令

chmod +x zed-camera

參數+x的意思是這個文件對於當前用戶是可執行的。也可使用

chmod 777 zed-camera

這樣全部用戶都有讀寫執行的權限。使用命令

./zed-camera

執行可執行程序,程序運行,並輸出如下信息:

driver: uvcvideo
card: UVC Camera (046d:0825)
bus_info: usb-xusbps-ehci.0-1.3
version: 197376
capabilities: 4000001
Device /dev/video0: supports capture.
Device /dev/video0: supports streaming.
Support format:
1.YUV 4:2:2 (YUYV)
2.MJPEG
fmt.type: 1
pix.pixelformat: YUYV
pix.height: 480
pix.width: 640
pix.field: 1
init /dev/video0 [OK]
grab yuyv OK
save /usr/image_yuv.yuv OK
change to RGB OK
save /usr/image_bmp.bmp OK

採集到的圖片默認是在/usr目錄下的,將其拷貝出來
cp /usr/image* /mnt
 
完整工程和代碼:https://files.cnblogs.com/files/liusiluandzhangkun/lab_v4l2_yuyv.zip
能夠指定任意分辨率攝像頭的代碼:https://files.cnblogs.com/files/liusiluandzhangkun/v4l2grab_Anysize.rar
相關文章
相關標籤/搜索