C/C++和OpenCV實(shí)現(xiàn)調(diào)用攝像頭
OpenCV 是一個(gè)強(qiáng)大的計(jì)算機(jī)視覺庫,它使得從攝像頭捕獲和處理視頻流變得非常簡單。本文將指導(dǎo)你如何使用 C/C++ 和 OpenCV 來調(diào)用攝像頭、讀取視頻幀并進(jìn)行顯示。
準(zhǔn)備工作
在開始之前,請確保你已經(jīng)正確安裝了 OpenCV 庫,并且你的開發(fā)環(huán)境(如 Visual Studio, Code::Blocks, CLion, 或者使用 CMake/GCC 的命令行環(huán)境)已經(jīng)配置好可以鏈接 OpenCV 庫。
你需要包含以下頭文件:
#include <opencv2/opencv.hpp> // 包含 OpenCV 的核心功能和高級 GUI (highgui) #include <iostream> // 用于標(biāo)準(zhǔn)輸入輸出
1. 打開攝像頭
要從攝像頭捕獲視頻,我們首先需要創(chuàng)建一個(gè) cv::VideoCapture
對象。它的構(gòu)造函數(shù)可以接受一個(gè)整數(shù)作為參數(shù),該整數(shù)表示攝像頭的索引。通常,0
代表系統(tǒng)默認(rèn)的內(nèi)置攝像頭,1
代表第一個(gè)外部攝像頭,以此類推。
cv::VideoCapture cap; // 創(chuàng)建一個(gè) VideoCapture 對象 int cameraIndex = 0; // 通常 0 是默認(rèn)攝像頭 cap.open(cameraIndex); // 或者直接 cv::VideoCapture cap(0); // 檢查攝像頭是否成功打開 if (!cap.isOpened()) { std::cerr << "錯誤: 無法打開攝像頭 " << cameraIndex << std::endl; return -1; // 或者進(jìn)行其他錯誤處理 }
提示:
- 你也可以傳遞一個(gè)視頻文件的路徑字符串給
cv::VideoCapture
的構(gòu)造函數(shù)或open()
方法來讀取視頻文件。 - 如果有多個(gè)攝像頭,你可以嘗試不同的索引(0, 1, 2, …)直到找到你想要的攝像頭。
2. 讀取視頻幀
一旦攝像頭成功打開,我們就可以在一個(gè)循環(huán)中逐幀讀取視頻。cv::VideoCapture::read()
方法或重載的 >>
運(yùn)算符可以用來獲取新的幀。
read()
方法會返回一個(gè)布爾值,表示是否成功讀取到一幀。讀取到的幀會存儲在一個(gè) cv::Mat
對象中。
cv::Mat frame; // 創(chuàng)建一個(gè) Mat 對象來存儲每一幀 while (true) { bool success = cap.read(frame); // 讀取新的一幀 // 或者 cap >> frame; if (!success || frame.empty()) { std::cerr << "錯誤: 無法從攝像頭讀取幀" << std::endl; break; // 如果讀取失敗或幀為空,則退出循環(huán) } // 在這里可以對 'frame' 進(jìn)行處理,例如: // cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY); // 轉(zhuǎn)換為灰度圖 // cv::GaussianBlur(frame, blurredFrame, cv::Size(5, 5), 0); // 高斯模糊 // ... (接下來的步驟:顯示幀) }
3. 顯示視頻幀
OpenCV 的 highgui
模塊提供了顯示圖像和視頻的功能。我們可以使用 cv::imshow()
函數(shù)來顯示捕獲到的幀。通常還需要配合 cv::waitKey()
來控制幀的顯示時(shí)間和處理用戶輸入。
// ... (在讀取幀的循環(huán)內(nèi)部) cv::imshow("攝像頭畫面", frame); // 在名為 "攝像頭畫面" 的窗口中顯示幀 // 等待按鍵,延遲 1 毫秒。 // 如果按下 'ESC'鍵 (ASCII 值為 27),則退出循環(huán) // waitKey 返回按下鍵的 ASCII 值,如果沒有按鍵則返回 -1 int key = cv::waitKey(1); if (key == 27) { // ESC 鍵 std::cout << "ESC鍵被按下,正在關(guān)閉..." << std::endl; break; } else if (key != -1) { // 可以添加其他按鍵的邏輯 // std::cout << "按鍵: " << key << std::endl; }
cv::waitKey(delay)
函數(shù)會等待指定的 delay
毫秒數(shù)。
- 如果
delay
為 0 或負(fù)數(shù),它會無限期等待直到有按鍵按下。 - 如果
delay
為正數(shù),它會等待delay
毫秒。如果在等待期間有按鍵按下,函數(shù)會返回按鍵的 ASCII 值;否則返回 -1。 - 對于視頻流,通常使用一個(gè)較小的值(如 1 或 30)來確保視頻流暢播放,并允許程序響應(yīng)按鍵事件。
4. 釋放資源
當(dāng)不再需要攝像頭或程序即將退出時(shí),務(wù)必釋放 cv::VideoCapture
對象,并銷毀所有創(chuàng)建的窗口。
// ... (在主函數(shù)末尾或退出前) cap.release(); // 釋放 VideoCapture 對象 cv::destroyAllWindows(); //銷毀所有由 OpenCV 創(chuàng)建的窗口
雖然 cv::VideoCapture
對象在析構(gòu)時(shí)會自動釋放攝像頭,但顯式調(diào)用 release()
是一個(gè)好習(xí)慣。
5. 獲取和設(shè)置攝像頭屬性 (可選)
cv::VideoCapture
對象還允許你獲取和設(shè)置攝像頭的一些屬性,例如幀的寬度、高度、FPS(每秒幀數(shù))等。這些屬性由 cv::CAP_PROP_*
枚舉定義。
- 獲取屬性:
cap.get(cv::CAP_PROP_FRAME_WIDTH)
- 設(shè)置屬性:
cap.set(cv::CAP_PROP_FRAME_WIDTH, newValue)
// 獲取攝像頭默認(rèn)的幀寬度和高度 double frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH); double frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT); double fps = cap.get(cv::CAP_PROP_FPS); std::cout << "默認(rèn)寬度: " << frameWidth << std::endl; std::cout << "默認(rèn)高度: " << frameHeight << std::endl; std::cout << "默認(rèn)FPS: " << fps << std::endl; // 嘗試設(shè)置新的寬度和高度 (攝像頭可能不支持所有值) // bool setWidthSuccess = cap.set(cv::CAP_PROP_FRAME_WIDTH, 1280); // bool setHeightSuccess = cap.set(cv::CAP_PROP_FRAME_HEIGHT, 720); // if (setWidthSuccess && setHeightSuccess) { // std::cout << "成功設(shè)置分辨率為 1280x720" << std::endl; // } else { // std::cout << "警告: 未能成功設(shè)置期望的分辨率" << std::endl; // // 再次獲取實(shí)際生效的寬度和高度 // frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH); // frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT); // std::cout << "當(dāng)前寬度: " << frameWidth << std::endl; // std::cout << "當(dāng)前高度: " << frameHeight << std::endl; // }
注意: 并非所有攝像頭都支持通過 set()
方法修改所有屬性,或者可能只支持特定的預(yù)設(shè)值。設(shè)置后最好再次 get()
以確認(rèn)實(shí)際生效的值。
完整示例代碼
下面是一個(gè)將以上所有步驟整合在一起的完整示例:
#include <opencv2/opencv.hpp> #include <iostream> int main() { // 1. 打開攝像頭 cv::VideoCapture cap; int cameraIndex = 0; // 嘗試不同的索引,如果默認(rèn)攝像頭不工作 cap.open(cameraIndex); // 檢查攝像頭是否成功打開 if (!cap.isOpened()) { std::cerr << "錯誤: 無法打開攝像頭 " << cameraIndex << std::endl; // 嘗試下一個(gè)攝像頭索引,如果需要 // cameraIndex = 1; // cap.open(cameraIndex); // if (!cap.isOpened()) { // std::cerr << "錯誤: 仍然無法打開攝像頭 " << cameraIndex << std::endl; // return -1; // } return -1; } std::cout << "攝像頭 " << cameraIndex << " 已成功打開." << std::endl; // (可選) 獲取和打印攝像頭屬性 double frameWidth = cap.get(cv::CAP_PROP_FRAME_WIDTH); double frameHeight = cap.get(cv::CAP_PROP_FRAME_HEIGHT); double fps = cap.get(cv::CAP_PROP_FPS); std::cout << "幀寬度: " << frameWidth << std::endl; std::cout << "幀高度: " << frameHeight << std::endl; std::cout << "FPS: " << fps << std::endl; // 注意:FPS 可能不準(zhǔn)確或不被所有攝像頭支持 cv::Mat frame; // 用于存儲每一幀 std::string windowName = "攝像頭畫面 - 按 ESC 退出"; cv::namedWindow(windowName, cv::WINDOW_AUTOSIZE); // 創(chuàng)建一個(gè)窗口 // 2. 讀取并顯示視頻幀 while (true) { bool success = cap.read(frame); // 或者 cap >> frame; if (!success || frame.empty()) { std::cerr << "錯誤: 無法從攝像頭讀取幀或視頻已結(jié)束" << std::endl; break; } // 在這里可以對 'frame' 進(jìn)行圖像處理 // 例如:顯示幀號或時(shí)間戳 // cv::putText(frame, "Frame: " + std::to_string(cap.get(cv::CAP_PROP_POS_FRAMES)), // cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2); // 3. 顯示幀 cv::imshow(windowName, frame); // 等待按鍵,延遲 (1000/FPS) ms 以大致匹配視頻幀率,或者簡單用 1-30ms // 如果 fps > 0, 使用 int delay = 1000.0 / fps; 否則使用一個(gè)默認(rèn)值 int delay = (fps > 0) ? static_cast<int>(1000.0 / fps) : 30; if (delay <= 0) delay = 30; // 防止 delay 為0或負(fù)數(shù)導(dǎo)致無限等待 int key = cv::waitKey(delay); // 等待約 30ms,或根據(jù)FPS計(jì)算 if (key == 27) { // ESC 鍵的 ASCII 值 std::cout << "ESC鍵被按下,正在關(guān)閉..." << std::endl; break; } else if (key == 's' || key == 'S') { // 示例:按 's' 保存當(dāng)前幀 std::string filename = "captured_frame.png"; cv::imwrite(filename, frame); std::cout << "當(dāng)前幀已保存為 " << filename << std::endl; } } // 4. 釋放資源 cap.release(); cv::destroyAllWindows(); return 0; }
編譯和運(yùn)行 (以 g++ 為例):
假設(shè)你的 OpenCV 安裝在標(biāo)準(zhǔn)路徑,并且你已經(jīng)設(shè)置了 pkg-config:
g++ your_code.cpp -o camera_app `pkg-config --cflags --libs opencv4` ./camera_app
如果未使用 pkg-config,你可能需要手動指定包含目錄和庫文件:
g++ your_code.cpp -o camera_app -I/path/to/opencv/include -L/path/to/opencv/lib -lopencv_core -lopencv_highgui -lopencv_videoio -lopencv_imgproc ./camera_app
(具體的庫名稱可能因 OpenCV 版本和模塊而略有不同,如 opencv_videoio
是處理視頻 I/O 的關(guān)鍵庫)。
常見問題與調(diào)試技巧
- 無法打開攝像頭:
- 確保攝像頭已連接并且驅(qū)動程序已正確安裝。
- 檢查是否有其他應(yīng)用程序正在使用該攝像頭。
- 嘗試不同的
cameraIndex
(0, 1, 2, …)。 - 在 Linux 上,檢查
/dev/video*
設(shè)備文件是否存在以及你是否有權(quán)限訪問它們。
- 視頻流卡頓或延遲:
- 確保
cv::waitKey()
的延遲參數(shù)設(shè)置合理。太小的值可能導(dǎo)致 CPU 占用過高,太大的值則導(dǎo)致卡頓。 - 圖像處理步驟如果過于復(fù)雜,會增加每幀的處理時(shí)間。
- 確保
- 窗口不顯示或一閃而過:
- 確保在主循環(huán)之后調(diào)用
cv::destroyAllWindows()
,并且cv::waitKey()
在循環(huán)內(nèi)部被正確調(diào)用以刷新窗口事件。
- 確保在主循環(huán)之后調(diào)用
- 幀為空 (
frame.empty()
為 true):- 這可能發(fā)生在視頻文件結(jié)束,或者攝像頭出現(xiàn)問題時(shí)。
到此這篇關(guān)于C/C++和OpenCV實(shí)現(xiàn)調(diào)用攝像頭的文章就介紹到這了,更多相關(guān)C++ OpenCV調(diào)用攝像頭內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt實(shí)現(xiàn)簡易秒表設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了Qt實(shí)現(xiàn)簡易秒表設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08詳解C語言中的getgrgid()函數(shù)和getgrnam()函數(shù)
這篇文章主要介紹了詳解C語言中的getgrgid()函數(shù)和getgrnam()函數(shù),是C語言入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-08-08C++編程使用findfirst和findnext查找及遍歷文件實(shí)現(xiàn)示例
這篇文章主要為大家介紹了C++編程如何使用findfirst和findnext查找及遍歷文件實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10C語言運(yùn)算符深入探究優(yōu)先級與結(jié)合性及種類
C語言運(yùn)算符號指的是運(yùn)算符號。C語言中的符號分為10類:算術(shù)運(yùn)算符、關(guān)系運(yùn)算符、邏輯運(yùn)算符、位操作運(yùn)算符、賦值運(yùn)算符、條件運(yùn)算符、逗號運(yùn)算符、指針運(yùn)算符、求字節(jié)數(shù)運(yùn)算符和特殊運(yùn)算符2022-05-05C++單例模式為何要實(shí)例化一個(gè)對象不全部使用static
這篇文章主要介紹了C++單例模式為何要實(shí)例化一個(gè)對象不全部使用static,文基于C++圍繞主題展開詳細(xì)內(nèi)容,需要的小伙伴可以參考一下2022-05-05