近年來,隨著計算機、網絡以及圖像處理、傳輸技術的飛速發展,攝像頭在工業控制領域的應用也越來越廣泛了,目前市面上的攝像頭可以分為兩類,一種是符合UVC規范的攝像頭,比如羅技的攝像頭就是UVC攝像頭。另一種是non-UVC攝像頭,即不符合UVC規范。UVC全稱為:USB video class (USB視頻類)在Linux-2.6.4及以上的版本都已經集成了UCV設備的驅動,而non-UVC攝像頭如果要使用,就需要硬件廠商提供專用的驅動。比如中星微的攝像頭就是non-UVC設備,需要專用的驅動。
1、Linux內核配置
本文以英創嵌入式板卡EM335x 為例來介紹對于USB攝像頭的支持,EM335x內核版本為Linux-3.12.10,USB攝像頭選用中星微的ZC301攝像頭,該攝像頭以其高性價比得以廣泛應用,同時在Linux內核中已經包括了對于ZC3XX系列攝像頭的驅動支持。
內核配置如下:
<*> Multimedia support --->
[*] Cameras/video grabbers support
[*] Media USB Adapters --->
<*> USB Video Class (UVC)
[*] UVC input events device support
<*> GSPCA based webcams --->
ZC3XX USB Camera Driver
編譯成功后,即可得到zc3xx系列USB攝像頭驅動文件:gspca_zc3xx.ko。
在EM335x板卡上,該文件放置在根文件系統/lib/modules/3.12.10/目錄下。應用時只需調用以下命令,即可完成對于USB攝像頭的驅動加載。
insmod /lib/modules/3.12.10/gspca_zc3xx.ko
驅動加載成功后,會自動生成設備節點:“/dev/video0',應用程序可以操作該設備節點對攝像頭進行圖像的采集和控制。因為中星微的攝像頭為non-UVC設備,所以需要再加專用的gspca_zc3xx.ko,如果是其他的UVC攝像頭,內核中已經集成了驅動,插上后就可以識別出來,不用再加載其他驅動。
2、Qt攝像頭應用程序簡介
UVC和non-UVC攝像頭都是用了V4L2驅動提供的API來操作攝像頭。Video for Linux two簡稱V4L2,是V4L的改進版。V4L2是Linux操作系統下用于采集圖片、視頻和音頻數據的API接口,配合適當的視頻采集設備和相應的驅動程序,可以實現圖片、視頻、音頻等的采集。在視頻監控系統和嵌入式多媒體終端中都有廣泛的應用。V4L2支持兩種方式來采集圖像:內存映射方式(mmap)和直接讀取方式(read)。在這里我們使用內存映射的方式來進行視頻采集。應用程序通過V4L2接口采集視頻數據可以分為五個步驟:
①打開視頻設備文件,進行視頻采集的參數初始化,通過V4L2接口設置視頻圖像的采集窗口、采集的點陣大小和格式;
②申請若干視頻采集的幀緩沖區,并將這些幀緩沖區從內核空間映射到用戶空間,便于應用程序讀取/處理視頻數據;
③將申請到的幀緩沖區在視頻采集輸入隊列排隊,并啟動視頻采集;
④驅動開始視頻數據的采集,應用程序從視頻采集輸出隊列取出幀緩沖區,處理完后,將幀緩沖區重新放入視頻采集輸入隊列,循環往復采集連續的視頻數據;
⑤停止視頻采集。
可以參考下圖:

可以看到每一個步驟都是通過ioctl這個接口去設置一些參數來實現的, 啟動視頻采集后,驅動程序開始采集數據,并把采集的數據放入視頻采集輸入隊列的第一個幀緩沖區,當一幀數據采集完成,也就是第一個幀緩沖區存滿數據以后,驅動程序將這一個緩沖區移至視頻采集輸出隊列,等待應用程序取出。驅動程序接下來繼續采集下一幀數據,并放入第二個幀緩沖區,同樣幀緩沖區存滿數據后,被放入視頻采集輸出隊列。
應用程序從視頻采集輸出隊列中取出含有視頻數據的幀緩沖區,處理幀緩沖區中的視頻數據,如存儲或壓縮。如果需要連續采集,應用程序需要將處理完數據的幀緩沖區重新放入視頻采集輸入隊列,如圖所示。

接下來結合程序來具體看一看通過V4L2接口來操作攝像頭的一些重要的步驟:
打開設備文件:
int fd;
fd=open('/dev/video0',O_RDWR);
獲取設備的基本信息,包括驅動版本號,設備支持操作等:
struct v4l2_capability cap;
ret=ioctl(fd,VIDIOC_QUERYCAP,&cap);
if(ret<0)
{
printf('failture VIDIOC_QUERYCAP
');
return -1;
}
printf('DriverName:%s
Card Name:%s
Bus info:%s
DriverVersion:%u.%u.%u
',cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0xFF,(cap.version>>8)&0xFF,cap.version&0xFF);
顯示所支持的格式:
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.index = 0;
//數據流類型,必須永遠是V4L2_BUF_TYPE_VIDEO_CAPTURE
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
{
printf('/t%d.%s/n',fmtdesc.index+1,fmtdesc.description);
fmtdesc.index++;
}
設置視頻的制式和幀格式,制式包括PAL,NTSC,幀的格式個包括寬度和高度等:
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//數據流類型,必須永遠是V4L2_BUF_TYPE_VIDEO_CAPTURE
fmt.fmt.pix.width = 640;//寬,必須是16的倍數
fmt.fmt.pix.height = 480;//高,必須是16的倍數
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;//視頻數據存儲類型//V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YVU420;//V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
//設置當前驅動的頻捕獲格式
ret = ioctl (fd, VIDIOC_S_FMT, &fmt);
if(ret<0)
{
printf('failture VIDIOC_S_FMT
');
return -1;
}
向驅動申請幀緩沖,一般不超過五個:
struct v4l2_requestbuffers req;
req.count=1;
req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory=V4L2_MEMORY_MMAP;
//申請幀緩沖
ret=ioctl(fd,VIDIOC_REQBUFS,&req);
if(ret<0)
{
printf('failture VIDIOC_REQBUFS
');
return -1;
}
if (req.count < 1)
{
printf('Insufficient buffer memory
');
return -1;
}
將申請到的幀緩沖映射到用戶空間,這樣就能夠直接操作幀緩沖了:
buffers =(buffer*)calloc (req.count, sizeof (*buffers));
if (!buffers) {
fprintf (stderr,'Out of memory/n');
exit(EXIT_FAILURE);
}
for (n_buffers = 0; n_buffers < req.count; ++n_buffers)
{
struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory =V4L2_MEMORY_MMAP;
buf.index =n_buffers;
//查詢序號為n_buffers 的緩沖區,得到其起始物理地址和大小
if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
{
printf('failture VIDIOC_QUERYBUF
');
return -1;
}
buffers[n_buffers].length= buf.length;
//映射內存
buffers[n_buffers].start=mmap (NULL,buf.length,PROT_READ | PROT_WRITE ,MAP_SHARED,fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
{
printf('failture mmap
');
return -1;
}
}
將申請到的幀緩沖全部入隊列,以便存放采集到的數據:
for (i = 0; i< req.count; ++i)
{
struct v4l2_buffer buffer;
buffer.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory =V4L2_MEMORY_MMAP;
buffer.index = i;
//將緩沖幀放入隊列尾
ioctl (fd,VIDIOC_QBUF, &buffer);
}
開始視頻的采集:
type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl (fd,VIDIOC_STREAMON, &type);
取出隊列中以取得采集數據的幀緩沖,獲得原始采集數據,因為這個攝像頭支持的格式為JPG,所以程序中將原始數據保存在新建的一個*.jpg文件中:
struct v4l2_buffer camera_buf;
CLEAR (camera_buf);
camera_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
camera_buf.memory = V4L2_MEMORY_MMAP;
//取出一個緩沖幀
i1 = ioctl (fd, VIDIOC_DQBUF, &camera_buf);
if(i1<0)
{
printf('failture
');
return -1;
}
fwrite(buffers[camera_buf.index].start, buffers[camera_buf.index].length, 1, file_fd);//將其寫入文件中
將緩沖幀重新入隊列尾,這樣可以循環采集:
//將緩沖重新入隊列尾
i1=ioctl (fd, VIDIOC_QBUF, &camera_buf);
if(i1<0)
{
printf('failture VIDIOC_QBUF
');
return -1;
}
如果需要關閉攝像頭,先停止視屏采集,釋放申請的幀緩沖,最后關閉設備節點:
//停止視頻的采集。VIDIOC_STREAMOFF
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))
printf('VIDIOC_STREAMOFF');
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap (buffers->start, buffers->length))
printf ('munmap error');
free(buffers);
//關閉視頻設備
close (fd);
所以通過這一套通用的V4L2接口來操作攝像頭的工作流程:
打開設備-> 檢查和設置設備屬性->設置幀格式-> 設置一種輸入輸出方法(緩沖區管理)-> 循環獲取數據-> 關閉設備。通過這幾個步驟已經可以操作攝像頭來獲取數據,下面來看看如何與Qt結合,將前面的代碼與Qt界面結合起來。
在Qt中主要就是實現兩個功能,一個是通過界面控制攝像頭的數據獲取,另一個是通過界面顯示攝像頭所拍攝下來的圖片。攝像頭的初始化設置,包括格式等參數的設置可以在Qt界面的構造函數中完成。
通過界面來控制攝像頭,可以在Qt的界面上做一個按鈕,在按鈕的單擊事件槽中調用攝像頭采集數據的部分即可:
void MainWindow::on_init_camera_clicked()//按鈕單擊事件
{
for (;;)//這一段涉及到異步IO
{
fd_set fds;
struct timeval tv;
int r;
FD_ZERO (&fds);//將指定的文件描述符集清空
FD_SET (fd, &fds);//在文件描述符集合中增加新的文件描述符
tv.tv_sec = 0;
tv.tv_usec = 500000;
r = select (fd + 1, &fds, NULL, NULL, &tv);//判斷是否可讀(即攝像頭是否準備好),tv是定時
if (-1 == r)
{
if (EINTR == errno)
continue;
printf ('select err
');
}
if (read_frame ())//如果可讀,執行read_frame ()函數,并跳出循環
break;
else
{
QMessageBox::information(this, tr('失敗'), tr('拍攝圖片失敗') , QMessageBox::Ok);
}
}
}
關于拍攝圖片的顯示問題,Qt中提供了很多實現的方法,比如可以在界面中采用一個label來顯示,這里采用GraphicsView來顯示,主要代碼如下:
image=new QImage(pictrue_name);
image->load(pictrue_name);
scene = new QGraphicsScene;
scene->addPixmap(QPixmap::fromImage(*image));
ui->graphicsView->setScene(scene);
ui->graphicsView->setAlignment(Qt::AlignCenter);
ui->graphicsView->show();//顯示
將攝像頭獲取的數據寫入文件中,再通過GraphicsView顯示出來。這樣就實現了Qt程序和攝像頭操作的結合,詳細的代碼請參考例程。
例程的效果如下圖所示:
-
Linux
+關注
關注
88文章
11760瀏覽量
219036 -
嵌入式主板
+關注
關注
7文章
6107瀏覽量
37078
發布評論請先 登錄
ESP32-S3在初始化攝像頭時檢測到攝像頭模組型號不被支持
自動駕駛既然有雙目攝像頭了,為什么還要三目攝像頭?
車載雙目攝像頭如何“看見”世界?
新時代的硬核基石:龍芯工控主板助力信創產業新基建
紅外攝像頭模組是什么?科技時代的眼睛
廬山派上用v1.9的linux+RTSmart SDK,修改攝像頭接cs1報錯怎么解決?
【EASY EAI Orin Nano開發板試用體驗】--USB攝像頭使用
【Milk-V Duo S 開發板免費體驗】4 - OV5647 攝像頭攝像頭測試
canmv-k230使用攝像頭出現紫色畫面,并且顯示不完全怎么修復?
社區安裝IPC攝像頭,跟安裝一般安防監控攝像頭有什么區別?
英創信息技術Linux工控主板攝像頭應用簡介
評論