(原創)基於ZedBoard的Webcam設計(一):USB攝像頭(V4L2接口)的圖片采集


一直想把USB攝像頭接到Zedboard上,搭建一個簡易視頻監控系統方便后續做視頻處理。Xilinx官方給出了一個Webcam攝像頭監控的例子,跑的是linaro,不知道是我的SD卡問題還是攝像頭的問題,播放視頻的時候總是會很卡,而且突然系統就死掉了。還是很喜歡自己動手,能學到新東西。Digilent官方給的OOB設計,那個精簡的linux足夠做簡單的linux開發了,而且OOB設計中USB驅動和V4L(Video for Linux)都提供好了。這幾天找了一些的V4L的資料,完成了攝像頭的單幀圖片采集,接下來要做的是QT界面顯示和視頻流的顯示了,最終的計划是完成視頻采集、編碼、存儲和以太網傳輸。希望能有時間和精力完成這么多。先把這幾天的做的東西整理出來和大家分享。為了方便大家對程序的理解,先簡單介紹一下linux下的V4L2的一些知識,然后再詳細介紹V4L2編程。

更多更新請關注我的博客:@超群天晴 http://www.cnblogs.com/surpassal/  

相關閱讀 : 

(原創)基於ZedBoard的Webcam設計(一):USB攝像頭(V4L2接口)的圖片采集  

(原創)基於ZedBoard的Webcam設計(二):USB攝像頭圖片采集+QT顯示  

(原創)基於ZedBoard的Webcam設計(三):視頻的采集和動態顯示  

(原創)基於ZedBoard的Webcam設計(四):MJPG編碼和AVI封裝  

(原創)基於ZedBoard的Webcam設計(五):x264編碼在zedboard上的實現(軟編碼)

 

硬件平台:Digilent ZedBoard + USB 攝像頭

開發環境:Windows XP 32 bit + Wmare 8.0 + Ubuntu 10.04 + arm-linux-xilinx-gnueabi交叉編譯環境

Zedboard linux: Digilent OOB Design 

 

一、一些知識

 1、V4L和V4L2。

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

2、YUYV與RGB24

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的格式也很多,不過常見的就是422、420等。YUYV就是422形式,簡單來說就是,兩個像素點P1、P2本應該有Y1、U1、V1和Y2、U2、V2這六個分量,但是實際只保留Y1、U1、Y2、V2。

圖1 YUYV像素

 
        

二、應用程序設計

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

 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);

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

1、初始化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,都沒有成功。

 1 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 2 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
 3 fmt.fmt.pix.height = IMAGEHEIGHT;
 4 fmt.fmt.pix.width = IMAGEWIDTH;
 5 fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
 6 
 7 if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
 8 {
 9     printf("Unable to set format\n");
10     return FALSE;
11 }     

為了確保設置的格式作用到攝像頭上,再通過命令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 }

完整的初始化代碼如下:

View Code
 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 }

2、圖像采集

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

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

(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);

完整的采集代碼:

View Code
 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 }

3、YUYV轉RGB24

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

char * pointer
pointer = buffers[0].start

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

View Code
 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 }

4、停止采集和關閉設備

使用命令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 }

5、主函數

需要把我們采集到圖像數據存儲成圖片,為了方便調試,先將原始的數據存儲為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;

完整的主函數

//@超群天晴
//http://www.cnblogs.com/surpassal/
int
main(void) { 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); }

 三、PC測試

程序編寫完后,可以先在PC上做測試(實際整個調試過程都是在PC上,直道最后PC上能實現功能再挪到ZedBoard上的)。PC上測試的結果

 在/usr目錄下可以查看到采集到的圖片

四、Zedboard測試

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

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

zynq> ./zed-camera
[  318.290000] usb 1-1.3: reset high-speed USB device number 3 using xusbps-ehci

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

可以看到我使用的USB攝像支持YUYV和MJPEG兩種格式。我也試過其他USB攝像頭,大部分都只支持YUYV而不支持MJPEG或者RGB24。

采集到的圖片默認是在/usr目錄下的,將其拷貝出來

cp /usr/image* /mnt

再PC上查看,效果還不錯

=============================

完整工程和代碼:lab_v4l2_yuyv.zip

可以指定任意分辨率攝像頭的代碼:v4l2grab_Anysize.rar 《感謝@jiaquwang分享》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM