OpenCV是一種經(jīng)常被用到的計(jì)算機(jī)視覺庫。然而,它的文檔是只用英文發(fā)布的。這對(duì)習(xí)慣中文閱讀的國內(nèi)計(jì)算機(jī)愛好者來說并不是太友好,特別是對(duì)那些還沒受過高等教育但對(duì)計(jì)算機(jī)科學(xué)抱有美好向往的普通大眾。
OpenCV各版本間的使用方法并不是完全統(tǒng)一的。翻譯工作與官方數(shù)據(jù)手冊(cè)的發(fā)布不可避免的會(huì)有滯后性,雖然本文作者已經(jīng)盡可能選取目前最新版本的文檔了,但是這些內(nèi)容也不可避免的會(huì)有一天變成過時(shí)的糟粕。所以提醒初學(xué)者們,盡信書不如無書。
很榮幸能成為大家學(xué)習(xí)OpenCV的領(lǐng)路人。作者祝大家都能在學(xué)習(xí)的過程中找人生到真正的意義。
圖像的基本操作 ? ? ? ? ?
對(duì)于人類來說,圖像可以解構(gòu)為畫面結(jié)構(gòu)、色彩和非常豐富的意象。你可以把它解構(gòu)為各種色塊或線條,然后用故事性的語言把這幅圖像存在腦海里。
反過來,對(duì)于計(jì)算機(jī)來說,這個(gè)過程就要機(jī)械得多了,計(jì)算機(jī)只認(rèn)識(shí)組成一副圖像的那一個(gè)個(gè)像素。為了存儲(chǔ)這一個(gè)個(gè)像素,需要像素的坐標(biāo)和色彩信息。OpenCV以一種叫Mat的結(jié)構(gòu)存儲(chǔ)圖像,你可以把它理解為一種數(shù)據(jù)結(jié)構(gòu),結(jié)構(gòu)上是下圖這樣的一個(gè)表格,表格的行列分布代表像素的行列。
在上圖的各個(gè)像素中,顏色又用三原色來表示,即BGR(藍(lán)綠紅)。這些BGR數(shù)據(jù)依次排列如下圖。其中,三原色里的每個(gè)顏色元素的值域是0-255(即8位),因此每個(gè)像素具有8x3位(即24位真彩色)。
我們來建立一個(gè)Mat數(shù)據(jù)M,存儲(chǔ)一個(gè)最簡(jiǎn)單的2x2像素的圖像,且每個(gè)像素為藍(lán)色(0,0,255),可以這樣寫。要注意的是,目前最新的OpenCV 4.0.1已經(jīng)不支持C了,代碼文件需要按C++寫。
Mat M(2,2, CV_8UC3, Scalar(0,0,255));//新建2x2像素圖像
imshow("image", M);//顯示M的圖像
當(dāng)然,大多數(shù)時(shí)候,我們是不會(huì)這樣傻傻地新建一個(gè)圖像的。我們可以直接從一個(gè)jpg文件里面讀取圖像,我將把讀取圖片文件的方法教給大家。
舉個(gè)例子,把一個(gè)叫Lena的照片放進(jìn)一個(gè)叫M的Mat結(jié)構(gòu),可以像下面這么寫。另外,我們還演示了怎么把一個(gè)彩色圖像以灰度圖的方式讀取。
Mat M = imread("lena.jpg");//圖像來自圖片文件
imshow("image", M);//顯示M的圖像
Mat img = imread("lena.jpg", IMREAD_GRAYSCALE);//以灰度形式讀取圖片文件
imshow("grayimage", img);//顯示img的圖像(它是灰度圖)
反過來,一個(gè)Mat數(shù)據(jù),也可以寫入文件,這樣就把圖片給存起來了。舉個(gè)例子,讀取Lena的照片文件,然后存到一個(gè)叫out的文件里。
imwrite("image.jpg", M);//顯示M的圖像
Mat img = imread("lena.jpg", IMREAD_GRAYSCALE);//以灰度形式讀取圖片文件
imwrite("grayimage.jpg", img);//顯示img的圖像(它是灰度圖)
一個(gè)人臉檢測(cè)的例子 ? ? ? ? ?
這個(gè)例子,需要引用3個(gè)OpenCV的頭文件:
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
當(dāng)然,也可以一步到位,把所有的OpenCv庫都引了
#include "opencv2/opencv.hpp"
應(yīng)用iostream庫,并使用命名空間來減少代碼里面文字的輸入量
#include
using namespace std;
using namespace cv;
定義兩個(gè)分類器,分別用來檢測(cè)臉和眼睛
CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;
加載已經(jīng)訓(xùn)練好的分類器,其中face_cascade_name和eyes_cascade_name是xml格式的分類器文件名。(OpenCV的Github里有已經(jīng)訓(xùn)練好的模型,可以免費(fèi)下載)
face_cascade.load( face_cascade_name );
eyes_cascade.load( eyes_cascade_name );
定義一個(gè)檢測(cè)人臉并顯示檢測(cè)結(jié)果的函數(shù),函數(shù)的輸入變量是Mat圖像。
void detectAndDisplay( Mat frame )
輸入的Mat變量frame是彩色的,色彩信息對(duì)檢測(cè)沒什么用。所以我們把它轉(zhuǎn)成灰度圖,并標(biāo)準(zhǔn)化。
Mat frame_gray;
cvtColor( frame, frame_gray, COLOR_BGR2GRAY );//轉(zhuǎn)灰度
equalizeHist( frame_gray, frame_gray );//標(biāo)準(zhǔn)化
一張圖里有可能會(huì)檢測(cè)到多個(gè)人臉,我們可以把檢測(cè)結(jié)果用一個(gè)叫faces的向量來存儲(chǔ)。用分類器進(jìn)行多尺度檢測(cè)的函數(shù)叫detectMultiScale,這個(gè)函數(shù)有兩個(gè)變量,第一個(gè)是輸入的圖像frame_gray,第二個(gè)是輸出的結(jié)果faces。
std::vector
face_cascade.detectMultiScale( frame_gray, faces );//檢測(cè)
由于faces是把檢測(cè)結(jié)果存成了一個(gè)向量,所以我們可以用size()函數(shù)得到這個(gè)向量的長(zhǎng)度。下面這個(gè)代碼循環(huán)遍歷了每個(gè)結(jié)果,計(jì)算了每張臉的位置。
for ( size_t i = 0; i < faces.size(); i++ )
{
Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );//獲得每張臉部中心的xy像素坐標(biāo)
ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2 ), 0, 0, 360, Scalar( 255, 0, 255 ), 4 );//給每個(gè)臉畫個(gè)圈圈框起來
}
上面這個(gè)循環(huán)里,還可以把檢測(cè)到個(gè)每個(gè)臉的區(qū)域單獨(dú)提取出來,再檢測(cè)一下眼睛。然后嵌入一個(gè)用圈圈框出眼睛的循環(huán)。這一步,依然使用detectMultiScale函數(shù)進(jìn)行檢測(cè)。
Mat faceROI = frame_gray( faces[i] );//提取臉部區(qū)域
std::vector
eyes_cascade.detectMultiScale( faceROI, eyes );//檢測(cè)眼睛
for ( size_t j = 0; j < eyes.size(); j++ )
{
Point eye_center( faces[i].x + eyes[j].x + eyes[j].width/2, faces[i].y + eyes[j].y + eyes[j].height/2 );//獲得每個(gè)眼睛的中心
int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
circle( frame, eye_center, radius, Scalar( 255, 0, 0 ), 4 );//把每個(gè)眼睛用圈圈框起來
}
接下來,我們把上面的內(nèi)容整合一下,這個(gè)人臉檢測(cè)并顯示檢測(cè)結(jié)果的函數(shù)
detectAndDisplay可以寫成這樣
void detectAndDisplay( Mat frame )
{
Mat frame_gray;
cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
equalizeHist( frame_gray, frame_gray );
//-- Detect faces
std::vector
face_cascade.detectMultiScale( frame_gray, faces );
for ( size_t i = 0; i < faces.size(); i++ )
{
Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );
ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2 ), 0, 0, 360, Scalar( 255, 0, 255 ), 4 );
Mat faceROI = frame_gray( faces[i] );
//-- In each face, detect eyes
std::vector
eyes_cascade.detectMultiScale( faceROI, eyes );
for ( size_t j = 0; j < eyes.size(); j++ )
{
Point eye_center( faces[i].x + eyes[j].x + eyes[j].width/2, faces[i].y + eyes[j].y + eyes[j].height/2 );
int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
circle( frame, eye_center, radius, Scalar( 255, 0, 0 ), 4 );
}
}
//-- Show what you got
imshow( "Capture - Face detection", frame );
}
實(shí)際中,循環(huán)讀攝像頭并用這個(gè)detectAndDisplay函數(shù)進(jìn)行檢測(cè)的代碼可以這么寫。
Mat frame;//存放攝像頭捕獲圖像的frame變量,它是個(gè)Mat數(shù)據(jù)
while ( capture.read(frame) )//循環(huán)把攝像頭圖像放入frame變量
{
detectAndDisplay( frame );//檢測(cè)人臉并顯示結(jié)果
}
最后,我們來寫一下主函數(shù),把上面那個(gè)攝像頭讀取和人臉檢測(cè)功能加上。
int main( int argc, const char** argv )
{
CommandLineParser parser(argc, argv,
"{help h||}"
"{face_cascade|../../data/haarcascades/haarcascade_frontalface_alt.xml|Path to face cascade.}"
"{eyes_cascade|../../data/haarcascades/haarcascade_eye_tree_eyeglasses.xml|Path to eyes cascade.}"
"{camera|0|Camera device number.}");
parser.about( " This program demonstrates using the cv::CascadeClassifier class to detect objects (Face + eyes) in a video stream. "
"You can use Haar or LBP features. " );
parser.printMessage();
String face_cascade_name = parser.get
String eyes_cascade_name = parser.get
//-- 1. Load the cascades
if( !face_cascade.load( face_cascade_name ) )
{
cout << "--(!)Error loading face cascade ";
return -1;
};
if( !eyes_cascade.load( eyes_cascade_name ) )
{
cout << "--(!)Error loading eyes cascade ";
return -1;
};
int camera_device = parser.get
VideoCapture capture;
//-- 2. Read the video stream
capture.open( camera_device );
if ( ! capture.isOpened() )
{
cout << "--(!)Error opening video capture ";
return -1;
}
Mat frame;
while ( capture.read(frame) )
{
if( frame.empty() )
{
cout << "--(!) No captured frame -- Break! ";
break;
}
//-- 3. Apply the classifier to the frame
detectAndDisplay( frame );
if( waitKey(10) == 27 )
{
break; // escape
}
}
return 0;
}
編輯:黃飛
評(píng)論