本章介绍OpenCV官方例程的引导学习与赏析,讲解如何编译OpenCV的源代码,并引入了对一些周边概念的认知。
OpenCV作为一个在全球使用人数众多的计算机视觉库,其实官方已经准备了大量的示例程序,供广大初学者学习。而市面上绝大多数的OpenCV书籍教程和网络博文的第一手知识来源,就是这些官方提供的技术文档和示例程序。
本节旨在带领大家粗略了解OpenCV官方提供的示例程序,看看学好OpenCV之后,可以写出哪些炫酷的程序来。
经过第1章的学习,我们已经安装、下载并配置好了OpenCV。在OpenCV安装目录下,可以找到OpenCV官方提供的示例代码。具体位于…\opencv\sources\samples\cpp目录下。
打开该文件夹,可以发现100余个C++版本的OpenCV官方示例程序。在…\opencv\sources\samples\cpp\tutorial_code路径下,存放着和官方教程配套的示例程序。其内容按OpenCV各组件模块而分类,非常适合学习,大家可以按需查询,分类学习,各个击破。
接下来,笔者将带着大家在此100余个示例程序中选择比较有特点的几个,得出运行效果。为了方便大家学习,以下的几个示例都和本书的其他主线示例程序一样,在配套代码包中提供了可以直接运行的执行文件与源工程。请在配套示例程序包中的第2章中找到第8到第13这6个配套示例程序(它们分别对应了2.1.1到2.1.6这5节的内容,2.1.5节分为了两个工程),对其进行编译运行并查看结果。
2.1.1 彩色目标跟踪:Camshift
本小节讲解的例程为彩色目标跟踪,程序的用法是根据鼠标框选区域的色度光谱来进行摄像头读入的视频目标的跟踪。其主要采用CamShift算法,全称是“Continuously Adaptive Mean-SHIFT”,是对MeanShift算法的改进,被称为连续自适应的MeanShift算法。
/* @File : 8_CamShiftDemo.cpp
* @Brief : 示例程序08
* @Details : 彩色目标跟踪操作
* @Date : 2015-11-01
* @OpenCV Version : 4.8.0
* @Development Tools : Windows 11 64bit && Visual Studio 2017
* @Modify : 2024-03-31
*/
#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <ctype.h>
using namespace cv;
using namespace std;
//-----------------------------------【全局变量声明】-----------------------------------------
// 描述:声明全局变量
//-------------------------------------------------------------------------------------------------
Mat image;
bool backprojMode = false;
bool selectObject = false;
int trackObject = 0;
bool showHist = true;
Point origin;
Rect selection;
int vmin = 10, vmax = 256, smin = 30;
//--------------------------------【onMouse( )回调函数】------------------------------------
// 描述:鼠标操作回调
//-------------------------------------------------------------------------------------------------
static void onMouse( int event, int x, int y, int, void* )
{
if( selectObject )
{
selection.x = MIN(x, origin.x);
selection.y = MIN(y, origin.y);
selection.width = std::abs(x - origin.x);
selection.height = std::abs(y - origin.y);
selection &= Rect(0, 0, image.cols, image.rows);
}
switch( event )
{
//此句代码的OpenCV2版为:
//case CV_EVENT_LBUTTONDOWN:
//此句代码的OpenCV3版为:
case EVENT_LBUTTONDOWN:
origin = Point(x,y);
selection = Rect(x,y,0,0);
selectObject = true;
break;
//此句代码的OpenCV2版为:
//case CV_EVENT_LBUTTONUP:
//此句代码的OpenCV3版为:
case EVENT_LBUTTONUP:
selectObject = false;
if( selection.width > 0 && selection.height > 0 )
trackObject = -1;
break;
}
}
//--------------------------------【help( )函数】----------------------------------------------
// 描述:输出帮助信息
//-------------------------------------------------------------------------------------------------
static void ShowHelpText()
{
cout <<"\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n"
<<"\n\n\t\t\t此为本书OpenCV3版的第8个配套示例程序\n"
<< "\n\n\t\t\t 当前使用的OpenCV版本为:" << CV_VERSION
<<"\n\n ----------------------------------------------------------------------------" ;
cout << "\n\n\t此Demo显示了基于均值漂移的追踪(tracking)技术\n"
"\t请用鼠标框选一个有颜色的物体,对它进行追踪操作\n";
cout << "\n\n\t操作说明: \n"
"\t\t用鼠标框选对象来初始化跟踪\n"
"\t\tESC - 退出程序\n"
"\t\tc - 停止追踪\n"
"\t\tb - 开/关-投影视图\n"
"\t\th - 显示/隐藏-对象直方图\n"
"\t\tp - 暂停视频\n";
}
const char* keys =
{
"{1| | 0 | camera number}"
};
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main( int argc, const char** argv )
{
ShowHelpText();
VideoCapture cap;
Rect trackWindow;
int hsize = 16;
float hranges[] = {0,180};
const float* phranges = hranges;
cap.open(0);
if( !cap.isOpened() )
{
cout << "不能初始化摄像头\n";
}
namedWindow( "Histogram", 0 );
namedWindow( "CamShift Demo", 0 );
setMouseCallback( "CamShift Demo", onMouse, 0 );
createTrackbar( "Vmin", "CamShift Demo", &vmin, 256, 0 );
createTrackbar( "Vmax", "CamShift Demo", &vmax, 256, 0 );
createTrackbar( "Smin", "CamShift Demo", &smin, 256, 0 );
Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj;
bool paused = false;
for(;;)
{
if( !paused )
{
cap >> frame;
if( frame.empty() )
break;
}
frame.copyTo(image);
if( !paused )
{
cvtColor(image, hsv, COLOR_BGR2HSV);
if( trackObject )
{
int _vmin = vmin, _vmax = vmax;
inRange(hsv, Scalar(0, smin, MIN(_vmin,_vmax)),
Scalar(180, 256, MAX(_vmin, _vmax)), mask);
int ch[] = {0, 0};
hue.create(hsv.size(), hsv.depth());
mixChannels(&hsv, 1, &hue, 1, ch, 1);
if( trackObject < 0 )
{
Mat roi(hue, selection), maskroi(mask, selection);
calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
//此句代码的OpenCV3版为:
normalize(hist, hist, 0, 255, NORM_MINMAX);
//此句代码的OpenCV2版为:
//normalize(hist, hist, 0, 255, CV_MINMAX);
trackWindow = selection;
trackObject = 1;
histimg = Scalar::all(0);
int binW = histimg.cols / hsize;
Mat buf(1, hsize, CV_8UC3);
for( int i = 0; i < hsize; i++ )
buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i*180./hsize), 255, 255);
//此句代码的OpenCV3版为:
cvtColor(buf, buf, COLOR_HSV2BGR);
//此句代码的OpenCV2版为:
//cvtColor(buf, buf, CV_HSV2BGR);
for( int i = 0; i < hsize; i++ )
{
int val = saturate_cast<int>(hist.at<float>(i)*histimg.rows/255);
rectangle( histimg, Point(i*binW,histimg.rows),
Point((i+1)*binW,histimg.rows - val),
Scalar(buf.at<Vec3b>(i)), -1, 8 );
}
}
calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
backproj &= mask;
RotatedRect trackBox = CamShift(backproj, trackWindow,
//此句代码的OpenCV3版为:
TermCriteria( TermCriteria::EPS | TermCriteria::COUNT, 10, 1 ));
//此句代码的OpenCV2版为:
//TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ));
if( trackWindow.area() <= 1 )
{
int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5)/6;
trackWindow = Rect(trackWindow.x - r, trackWindow.y - r,
trackWindow.x + r, trackWindow.y + r) &
Rect(0, 0, cols, rows);
}
if( backprojMode )
cvtColor( backproj, image, COLOR_GRAY2BGR );
//此句代码的OpenCV3版为:
ellipse( image, trackBox, Scalar(0,0,255), 3, LINE_AA );
//此句代码的OpenCV2版为:
//ellipse( image, trackBox, Scalar(0,0,255), 3, CV_AA );
}
}
else if( trackObject < 0 )
paused = false;
if( selectObject && selection.width > 0 && selection.height > 0 )
{
Mat roi(image, selection);
bitwise_not(roi, roi);
}
imshow( "CamShift Demo", image );
imshow( "Histogram", histimg );
char c = (char)waitKey(10);
if( c == 27 )
break;
switch(c)
{
case 'b':
backprojMode = !backprojMode;
break;
case 'c':
trackObject = 0;
histimg = Scalar::all(0);
break;
case 'h':
showHist = !showHist;
if( !showHist )
destroyWindow( "Histogram" );
else
namedWindow( "Histogram", 1 );
break;
case 'p':
paused = !paused;
break;
default:
;
}
}
return 0;
}
运行结果:
直方图
2.1.2 光流:optical flow
光流(optical flow)法是目前运动图像分析的重要方法,由Gibso于1950年首先提出。光流用来指定时变图像中模式的运动速度,因为当物体在运动时,在图像上对应点的亮度模式也在运动。这种图像亮度模式的表观运动(apparent motion)就是光流。光流表达了图像的变化,由于它包含了目标运动的信息,因此可被观察者用来确定目标的运动情况。
/* @File : 9_OpticalFlow.cpp
* @Brief : 示例程序09
* @Details : 来自OpenCV安装目录下Samples文件夹中的官方示例程序-利用光流法进行运动目标检测
* @Date : 2015-11-01
* @OpenCV Version : 4.8.0
* @Development Tools : Windows 11 64bit && Visual Studio 2017
* @Modify : 2024-03-31
*/
/************************************************************************
* Copyright(c) 2011 Yang Xian
* All rights reserved.
*
* File: opticalFlow.cpp
* Brief: lk光流法做运动目标检测
* Version: 1.0
* Author: Yang Xian
* Email: xyang2011@sinano.ac.cn
* Date: 2011/11/18
* History:
************************************************************************/
//---------------------------------【头文件、命名空间包含部分】----------------------------
// 描述:包含程序所使用的头文件和命名空间
//-------------------------------------------------------------------------------------------------
#include <opencv2/video/video.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>
#include <cstdio>
using namespace std;
using namespace cv;
//-----------------------------------【全局函数声明】-----------------------------------------
// 描述:声明全局函数
//-------------------------------------------------------------------------------------------------
void tracking(Mat &frame, Mat &output);
bool addNewPoints();
bool acceptTrackedPoint(int i);
//-----------------------------------【全局变量声明】-----------------------------------------
// 描述:声明全局变量
//-------------------------------------------------------------------------------------------------
string window_name = "optical flow tracking";
Mat gray; // 当前图片
Mat gray_prev; // 预测图片
vector<Point2f> points[2]; // point0为特征点的原来位置,point1为特征点的新位置
vector<Point2f> initial; // 初始化跟踪点的位置
vector<Point2f> features; // 检测的特征
int maxCount = 500; // 检测的最大特征数
double qLevel = 0.01; // 特征检测的等级
double minDist = 10.0; // 两特征点之间的最小距离
vector<uchar> status; // 跟踪特征的状态,特征的流发现为1,否则为0
vector<float> err;
//--------------------------------【help( )函数】----------------------------------------------
// 描述:输出帮助信息
//-------------------------------------------------------------------------------------------------
static void help()
{
//输出欢迎信息和OpenCV版本
cout <<"\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n"
<<"\n\n\t\t\t此为本书OpenCV3版的第9个配套示例程序\n"
<< "\n\n\t\t\t 当前使用的OpenCV版本为:" << CV_VERSION
<<"\n\n ----------------------------------------------------------------------------" ;
}
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main()
{
Mat frame;
Mat result;
//VideoCapture capture("a.mp4");
VideoCapture capture("1.avi");
help();
if(capture.isOpened()) // 摄像头读取文件开关
{
while(true)
{
capture >> frame;
if(!frame.empty())
{
tracking(frame, result);
}
else
{
printf(" --(!) No captured frame -- Break!");
break;
}
int c = waitKey(50);
if( (char)c == 27 )
{
break;
}
}
}
return 0;
}
//-------------------------------------------------------------------------------------------------
// function: tracking
// brief: 跟踪
// parameter: frame 输入的视频帧
// output 有跟踪结果的视频帧
// return: void
//-------------------------------------------------------------------------------------------------
void tracking(Mat &frame, Mat &output)
{
//此句代码的OpenCV3版为:
cvtColor(frame, gray, COLOR_BGR2GRAY);
//此句代码的OpenCV2版为:
//cvtColor(frame, gray, CV_BGR2GRAY);
frame.copyTo(output);
// 添加特征点
if (addNewPoints())
{
goodFeaturesToTrack(gray, features, maxCount, qLevel, minDist);
points[0].insert(points[0].end(), features.begin(), features.end());
initial.insert(initial.end(), features.begin(), features.end());
}
if (gray_prev.empty())
{
gray.copyTo(gray_prev);
}
// l-k光流法运动估计
calcOpticalFlowPyrLK(gray_prev, gray, points[0], points[1], status, err);
// 去掉一些不好的特征点
int k = 0;
for (size_t i=0; i<points[1].size(); i++)
{
if (acceptTrackedPoint(i))
{
initial[k] = initial[i];
points[1][k++] = points[1][i];
}
}
points[1].resize(k);
initial.resize(k);
// 显示特征点和运动轨迹
for (size_t i=0; i<points[1].size(); i++)
{
line(output, initial[i], points[1][i], Scalar(0, 0, 255));
circle(output, points[1][i], 3, Scalar(0, 255, 0), -1);
}
// 把当前跟踪结果作为下一此参考
swap(points[1], points[0]);
swap(gray_prev, gray);
imshow(window_name, output);
}
//-------------------------------------------------------------------------------------------------
// function: addNewPoints
// brief: 检测新点是否应该被添加
// parameter:
// return: 是否被添加标志
//-------------------------------------------------------------------------------------------------
bool addNewPoints()
{
return points[0].size() <= 10;
}
//-------------------------------------------------------------------------------------------------
// function: acceptTrackedPoint
// brief: 决定哪些跟踪点被接受
// parameter:
// return:
//-------------------------------------------------------------------------------------------------
bool acceptTrackedPoint(int i)
{
return status[i] && ((abs(points[0][i].x - points[1][i].x) + abs(points[0][i].y - points[1][i].y)) > 2);
}
运行结果:
2.1.3 点追踪:
lkdemo在…\opencv\sources\samples\cpp目录下(实际路径会因为OpenCV版本的不同略有差异)的lkdemo.cpp文件中,存放着这样一个精彩的例程。程序运行后,会自动启用摄像头,这时按键盘上的“r”键来启动自动点追踪,便可以看到如图2.7所示的效果图1。而我们在摄像头中移动物体,可以看到物体上的点随着物体一同移动(如图2.8所示),富含科技感且妙趣横生。
/* @File : 10_lkdemo.cpp
* @Brief : sources\samples\cpp\lkdemo.cpp
* @Details : 来自OpenCV安装目录下Samples文件夹中的官方示例程序-点追踪技术演示
* @Date : 2015-11-01
* @OpenCV Version : 4.8.0
* @Development Tools : Windows 11 64bit && Visual Studio 2017
* @Modify : 2024-03-31
*/
#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/videoio.hpp"
#include <iostream>
#include <ctype.h>
using namespace cv;
using namespace std;
//--------------------------------【help( )函数】----------------------------------------------
// 描述:输出帮助信息
//-------------------------------------------------------------------------------------------------
static void help()
{
//输出欢迎信息和OpenCV版本
cout <<"\n\n\t\t\tThis is a demo of Lukas-Kanade optical flow lkdemo()\n"
<< "\n\n\t\t\t 当前使用的OpenCV版本为:" << CV_VERSION
<<"\n\n ----------------------------------------------------------------------------" ;
cout << "\n\n\t该Demo演示了 Lukas-Kanade基于光流的lkdemo\n";
cout << "\n\t程序默认从摄像头读入视频,可以按需改为从视频文件读入图像\n";
cout << "\n\t操作说明: \n"
"\t\t通过点击在图像中添加/删除特征点\n"
"\t\tESC - 退出程序\n"
"\t\tr -自动进行追踪\n"
"\t\tc - 删除所有点\n"
"\t\tn - 开/光-夜晚模式\n"<< endl;
}
Point2f point;
bool addRemovePt = false;
static void onMouse( int event, int x, int y, int /*flags*/, void* /*param*/ )
{
if( event == EVENT_LBUTTONDOWN )
{
point = Point2f((float)x, (float)y);
addRemovePt = true;
}
}
int main( int argc, char** argv )
{
VideoCapture cap;
TermCriteria termcrit(TermCriteria::COUNT | TermCriteria::EPS, 20, 0.03);
Size subPixWinSize(10, 10), winSize(31, 31);
const int MAX_COUNT = 500;
bool needToInit = false;
bool nightMode = false;
help();
cv::CommandLineParser parser(argc, argv, "{@input|0|}");
string input = parser.get<string>("@input");
if (input.size() == 1 && isdigit(input[0]))
cap.open(input[0] - '0');
else
cap.open(input);
if (!cap.isOpened())
{
cout << "Could not initialize capturing...\n";
return 0;
}
namedWindow("LK Demo", 1);
setMouseCallback("LK Demo", onMouse, 0);
Mat gray, prevGray, image, frame;
vector<Point2f> points[2];
for (;;)
{
cap >> frame;
if (frame.empty())
break;
frame.copyTo(image);
cvtColor(image, gray, COLOR_BGR2GRAY);
if (nightMode)
image = Scalar::all(0);
if (needToInit)
{
// automatic initialization
goodFeaturesToTrack(gray, points[1], MAX_COUNT, 0.01, 10, Mat(), 3, 3, 0, 0.04);
cornerSubPix(gray, points[1], subPixWinSize, Size(-1, -1), termcrit);
addRemovePt = false;
}
else if (!points[0].empty())
{
vector<uchar> status;
vector<float> err;
if (prevGray.empty())
gray.copyTo(prevGray);
calcOpticalFlowPyrLK(prevGray, gray, points[0], points[1], status, err, winSize,
3, termcrit, 0, 0.001);
size_t i, k;
for (i = k = 0; i < points[1].size(); i++)
{
if (addRemovePt)
{
if (norm(point - points[1][i]) <= 5)
{
addRemovePt = false;
continue;
}
}
if (!status[i])
continue;
points[1][k++] = points[1][i];
circle(image, points[1][i], 3, Scalar(0, 255, 0), -1, 8);
}
points[1].resize(k);
}
if (addRemovePt && points[1].size() < (size_t)MAX_COUNT)
{
vector<Point2f> tmp;
tmp.push_back(point);
cornerSubPix(gray, tmp, winSize, Size(-1, -1), termcrit);
points[1].push_back(tmp[0]);
addRemovePt = false;
}
needToInit = false;
imshow("LK Demo", image);
char c = (char)waitKey(10);
if (c == 27)
break;
switch (c)
{
case 'r':
needToInit = true;
break;
case 'c':
points[0].clear();
points[1].clear();
break;
case 'n':
nightMode = !nightMode;
break;
}
std::swap(points[1], points[0]);
cv::swap(prevGray, gray);
}
return 0;
}
运行结果:
2.1.4 人脸识别:objectDetection
人脸识别是图像处理与OpenCV非常重要的应用之一,OpenCV官方专门有教程和代码讲解其实现方法。此示例程序就是使用objdetect模块检测摄像头视频流中的人脸,位于…\opencv\sources\samples\cpp\tutorial_code\objectDetection路径之下。需要额外注意的是,需要将“…\opencv\sources\data\haarcascades”路径下的“haarcascade_eye_tree_eyeglasses.xml”和“haarcascade_frontalface_alt.xml”文件复制到和源文件同一目录中,才能正确运行。运行程序,将自己的脸对准摄像头,或者放置一张照片对准摄像头任其捕获,便可以发现程序准确地识别出了人脸,并用彩色的圆将脸圈出。如图2.9所示。
/* @File : 11_ObjectDetection.cpp
* @Brief : objectDetection.cpp
* @Details : 来自OpenCV安装目录下Samples文件夹中的官方示例程序-人脸识别 samples\cpp\tutorial_code\objectDetection
* @Date : 2015-11-01
* @OpenCV Version : 4.8.0
* @Development Tools : Windows 11 64bit && Visual Studio 2017
* @Modify : 2024-03-31
*/
/**
* @file ObjectDetection.cpp
* @author A. Huaman ( based in the classic facedetect.cpp in samples/c )
* @brief A simplified version of facedetect.cpp, show how to load a cascade classifier and how to find objects (Face + eyes) in a video stream
*/
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
void detectAndDisplay( Mat frame );
//--------------------------------【全局变量声明】----------------------------------------------
// 描述:声明全局变量
//-------------------------------------------------------------------------------------------------
//注意,需要把"haarcascade_frontalface_alt.xml"和"haarcascade_eye_tree_eyeglasses.xml"这两个文件复制到工程路径下
//String face_cascade_name = "haarcascade_frontalface_alt.xml";
//String eyes_cascade_name = "haarcascade_eye_tree_eyeglasses.xml";
CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;
string window_name = "Capture - Face detection";
RNG rng(12345);
//--------------------------------【help( )函数】----------------------------------------------
// 描述:输出帮助信息
//-------------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//输出欢迎信息和OpenCV版本
cout << "\n\n\t\t\t 当前使用的OpenCV版本为:" << CV_VERSION
<<"\n\n ----------------------------------------------------------------------------" ;
}
/** @function main */
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("\nThis program demonstrates using the cv::CascadeClassifier class to detect objects (Face + eyes) in a video stream.\n"
"You can use Haar or LBP features.\n\n");
parser.printMessage();
//String face_cascade_name = samples::findFile(parser.get<String>("face_cascade"));
//String eyes_cascade_name = samples::findFile(parser.get<String>("eyes_cascade"));
String face_cascade_name = "haarcascade_frontalface_alt.xml";
String eyes_cascade_name = "haarcascade_eye_tree_eyeglasses.xml";
//-- 1. Load the cascades
if (!face_cascade.load(face_cascade_name))
{
cout << "--(!)Error loading face cascade\n";
return -1;
};
if (!eyes_cascade.load(eyes_cascade_name))
{
cout << "--(!)Error loading eyes cascade\n";
return -1;
};
int camera_device = parser.get<int>("camera");
VideoCapture capture;
//-- 2. Read the video stream
capture.open(camera_device);
if (!capture.isOpened())
{
cout << "--(!)Error opening video capture\n";
return -1;
}
Mat frame;
while (capture.read(frame))
{
if (frame.empty())
{
cout << "--(!) No captured frame -- Break!\n";
break;
}
//-- 3. Apply the classifier to the frame
detectAndDisplay(frame);
if (waitKey(10) == 27)
{
break; // escape
}
}
return 0;
}
/** @function detectAndDisplay */
void detectAndDisplay(Mat frame)
{
Mat frame_gray;
cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
equalizeHist(frame_gray, frame_gray);
//-- Detect faces
std::vector<Rect> faces;
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<Rect> eyes;
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);
}
运行结果:
2.1.5 支持向量机引导
在OpenCV的机器学习模块中,官方为我们准备了两个示例程序。第一个程序是使用CvSVM::train函数训练一个SVM分类器,如图2.10所示。
/* @File : 12_SupportVectorMachines.cpp
* @Brief : introduction_to_svm.cpp
* @Details : 来自OpenCV安装目录下Samples文件夹中的官方示例程序-samples\cpp\tutorial_code\ml\introduction_to_svm\introduction_to_svm.cpp
* @Date : 2015-11-01
* @OpenCV Version : 4.8.0
* @Development Tools : Windows 11 64bit && Visual Studio 2017
* @Modify : 2024-03-31
*/
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/ml.hpp>
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
// Set up training data
//! [setup1]
int labels[4] = { 1, -1, -1, -1 };
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
//! [setup1]
//! [setup2]
Mat trainingDataMat(4, 2, CV_32F, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);
//! [setup2]
// Train the SVM
//! [init]
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
//! [init]
//! [train]
svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);
//! [train]
// Data for visual representation
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
// Show the decision regions given by the SVM
//! [show]
Vec3b green(0, 255, 0), blue(255, 0, 0);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
Mat sampleMat = (Mat_<float>(1, 2) << j, i);
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i, j) = green;
else if (response == -1)
image.at<Vec3b>(i, j) = blue;
}
}
//! [show]
// Show the training data
//! [show_data]
int thickness = -1;
circle(image, Point(501, 10), 5, Scalar(0, 0, 0), thickness);
circle(image, Point(255, 10), 5, Scalar(255, 255, 255), thickness);
circle(image, Point(501, 255), 5, Scalar(255, 255, 255), thickness);
circle(image, Point(10, 501), 5, Scalar(255, 255, 255), thickness);
//! [show_data]
// Show support vectors
//! [show_vectors]
thickness = 2;
Mat sv = svm->getUncompressedSupportVectors();
for (int i = 0; i < sv.rows; i++)
{
const float* v = sv.ptr<float>(i);
circle(image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thickness);
}
//! [show_vectors]
imwrite("result.png", image); // save the image
imshow("SVM Simple Example", image); // show it to the user
waitKey();
return 0;
}
运行结果:
第二个示例程序主要用于讲解在训练数据线性不可分时,如何定义支持向量机的最优化问题。如图2.11所示。
/* @File : 13_SVMForNonLinearlySeparableData.cpp
* @Brief : non_linear_svms.cpp
* @Details : 来自OpenCV安装目录下Samples文件夹中的官方示例程序-samples\cpp\tutorial_code\ml\non_linear_svms\non_linear_svms.cpp
* @Date : 2015-11-01
* @OpenCV Version : 4.8.0
* @Development Tools : Windows 11 64bit && Visual Studio 2017
* @Modify : 2024-03-31
*/
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include "opencv2/imgcodecs.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/ml.hpp>
using namespace cv;
using namespace cv::ml;
using namespace std;
static void help()
{
cout << "\n--------------------------------------------------------------------------" << endl
<< "This program shows Support Vector Machines for Non-Linearly Separable Data. " << endl
<< "--------------------------------------------------------------------------" << endl
<< endl;
}
int main()
{
help();
const int NTRAINING_SAMPLES = 100; // Number of training samples per class
const float FRAC_LINEAR_SEP = 0.9f; // Fraction of samples which compose the linear separable part
// Data for visual representation
const int WIDTH = 512, HEIGHT = 512;
Mat I = Mat::zeros(HEIGHT, WIDTH, CV_8UC3);
//--------------------- 1. Set up training data randomly ---------------------------------------
Mat trainData(2 * NTRAINING_SAMPLES, 2, CV_32F);
Mat labels(2 * NTRAINING_SAMPLES, 1, CV_32S);
RNG rng(100); // Random value generation class
// Set up the linearly separable part of the training data
int nLinearSamples = (int)(FRAC_LINEAR_SEP * NTRAINING_SAMPLES);
//! [setup1]
// Generate random points for the class 1
Mat trainClass = trainData.rowRange(0, nLinearSamples);
// The x coordinate of the points is in [0, 0.4)
Mat c = trainClass.colRange(0, 1);
rng.fill(c, RNG::UNIFORM, Scalar(0), Scalar(0.4 * WIDTH));
// The y coordinate of the points is in [0, 1)
c = trainClass.colRange(1, 2);
rng.fill(c, RNG::UNIFORM, Scalar(0), Scalar(HEIGHT));
// Generate random points for the class 2
trainClass = trainData.rowRange(2 * NTRAINING_SAMPLES - nLinearSamples, 2 * NTRAINING_SAMPLES);
// The x coordinate of the points is in [0.6, 1]
c = trainClass.colRange(0, 1);
rng.fill(c, RNG::UNIFORM, Scalar(0.6*WIDTH), Scalar(WIDTH));
// The y coordinate of the points is in [0, 1)
c = trainClass.colRange(1, 2);
rng.fill(c, RNG::UNIFORM, Scalar(0), Scalar(HEIGHT));
//! [setup1]
//------------------ Set up the non-linearly separable part of the training data ---------------
//! [setup2]
// Generate random points for the classes 1 and 2
trainClass = trainData.rowRange(nLinearSamples, 2 * NTRAINING_SAMPLES - nLinearSamples);
// The x coordinate of the points is in [0.4, 0.6)
c = trainClass.colRange(0, 1);
rng.fill(c, RNG::UNIFORM, Scalar(0.4*WIDTH), Scalar(0.6*WIDTH));
// The y coordinate of the points is in [0, 1)
c = trainClass.colRange(1, 2);
rng.fill(c, RNG::UNIFORM, Scalar(0), Scalar(HEIGHT));
//! [setup2]
//------------------------- Set up the labels for the classes ---------------------------------
labels.rowRange(0, NTRAINING_SAMPLES).setTo(1); // Class 1
labels.rowRange(NTRAINING_SAMPLES, 2 * NTRAINING_SAMPLES).setTo(2); // Class 2
//------------------------ 2. Set up the support vector machines parameters --------------------
cout << "Starting training process" << endl;
//! [init]
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setC(0.1);
svm->setKernel(SVM::LINEAR);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, (int)1e7, 1e-6));
//! [init]
//------------------------ 3. Train the svm ----------------------------------------------------
//! [train]
svm->train(trainData, ROW_SAMPLE, labels);
//! [train]
cout << "Finished training process" << endl;
//------------------------ 4. Show the decision regions ----------------------------------------
//! [show]
Vec3b green(0, 100, 0), blue(100, 0, 0);
for (int i = 0; i < I.rows; i++)
{
for (int j = 0; j < I.cols; j++)
{
Mat sampleMat = (Mat_<float>(1, 2) << j, i);
float response = svm->predict(sampleMat);
if (response == 1) I.at<Vec3b>(i, j) = green;
else if (response == 2) I.at<Vec3b>(i, j) = blue;
}
}
//! [show]
//----------------------- 5. Show the training data --------------------------------------------
//! [show_data]
int thick = -1;
float px, py;
// Class 1
for (int i = 0; i < NTRAINING_SAMPLES; i++)
{
px = trainData.at<float>(i, 0);
py = trainData.at<float>(i, 1);
circle(I, Point((int)px, (int)py), 3, Scalar(0, 255, 0), thick);
}
// Class 2
for (int i = NTRAINING_SAMPLES; i < 2 * NTRAINING_SAMPLES; i++)
{
px = trainData.at<float>(i, 0);
py = trainData.at<float>(i, 1);
circle(I, Point((int)px, (int)py), 3, Scalar(255, 0, 0), thick);
}
//! [show_data]
//------------------------- 6. Show support vectors --------------------------------------------
//! [show_vectors]
thick = 2;
Mat sv = svm->getUncompressedSupportVectors();
for (int i = 0; i < sv.rows; i++)
{
const float* v = sv.ptr<float>(i);
circle(I, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thick);
}
//! [show_vectors]
imwrite("result.png", I); // save the Image
imshow("SVM for Non-Linear Training Data", I); // show it to the user
waitKey();
return 0;
}
运行结果:
以上就是对OpenCV官方提供的示例程序的引导和概览。
2.2 开源的魅力:编译OpenCV源代码
本节,我们将一起探讨如何通过已经安装的OpenCV,选择不同的编译器类型,生成高度还原的OpenCV开发时的解决方案工程文件,并欣赏OpenCV新版本中总计六十六多万行的精妙源代码。我们可以对源代码进行再次编译,得到二进制文件,或者修改原版官方的OpenCV代码,并编译后为自己所用,从而为深入理解OpenCV的开源魅力迈出坚实的一步。
2.2.1 下载安装CMake
想要在Windows平台下生成OpenCV的解决方案,需要一个名为CMake的开源软件。note:CMake,是“crossplatform make”的缩写,它是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。只是CMake的组态档取名为CmakeLists.txt。Cmake并不直接建构出最终的软件,而是产生标准的建构档(如Unix的Makefile或Windows Visual C++的projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是CMake和SCons等其他类似系统的区别之处。
CMake可以在官网:http://www.cmake.org/上下载到。打开此链接,首先转到其下载页面,如图2.12所示。下载页面的Source distributions处可以下载到CMake软件的源码,对这款开源软件感兴趣的读者不妨研究一下。
2.5 argc与argv参数解惑
2.5.1 初识main函数中的argc和argv在与OpenCV打交道时,我们常会在相关的示例程序中见到argc和argv这两个参数,如OpenCV的官方示例程序Samples中、OpenCV经典教材《Learning OpenCV》中,等等。这常常会让众多初学者疑惑,不清楚它们是何用途。本节内容正为解此惑而写。
argc和argc中的arg指的是“参数”(例如:arguments,argument counter和argument vector)。其中,argc为整数,用来统计运行程序时送给main函数的命令行参数的个数;而*argv[]:为字符串数组,用来存放指向字符串参数的指针数组,每一个元素指向一个参数。
Argc,argc这两个参数一般在用命令行编译程序时有用。在初学C++时,往往要弱化argc和argv的用法,main函数常常不带参数,如下。
void main( )
{
}
而在opencv的官方示例程序中,main函数的写法常常会带上两个形参,一般为argc和argv,并且在函数体内部会使用到这两个形参,如下:
int main(int argc, char ** argv)
{
const char *imagename = argc > 1 ? argv[1] : "lena.jpg";
......
return 0;
}
其实,带形参的main函数,如main(int argc, char*argv[], char**env),是UNIX、Linux以及Mac OS操作系统中C/C++的main函数的标准写法,并且是血统最纯正的main函数的写法。可能是由于外国的专家们更习惯使用UNIX、Linux以及Mac OS等操作系统,所以我们接触到由他们开发和维护的OpenCV这款开源视觉库的时候,自然会发现代码中常有argc和argv的出现。
2.5.2 argc、argv的具体含义
argc和argv这两个参数一般在用命令行编译程序时有用。
主函数main中变量(int argc, char*argv[])的含义
有些编译器允许将main()的返回类型声明为void,这就已不再是合法的C++了。
其实,main(int argc, char*argv[], char**env)才是UNIX和Linux中的标准写法。其中,第一个参数,int类型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,在Vsiual Studio中默认值为1。第二个参数,char*类型的argv[]:,为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
● argv[0]指向程序运行的全路径名
● argv[1]指向在DOS命令行中执行程序名后的第一个字符串
● argv[2]指向执行程序名后的第二个字符串
● argv[3]指向执行程序名后的第三个字符串
● argv[argc]为NULL
需要指出,argv[1]对应于【项目属性】→【配置属性】→【调试】→【命令参数】中的值。记住双引号也要带上,比如读取名为1.jpg的图片,如图2.30所示,就要在命令参数中填字符串“1.jpg”。
而如果有多个字符串,则用空格隔开。比如要读入两张名称分别为1.jpg和2.jpg的图片,在命令参数中填“1.jpg”“2.jpg”(“1.jpg”和“2.jpg”之间用空格隔开)即可,如图2.31所示。
第三个参数,char**类型的env,为字符串数组。env[]的每一个元素都包含ENVVAR=value形式的字符串。其中ENVVAR为环境变量,value为ENVVAR的对应值。在OpenCV中很少使用它。
argc、argv和env是在main()函数之前被赋值的。其实,main()函数严格意义上并不是真正的程序入口点函数,往往入口点还与操作系统有关。而在Windows的控制台应用程序中,将main()函数作为程序入口点,并且很少使用argc、argv等命令行参数。
2.5.4 总结
讲解至此,读者应该对argc和argv有了比较透彻的认识。
简单来说
● int argc表示命令行字串的个数
● char*argv[]表示命令行参数的字符串
由于我们使用的开发环境为Visual Studio,往后若在OpenCV相关代码中遇到这两个参数,可以在代码中将其用路径字符串替换,或者在项目属性页中给其赋值,而对程序整体影响不大的部分就将其注释掉。
2.6 格式输出函数printf()简析
2.6.1 格式输出:printf()函数
相信学习过C语言的读者们都知道,printf函数并非OpenCV中的函数,而是标准的C语言函数,包含在studio.h之中。只不过OpenCV对其也有包含,我们只需包含头文件如opencv.hpp,就可以使用它。
printf函数是我们经常会用到的格式输出函数,其关键字最末一个字母f即为“格式”(format)之意。其功能是按用户指定的格式,把指定的数据显示到窗口中。printf函数调用的一般形式如下:
int printf(const char *format, ...);
即
int printf("格式控制字符串", 输出列表);
printf函数的一个比较有特殊的用法是“格式字符串”,其用于指定输出格式,可由格式字符串和非格式字符串两种组成。格式字符串是以%开头的字符串,在%后面跟有各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。如表2.4所示。
而上述表中没有,或不带%的“非格式字符”,则以原样输出,且格式字符串和各输出项在数量和类型上应该一一对应。
除了格式字符串,printf还有一些特殊规定的字符,用法如表2.5所示。
2.6.2 示例程序:printf函数的用法示例
/* @File : 14_Use_printf.cpp
* @Brief : 示例程序14
* @Details : printf函数用法示例
* @Date : 2015-11-01
* @OpenCV Version : 4.8.0
* @Development Tools : Windows 11 64bit && Visual Studio 2017
* @Modify : 2024-03-31
*/
//#include <opencv2/opencv.hpp>
//using namespace cv;
#include <stdio.h>
void main()
{
int a=66,b=68;
printf("\n\t%d %d\n",a,b);//输出十进制整型
printf("\n\t%06d,%06d\n",a,b);//输出6位十进制整型
printf("\n\t%c,%c\n",a,b);//按字符型输出
printf("\n\t结果为:a=%d,b=%d",a,b);//可以配合其他内容一同输出
getchar();//等待读入任意字符而结束,在此用于保持窗口显示,直到任意按键按下
}
运行结果:
以上讲解的内容已经足够应付本书将要讲解的代码里关于printf的使用了,现在,让我们看一个示例,以结束printf函数的讲解,向下一节进发。