opencv雖然很有名,但是自己一直沒怎麼玩過,暑假的時候使用深度相機做專案,但負責的不是程式碼模組,也只是配好了環境,沒有繼續瞭解影像處理。最近電子實習老師有教這個東西,但是身邊不少同學遇到了麻煩,所以在此總結了一下,彙總了一些我行之有效的教程和官方資料,並且附上了兩個例程;方便ubuntu環境下的opencv新手快速上手。
00 opencv4.5.3
先放一個連結,當初在ubuntu1804上無痛配置好opencv4.5.3的環境,採用的是
下面是我自己的總結,大家可以避免頁面跳轉。
00-1 安裝相關軟體包
1 sudo apt install build-essential 2 3 sudo apt install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev 4 5 sudo apt install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
如果第三個命令列無法定位軟體包;
則
1 sudo add-apt-repository "deb http://security.ubuntu.com/ubuntu xenial-security main" 2 3 sudo apt update 4 5 sudo apt upgrade 6 7 sudo apt install libjasper1 libjasper-dev
libjasper1 是 libjasper-dev 的依賴包
00-2 原始碼下載
opencv官網下載:
找到自己想要的版本,這裡我使用的是4.5.3,暑假的時候還算比較新,現在已經out了。
下載成功後解壓,解壓到哪個資料夾都可以。
00-3 編譯原始碼
採用cmake的編譯方法對下載解壓後的軟體包進行編譯。
進入解壓出來的OpenCV-4.5.3資料夾,建立一個新資料夾,我建立的叫 build
進入新資料夾,開啟終端,進入這個資料夾
執行命令
1 cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/your_opencv_install_path 2 3 sudo make 4 //為了快也可以直接 sudo make -j3並行編譯 5 6 sudo make install
完成後,OpenCV 就安裝好了,接下來要配置 OpenCV 的編譯環境;
00-4 配置環境
首先將OpenCV的庫新增到路徑,從而可以讓系統找到
開啟opencv.conf ,開啟後很可能是空白
sudo gedit /etc/ld.so.conf.d/opencv.conf
在文末新增
/usr/local/lib
接下來配置 bash
sudo gedit /etc/bash.bashrc
在文末新增
1 PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig 2 3 export PKG_CONFIG_PATH
儲存後退出重新整理(source)
1 source /etc/bash.bashrc 2 3 sudo updatedb
配置完成。
01 測試安裝成功
轉到 opencv-4.5.3/sample/cpp/example_cmake 目錄下,開啟終端
執行如下命令:
1 cmake . 2 make 3 ./opencv_example
彈出該頁面:
然後把電腦自帶的攝像頭開啟,就會出現畫面。說明安裝、配置成功。
02 讀取jpg並顯示
02-1 工程建立
首先,在opencv4.5.3資料夾裡建立一個我們學習用的資料夾,日後所有的學習專案都會放在這裡,方便管理。
mkdir opencv-learn
然後,我們進入這個資料夾
cd opencv-learn
建立今天的第一個專案
1 mkdir learn1 2 cd learn1 3 touch DisplayImage.cpp
02-2 C++程式
編寫我們讀取jpg並顯示的程式
1 #include <stdio.h> 2 #include <opencv2/opencv.hpp> 3 using namespace cv; 4 int main(int argc, char** argv ) 5 { 6 if ( argc != 2 ) 7 { 8 printf("usage: DisplayImage.out <Image_Path>\n"); 9 return -1; 10 } 11 Mat image; 12 image = imread( argv[1], 1 ); 13 if ( !image.data ) 14 { 15 printf("No image data \n"); 16 return -1; 17 } 18 namedWindow("Display Image", WINDOW_AUTOSIZE ); 19 imshow("Display Image", image); 20 waitKey(0); 21 return 0; 22 }
02-3 程式碼解析
opencv的程式碼風格與arduino很相似,函式很多,且名字與功能強相關,初入門的程式設計主要是函式的呼叫,不涉及複雜資料結構與演算法。
前面的if-else是魯棒性保證,感興趣可以查一下argv與argc;這裡程式碼的意思是如果命令列編譯引數不是2,那麼報錯。
argv與argc簡介如下:
這兩個引數在使用命令列編譯的時候有用。
argc 為整型,是命令列引數的個數;
argv 是字串型,為字串陣列,陣列成員意義如下:
argv[0]指向程式名;
argv[1]指向程式名後的第一個字串;
...以此類推
接下來:
Mat image;
定義一個Mat格式的矩陣image用來儲存下面會用到的圖片。
image = imread( argv[1], 1 );
把讀取的圖片資訊,存進來。
namedWindow("Display Image", WINDOW_AUTOSIZE );
這裡是指定展現處理後圖片的視窗的名字
1 imshow("Display Image", image); 2 waitKey(0);
展示圖片(會彈出一個視窗,名字是上面定義的名字)
-
在顯示圖片和視訊時,會在imshow()時,通常在後面加上while(WaitKey(n)==key),key為大於0的數即可,這樣程式將在此處迴圈執行直到按鍵響應為key。
-
delay:為0時,則會一直顯示這一幀,這在顯示攝像頭和視訊時有用。這個引數用於設定在顯示完一幀影像後程式等待“delay”ms後再顯示下一幀圖片。
-
舉幾個例子
1 if(waitKey(10)>=0)//是說10ms中按任意鍵進入if語句 2 3 while(waitKey(2)!=27)//表示不按Esc按鍵則一直在2ms後顯示
02-4 CMakeLists.txt編寫
A 內容
接下來我們採用CMakeLists.txt的形式進行編譯。
這種形式可以避免引用庫不全面而造成的未定義報錯。//而這在opencv程式設計中是經常出現的。
因此CMakeLists.txt是一種很好的編譯方式,值得學習。
1 touch CMakeLists.txt 2 3 gedit CMakeLists.txt
在彈出的頁面
1 cmake_minimum_required(VERSION 2.8) 2 project( DisplayImage ) 3 find_package( OpenCV REQUIRED ) 4 add_executable( DisplayImage DisplayImage.cpp ) 5 target_link_libraries( DisplayImage ${OpenCV_LIBS} )
B 解釋
這裡這些語句的意思是:⬇
1.cmake_minimum_required,指定最小需要的版本。裡面的內容也是與版本資訊有關。
2.project,基本用法是:用於指定cmake工程的名字,比如此處就制定了當前工程的名字是DisplayImage
3.
關於
當我們編譯一個需要使用第三方庫的程式時,我們需要知道的是:
目標 對照 去哪兒找標頭檔案 .h GCC的 -I 引數 去哪兒找庫檔案 (.so/.dll/.lib/.dylib/…) GCC的 -L引數 需要連結的庫檔案的名字 GCC的 -l 引數 比如我們需要一個第三方庫 curl,那麼我們的 CMakeLists.txt 需要指定標頭檔案目錄,和庫檔案,類似:
1 include_directiories(/usr/include/curl) 2 3 target_link_libraries(myprogram path/curl.so)而如果我們要藉助cmake語法的功能,如果藉助於cmake提供的finder會怎麼樣呢?使用cmake的Modules目錄下的FindCURL.cmake,相應的CMakeList.txt 檔案:
1 find_package(CURL REQUIRED) 2 3 include_directories(${CURL_INCLUDE_DIR}) 4 5 target_link_libraries(curltest ${CURL_LIBRARY})cmake是如何查詢這些列出的第三方庫的,在這裡就不談了
可以看出我們編寫的CMakeList.txt這句的意思是OpenCV的依賴庫的新增。
4.add_executable
使用指定的原始檔來生成目標可執行檔案。這裡的目標可執行檔案分為三類:
-
普通可執行目標檔案
-
匯入可執行目標檔案
-
別名可執行目標檔案
分別對應的三種命令格式如下:
1 add_executable (<name> [WIN32] [MACOSX_BUNDLE] 2 [EXCLUDE_FROM_ALL] 3 [source1] [source2 ...]) 4 5 add_executable (<name> IMPORTED [GLOBAL]) 6 7 add_executable (<name> ALIAS <target>)
這裡使用的是最簡單的形式:
1 #語法:add_executable(可執行程式名 要編譯的cpp)
5.target_link_libraries
該指令的作用為將目標檔案與庫檔案進行連結。
這項的編寫需要注意:庫之間可能也存在著依賴關係,被依賴的庫放在後面。
02-5 編譯
編寫完成後,我們編譯這個專案:
1 cmake . 2 3 make
02-6 執行得到結果
然後我們執行
./DisplayImage TEST.jpg
即可看到C++程式呼叫opencv處理後顯示的圖片,雖然在此例子中展示的是圖片原貌。
在這個程式基礎上可以實現灰度圖等等。
03 使用電腦攝像頭初步
03-1 工程建立
我們建立一個新的資料夾learn2並建立檔案
1 mkdir learn2 2 3 cd learn2 4 5 touch testopencv_camera.cpp
03-2 C++程式
1 //兩個功能:如果直接執行程式./exe檔案,就是開啟電腦攝像頭並顯示;如果./exe avi視訊,那會讀取這個avi視訊,並在視窗裡顯示出來。 2 #include "opencv/highgui.h" 3 #include "opencv/cv.h" 4 using namespace std; 5 using namespace cv; 6 7 int main(int argc, char** argv){ 8 cvNamedWindow("testcamera", CV_WINDOW_AUTOSIZE); 9 CvCapture* capture; 10 if (argc == 1){ 11 capture=cvCaptureFromCAM(0); 12 printf("capture 0\n"); 13 } 14 else { 15 capture = cvCreateFileCapture(argv[1]); 16 printf("capture argv1\n"); 17 } 18 assert(capture != NULL); 19 IplImage* frame; 20 frame = cvCreateImage(cvSize(640, 320), IPL_DEPTH_16U, 3); 21 while(1){ 22 frame = cvQueryFrame(capture); 23 if (!frame) 24 break; 25 cvShowImage("testcamera", frame); 26 char c=cvWaitKey(33);//顯示間隔是33ms 27 if (c==27)////27ms內按下任意建進入if 28 break; 29 } 30 cvReleaseCapture(&capture); 31 cvDestroyWindow("testcamera"); 32 return 0; 33 } 34
03-3 程式碼解析
常規庫、工作空間準備;
1 #include "opencv/highgui.h" 2 #include "opencv/cv.h" 3 using namespace std; 4 using namespace cv;
接下來,如果我們的命令列編譯引數只有一個(argc==1)那麼呼叫cvCaptureFromCAM()函式初始化CvCapture結構的 capture變數。
這是OpenCV庫中的一個函式。
初始化從攝像頭中獲取的視訊
CvCapture* cvCaptureFromCAM( int index );
index是個整數;
要使用的攝像頭索引。如果只有一個攝像頭或者用哪個攝像頭也無所謂,那使用引數-1應該便可以。
這個函式用“從攝像頭獲取的視訊流“分配和初始化CvCapture結構。
目前在Windows下可使用兩種介面:Video forWindows(VFW)和Matrox Imaging Library(MIL);
Linux下也有兩種介面:V4L和FireWire(IEEE1394)。(現在不需要深入瞭解這個)
釋放這個結構,使用函式cvReleaseCapture。
接著,否則的話,我們這個程式會判定我們將使用下面這個功能:
讀取一段視訊(avi格式)並顯示出來;
我們選取引數中的第二個引數(avi視訊)讀取到capture變數裡。
接下來是一個
1 assert(capture != NULL); 2 //意為如果表示式為假,整個程式將推出,不再執行,如果表示式成立,則繼續執行。 3 4 //使用assert()的缺點是,頻繁的呼叫會極大的影響程式的效能,增加額外的開銷。
接下來我們要建立一個
1 IplImage* frame;//建立變數 2 3 frame = cvCreateImage(cvSize(640, 320), IPL_DEPTH_16U, 3); 4 //寬為640,高為360,影像畫素的位深度為16位無符號整數的影像 5 //儘管在一般IPL影像格式中可以以非交叉的方式儲存,並且一些OpenCV可以處理它,但此函式只能建立交叉儲存的影像。
函式cvCreateImage建立影像首地址,並分配儲存空間。
IplImage* cvCreateImage(CvSize cvSize(int width, int height), int depth, int channels);
接下來進入迴圈
攝像頭或者檔案中抓取並解壓返回這一幀
frame = cvQueryFrame(capture);
這個函式:
1 IplImage* cvQueryFrame( CvCapture* capture );
這個函式僅僅是函式cvGrabFrame和函式cvRetrieveFrame在一起呼叫的組合。
返回的影像不可以被使用者釋放或者修改。
抓取後,capture被指向下一幀,可用cvSetCaptureProperty調整capture到合適的幀。
如果影像儲存空間沒有批下來,則退出迴圈;
1 if (!frame)、 2 break;
批下來了,就展示出來視訊的一幀frame
cvShowImage("testcamera", frame);
下面是退出程式的控制程式;
1 char c=cvWaitKey(33); 2 if (c==27)//27ms 3 break; 4 } 5 cvReleaseCapture(&capture); 6 cvDestroyWindow("testcamera"); 7 return 0;
03-4 編譯執行結果
1 # 編譯 2 g++ testopencv_camera.cpp -o first `pkg-config --libs --cflags opencv` -ldl 3 #執行 4 ./first 5 #就可以看到電腦攝像頭顯示的"視訊"