openCV常用算子整理

openCV3-api

本笔记主要用来记录openCV3中常用的api,大多数都是笔者在实际学习过程中用到的,可以说是经验总结,对于openCV的学习方法主要还是要理论+实战才是正道.

在本笔记中,把api按照项目场景进行分类,其中第一章是描述的openCV的基础api比如图像创建、读取、颜色空间转换等,第二章则是滤波、阈值、采样,第三章则主要针对的是形态学操作,第四章是边缘、轮廓检测, 第五章是模版匹配,第六章是目标追踪,循序渐进从传统方法到深度学习方法递进.

笔记的章节并不是固定的,也不是独立的,相互之间存在重复记录,一方面为了复习,另一方面是某些章节之间存在较为紧密的联系.

笔记的章节数目也不是固定的,随着学习的深入,会不断的追加.

⚠️该笔记理论设计较少,若需掌握相关理论详情,需要参考DIP数字图像处理笔记,这里仅以openCV实现方法为主要学习、记忆目标.

第一章 openCV基础操作

openCV基础操作主要是图片、视频的读取,图片的颜色变换,基本形状的绘制等api,还有openCV中的基础类等

1.1元素数据类型

openCV中像素点的数据类型如CV_8UC3,表示该像素点在内存中占8位,U是unsigned无符号类型,C表示channels,即为3.

同理CV_32FC12表示,该像素点在内存中占32位,是float类型,channels=12

1.2 四大基本类

Point p = Point(20,30);
Size s = Size(1920,1080);
Scalar color = Scalar(255,0,0);
Rect rect = Rect(0,0,1920,1080);

对于Rect类而言,有如下内部成员

cout<<rect.tl();
cout<<rect.br();
cout<<rect.x;
cout<<rect.y;
cout<<rect.width();
cout<<rect.height();

1.3 绘制标准形状

cv::line(Mat src, Point p1, Point p2, Scalar color, int thickness, LINE_8,0);

cv::rectangle(Mat src, Rect rect, Scalar color, int thickness, LINE_8);

cv::putText(Mat src, String text, Point p, font_type, 12, Scalar color, int thickness, LINE_8);

1.4 Mat

Mat类可以说是openCV中使用频率最高的类,首先介绍对图片的读取和复制

Mat src (2, 2, CV_8UC3, Scalar(0,0,255);
Mat src;
Mat a = imread ("~/a.png");
Mat a(b);
Mat c = a.clone();
Mat copyImage;
a.copyTo(copyImage);

上述是对已有图片进行读取复制,这些图片的像素值是已经被相机或其他设备记录好的,那么如何创建一张像素值全是0或者全是1的像素呢? 或者指定宽高和具体像素值的图片呢?

Mat a = imread("~/a.jpeg");
Mat zero = Mat::zeros(a.size(), a.type());
Mat zero = Mat::zeros(2,2,CV_32FC3);

Mat dst;
dst.create(Size(20, 20), CV_32UC2);

Mat kernel = (Mat_<double>(3, 3)<<0, 1, 0, 1, 0, 1, 0, 1, 0);
filter2D(Mat src, Mat dst, src.depth(), kernel);
//	上述的filter2D()算子是用来进行滤波操作的

1.5 视频读取

VideoCapture cap ("~/video/a.mp4");
VideoCapture cap(0);
VideoCapture cap;
cap.open("~/video/a.mp4")
if(cap.isOpened()){}
Mat frame;
cap>>frame;
cap.read(frame);
if(cap.read(frame)){}
if(frame.empty()){}
cap.release();

上述是从视频或视频流中读取图片,判断读取内容,接下来需要解析图片参数

VideoCapture cap("~/video/a.mp4");
Mat frame;
cap>>frame;
//cap.read(frame);
int  width = cap.get(CV_CAP_PROP_FRAME_WIDTH);
int height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);
int frameRate = cap.get(CV_CAP_PROP_FPS);
int totalFrames = cap.get(CV_CAP_PROP_FRAME_COUNT);总帧数

1.6 图像显示、保存和转换

该节展示的是图像的读取,显示,修改保存等操作,修改主要包括的是颜色空间的转换等

Mat src = imread("~/picture/a.jpg");
namedWindow('a_window',WINDOW_AUTOSIZE);
imshow('a_window',src);

Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
imwrite("~/picture/a_gray.jpg");

接下来展示如何显示图片的基本参数

int channels = src.channels();
int height = src.rows;
int width = src.cols;

当图片的像素值超过255时,可以通过如下算子进行调整

src.at<uchar>(row, col) = saturate_cast(12*9);

图像转换

src.convertTo(dst, CV_32FC3);

resize(Mat src, 
	Mat output,
	Size(newWidth, newHeight), 
	interPolation);

上述的interPolation参数是插值算法,主要有如下参数设置

interPolation参数

参数

解释

备注

INTER_NEAREST

最近邻插值算法

 

INTER_LINEAR

双线性插值

默认该参数设置

INTER_AREA

基于局部像素的重采样

 

INTER_CUBIC

基于4x4像素邻域的3次插值算法

 

INTER_LANCZOS4

基于8x8像素邻域的Lanczos插值算法

 

1.7 指针访问像素

本节主要通过一个完整的例子来表示如何使用指针访问像素值,并且该示例展示了如何通过指针访问像素的方法,完成3x3的kernel对RGB图片的滤波算法

Mat src = imread("~/video/a.jpg");
Mat dst = Mat(src.size(), src.type());
int channels = src.channels();
int height = src.rows;
int width = src.cols * channels;
for ( int row = 1; row<height; row++){
	const uchar * current = src.ptr<uchar>(row);
	const uchar * next = src.ptr<uchar>(row + 1);
	const uchar * previous = src.ptr<uchar>(row - 1);
	uchar * output = dst.ptr<uchar>(row);
	for (int col = channels; col < channels *( src.cols -1); col++){
		*output = saturate_cast("");
		*output = current[col] * 3 + next[col] - previous[col];
		//该层循环内部为像素级操作
		output ++;
	}
}

1.8 直接访问像素点

上述是通过指针来访问像素的值显然是通过先用rowPtr = src.ptr<uchar>(row)定位到行,再使用locationPtr = rowPtr[col]来访问该行的某列的像素

直接访问像素点,根据对象类型不同,比如单通道灰度图,或者三通道RGB图,方法也不同

Mat src = imread("gray");
int src_pixel = src.at<uchar>(row, col);

Mat rgb = imread("rgb");
int b = rgb.at<Vec3b>(row, col)[0];
int g = rgb.at<Vec3b>(row, col)[1];
int r = rgb.at<Vec3b>(row, col)[2];

//像素反转
int channels = src.channels();
int height = src.rows;
int width = src.cols * channels;
for (int row = 0; row<height; row++){
	for (int col =0; col < width; col++){
		dst.at<uchar>(row, col) = saturate_cast(255- src.at<uchar>(row, col));
	}
}

//像素反转api
bitwise_not(src, dst);

第二章 滤波阈值与采样

滤波

各种滤波API,可以通过像素级别操作完成相同的算法效果,这里只是API

滤波其实是分成了中值滤波和均值滤波,均值滤波又包含了加权均值滤波,加权均值滤波进一步演化成Gaussian加权均值滤波算法

简单说明:均值滤波就是求滑动窗口内像素平均值,中值滤波就是求中间值,高斯滤波就是按公式来求

//均值滤波
blur(Mat src, Mat dst, Size(xradius,yradius),Point(-1,-1));

//GaussianBlur
GaussianBlur(Mat src, Mat dst, Size(xradius,yradius),sigmax,sigmay);

//中值滤波 常用在处理椒盐噪声上
mediaBlur(Mat src, Mat dst, Size(xradius,yradius);

GaussianBlur的像素级别实现方法

GaussianBlur 像素级别操作

首先介绍Gaussian加权均值滤波算法,简单来说就是:

  • 对滑动窗口内的每个像素加以权值,而每个滑动窗口的元素正是对应像素的权值,即通过卷积即可完成加权求和,在通过/N可得到像素加权均值,

  • 一般的滑动窗口内的权值是我们自己设计的,这里采用的权值分布是高斯分布(参考正态分布,高斯分布就是正态分布),一般使用标准2维正态分布,高斯滤波把周围像素对中心像素的距离分布影响考虑进去了

二维正态分布

由于我们往往采用的是xy独立同分布,所以ρ=0, μ是位置参数,σ是尺度参数

下面程序采用了相同的σ,实际应该有σ_x与σ_y

void getGaussianMatrix(int size, double sigma, Mat &Gauss){
    Gauss.create(Size(size,size), CV_32F1);
    float sum = 0.0
    float center = size/2;
    /*要产生一个3x3的高斯滤波器模板/kernel,要以末班中心位置为原点坐标进行取样,center用来平衡周围像素的值*/
    const float PI = 3.1415926;
    for (int i =0;i<size;i++){
        for(int j =0;j<size;j++){
            Gauss.at<float>(i,j) = 
                (1/(2*PI*sigma*sigma))*exp(-((i-center)*(i-center)+(j-center)*(j-center))/(2*sigma*sigma))
            sum += Gauss.at<float>(i,j);
        }
    }
    # 归一化
    for (int i =0;i<size;i++){
        for(int j=0;j<size;j++){
            Gauss.at<float>(i,j) /= sum;
        }
    }
}

图像分辨率操作,上下采样与像素级减法

//上采样,就是扩大分辨率,用像素0来填充新有的像素点,然后用kernel滑动,用来把值为0的像素点和周围像素值进行近似处理,一般采用都是Gaussian kernel

pyrUp(Mat src, Mat output,Size(width,height);
//下采样,就是缩小分辨率,可以去掉1/2,或者1/4等等
pyrDown(Mat src,Mat output,Size(width,height);
//像素级别减法
subtract(Mat a, Mat b, Mat output);
//output=a-b

第三章 形态学

结构元素

getStructuringElement() 获得结构元素api, 返回一个kernel,这个kernel的元素是0或1, 可以指定1在kernel中的分布形状,比如矩形是全都是1,菱形是一个由1组成的菱形分布

Mat kernel = getStructuringElement(MORPH_RECT, Size(3,3),Point(-1,-1);

// MORPH_RECT 矩形, 
// MORPH_CROSS交叉型,
// MORPH_ELLIPSE椭圆形等,这都是元素1的分布形状

膨胀,腐蚀,open,close,tophat,blackhat

Mat src = imread(“path”);
Mat output;
src.copyTo(output);
// morphology都需要的是元素为1或0的kernel,所以直接用getStructruingElement()
Mat structElement = getStructuringElement(MORPH_RECT,Size(3,3),Point(-1,-1));
//膨胀
dilate(Mat src, Mat output, Mat structElement,Point(-1,-1));
//腐蚀
erode(Mat src, Mat output,Mat structElement,Point(-1,-1));
//open
morphologyEx(Mat src, Mat output,MORPH_OPEN,Mat structElement);
//close
morphologyEx(Mat src, Mat output,MORPH_CLOSE,Mat structElement);
//tophat
morphologyEx(Mat src, Mat output,MORPH_TOPHAT,Mat structElement);
//blackhat
morphologyEx(Mat src, Mat output,MORPH_BLACKHAT,Mat structElement);

第四章 边缘/轮廓检测

阈值

一般可通过阈值操作来进行图像分割,从而获得各区域的轮廓,边缘,因此阈值放在第四章边缘轮廓检测中.

一般阈值

阈值操作threshold,设定高低阈值,像素在三个区间内分别采取不同措施对像素值进行修改

Mat src=imread(“path”);
Mat output;
src.copyTo(output);
threshold(Mat src,Mat output,default_value,max_value,type);
//threshold(src,dst,10,255,THRESHOLD_BINARY);
//THRESHOLD_BINARY 二值化,THRESHOLD_BINARY_INV 反二值化, THRESHOLD_TRUNC截断

自适应阈值

自适应阈值adaptiveThreshold, 只支持8bit图片

Mat src = imread(“path”);
Mat dst;
cvtColor(src,dst,COLOR_BGR2GRAY);
convertTo(dst,dst,CV_8UC1);
adaptiveThreshold(Mat src,Mat output, adaptiveMethod,threshold_type,int blockSize,double offset);
//adaptiveThreshold(src,output,ADAPTIVE_THRESH_MEAN_C,THRESHOLD_BINARY,15,-2);
//adaptiveMethod是自适应方法, threshold_type是阈值化类型,bloaksize是方形邻域块边长,offset是偏移量

轮廓检测

轮廓检测流程:

  1. 灰度处理

  2. 二值化,需要CV_8UC1类型的图片

  3. 检测轮廓

  4. 绘制轮廓

binary, contours, hierarchy = findContours(src, output,RETR_TREE,CHAIN_APPROX_NONE);
// findContours()会返回3个参数,第一个是二值化的图片,第二个是轮廓数据,第三个是层次结构,常用的是contours轮廓结构信息
//绘制轮廓
Mat res;

res = drawContours(src,contours,-1,Scalar(0,0,255),2);
// drawContours(Mat src, Mat contours,int contoursIdx,Scalar color,int thickness,int lineType,Mat hierarchy,int maxLevel,Point offset);
// contoursIdx = -1选择全部
// contours[i]可以选择轮廓

//常用boundingRect()函数, 作用:计算轮廓垂直边界最小矩形
Rect boundingRect(points);
//这里的points可以是Mat对象,也可是二维点集合,也可以是点序列

⚠️这里的output_contours类型是vector<vector<Point>>类型,实际上寸了x个轮廓,即vector<x>,每个轮廓是一系列的点集合,即vector<Point>, 想访问某个轮廓可以for循环vector<x>,得到的是vector<Point>

Mat src = (“path”);
Mat output_contours;

cv::findContours(Mat src,  vector<vector<Point>>output_contours, int mode,int method, Point offset = Point());

/*
mode:轮廓检索类型
RETR_EXTRENAL,外面的轮廓, 
RETR_LIST,所有轮廓,并保存到列表中,
RETR_CCOMP,所有轮廓,并组成两层,一个外轮廓一个内轮廓, 
RETR_TREE,检测所有轮廓,并按层次分成树结构,这个比较常用

method:轮廓逼近方法
CHAIN_APPROX_NONE,直接画出轮廓,
CHAIN_APPROX_SIMPLE,只保留关键点,比如长方形是四个顶点
*/

边缘检测/提取

对于边缘提取算法,我们一般采用Sobel算子检测方法和Canny算法,Sobel算子对灰度渐变,多噪声图片处理效果很好,但对边缘定位精度较差,即定位不准确(大多数边缘不止一个像素); Canny算法不容易受到噪声干扰,利用强弱边缘,即弱边缘和强边缘相连时,才将弱边缘考虑,从而容易检测到真正的边缘,即定位精度比较高.

精度要求不高可以考虑Sobel边缘提取方法, 反之考虑Canny边缘提取方法,还熟悉Laplance算子,由于对噪声比较敏感,常不用在边缘检测上

Sobel边缘检测算法详解

  • Sobel算子分成横向检测了纵向检测,在解释前首先理解,边缘的概念,边缘理论上是像素值得突变处,导数正好是变化率的意义,因此在x轴方向导数最大,则是一个垂直边缘,相反在y轴方向变化最大,则是一个水平边缘,

  • 通过计算像素的差值,得到的图像,越明亮的地方越是边缘(这也表示,并不是存在差值就是边缘点,而是比较明亮的才是,实现手段就是利用阈值进行过滤),将xy方向的sobel输出图进行线性混合,则得到了整个图像的边缘信息

实现方法:

  1. 最后,根据阈值T,若G(row,col)大于T,则认为是边缘点,过滤后得到最后的G,即图片边缘信息图(这里是单阈值)
  2. 理论使用G=sqrt(Gx^2 + Gy^2),实际考虑效率使用|G| = |Gx| + |Gy|,即计算各方向边缘图的绝对值,然后进行线性混合
  3. 其次计算y方向的边缘信息,得到Gy
  4. 首先计算x方向的边缘信息,得到Gx
  5. 灰度处理
  6. 得到图片后,先对图片进行增强,利用GaussianBlur过滤噪点
Mat src = imread(“path”);
Mat gray;
Mat output_x;
Mat output_y;
Mat output_absx;
Mat output_absy;
Mat sobel_image;
Mat output_laplance;
cvtColor(src, gray, COLOR_BGR2GRAY);

//Sobel(Mat src, Mat output, int depth, int dx, int dy,int ksize,double scale,double delta,int borderType);

//Sobel边缘检测算法实现基于cv::Sobel
Sobel(src,output_x,src.depth(),1,0,3); # 一般只用到前几个参数,dx和dy选择哪个方向就置1
convertScaleAbs(output_x,output_absx);
Sobel(src,output_y,src.depth(),0,1,3);
convertScaleAbs(output_y,output_absy);
addWeighted(output_absx,0.5,output_absy,0.5,sobel_image);


Mat k_sobel_x = (Mat_<double>(3,3)<< -1, 0, 1,
                                     -2, 0, 2,
                                     -1, 0, 1);

Mat k_sobel_y = (Mat_<double>(3,3)<< -1, -2, -1,
                                      0,  0,  0,
                                      1,  2,  1);

//x和y的滑动结果需要分别取绝对再相加,就得到了xy两方向的像素导数值,及时调用
//绝对值api
convertScaleAbs(


Mat k_laplance =(Mat_<double>(3,3)<< 0, -1, 0,
                                     -1, 4, -1,
                                     0 , -1, 0);
filter2D(Mat src, Mat output,src.depth(),Mat kernel);   

Canny边缘检测算法

流程如下

  1. GaussianBlur去噪点

  2. 灰度处理

  3. 计算Sobel梯度信息

  4. NMS(非最大值抑制)得到疑似边缘点

    1. 简单说明,就是寻找像素点局部最大值,将非极大值点对应的灰度值设置为背景像素点而非边缘像素点, 像素点邻域内满足梯度值最大的点则判为边缘像素点,

    2. 上述操作完成了对非极大值点的抑制,提出了大部分非边缘点

    3. 此时得到的像素点是疑似边缘点

  5. 双阈值Double-Treshold来检测且定位真正的边缘和潜在的边缘,即强弱边缘

    1. 疑似边缘点高于高阈值,则保留

    2. 疑似边缘点低于低阈值,则排除

    3. 疑似边缘点在高低阈值中间时,则判断该像素是否连接到一个保留像素点, 连接则保留,反之排除

//cv::Canny()
Mat src =imread(“path”);
Mat src_gray;
Mat output;
GaussianBlur(src,src,Size(3,3),0,0,BORDER_DEFAULT);
cvtColor(src, src_gray, COLOR_BGR2GRAY);
int T1 = 40;
int T2 =190;
Canny(src_gray,output,T1,T2,3,true);
//Canny(Mat src, Mat output, double threshold1, thrshold2,int kernelSize, bool L2gradient=false)
//L2gradient=false时则用L1归一化,ture则用L2

第五章 模板匹配

模板匹配

左图为模板,右图为输入图片

左边是模板,右边是图片,模板在图像上滑动,从而寻找模板在图片中的位置

然后两图片逐像素比较,有点暴力匹配了,就是比较像素差异,一般的算法是相减然后平方,得到的数越小,像素点之间的相似程度越高,

这里有很多不同的方法template match modes

  • TM_SQDIFF 方差匹配

  • TM_SQDIFF_NORMED 归一化方差匹配

  • TM_CCORR 相关性匹配

  • TM_CCORR_NORMED 归一化相关性匹配

  • TM_CCOEFF 相关系数匹配

  • TM_CCOEFF_NORMED 归一化相关系数匹配

这里注意一下, 方差匹配法用最小值来表示最佳匹配,而相关性和相关系数两个方法用最大值表示最佳匹配

归一化是用来把匹配结果数据进行归一化,使其显示在0-1,可以理解为0-255,这样你可以直观看到结果图片的亮度,

匹配结果
/*
cv::matchTemplate(Mat src, Mat template,Mat result,int method,Mat mask);
如果你的图片是WxH,模板是wxh,那么你会得到一个(W-w+1)*(H-h+1)的图片,mask不是必须参数

这里的result就是得到值,也是个Mat对象,这里的元素都是相关的匹配方法的计算结果数值, 你可以通过minMaxLoc()来定位出最小值(我们这里用方差匹配),和最小值所在的位置,这个位置就是图片中位置,就是滑动时,模板在图像中的最上角的像素点,
也就是说,你得到的result通过minMaxLoc()可以得到方法匹配数值结果和模板在图片中左上角的像素点坐标,下一步你可以根据这个信息,来画个框,框出你的匹配结果
*/
int main() {
    Mat tmp = imread("C:/Users/sherlock/Pictures/template.jpg");
    Mat src = imread("C:/Users/sherlock/Pictures/test.jpg");
    Mat result;
    Mat dst;
    src.copyTo(dst);

    Point minLoc;
    double minVal;
    matchTemplate(src, tmp, result, TM_SQDIFF_NORMED);
    
    cout << result.size() << endl;
    
    minMaxLoc(result, &minVal, 0, &minLoc, 0);
     
    Point result_rect = minLoc;
    Size result_size = tmp.size();
    rectangle(dst, Rect(minLoc, tmp.size()), Scalar(0, 100, 255), 1, 8, 0);
     
    cout << minLoc << endl;
    cout << minVal << endl;
    imshow("tmp", tmp);
    imshow("src", src);
    imshow("result", result);
    imshow("drawResult", dst);

    waitKey(0);
    return 0;
}

最大值最小值定位函数cv::minMalLoc();

cv::minMaxLoc(Mat src, double* minVal, double* maxVal, Point* minLoc, Point* maxLoc);
必须是8bit channels=1

Mat src = (Mat_<uchar>(2,2)<<28,21,12,3);
src.convertTo(src,CV_8UC1);
Point minLoc,maxLoc;
double minVal,maxVal;
minMaxLoc(src,&minVal,&maxVal,&minLoc,&maxLoc);
cout<<src<<endl;
cout<<minLoc<<endl;
cout<<minVal<<endl;

第六章 目标追踪

目标追踪算法apihttps://docs.opencv.org/3.4.10/d9/df8/group__tracking.html

tracking是基于视觉的目标跟踪扩展库,在opencv_contrib模块中

在tracking模块中,包含了很多目标跟踪的class,例如:

cv::Detector
cv::MultiTracker
cv::Tracker
cv::TrackerKCF  //显然这是cv::Tracker的子类,继承了Tracker的cen
cv::TrackerMIL
   

首先要包含tracker头文件

#include <opencv2/tracking/tracker.hpp>

创建追踪器(由于创建的是个指针类,所以采用->符号来访问类函数(方法))

Ptr<TrackerMIL> tracker= TrackerMIL::create();
Ptr<TrackerTLD> tracker= TrackerTLD::create();
Ptr<TrackerKCF> tracker = TrackerKCF::create();
Ptr<TrackerMedianFlow> tracker = TrackerMedianFlow::create();
Ptr<TrackerBoosting> tracker= TrackerBoosting::create();

tracker->init(frame,bbox);
tracker->update(frame,bbox);

多目标追踪器

MultiTracker multi_trackers;
multi_trackers.add();
multi_trakcers.getObjects();
multi_trackers.getObjects().size();

bool cv::MultiTracker::add(Ptr<Tracker> newTracker,InputArray image,const Rect2d &boundingBox);
//.add()方法返回一个bool变量,表示成功与否,这里是添加一个追踪器和感兴趣box
bool cv::MultiTracker::add(vectro<Ptr<Tracker>> newTrackers, InputArray image, vector<Rect2d> boundingBoxes);
//.add()方法的重载是添加一组追踪器和对应感兴趣boxes

const vector<Rect2d> & cv::MultiTracker::getObjects() const;
//.getObjects()方法是返回MultiTracker类对象的objects成员,这个成员就是存储了全部的bboxes,是vector<Rect2d>类型,就是返回了个vector对象
//所以可以通过.size()方法来进行.getObjects().size()获得bboxes总个数,即vector总长度
//也可以通过[]访问某个bbox,即.getObjects9()[k]访问第k个bbox,得到的是个Rect类对象

bool::cv::MultiTracker::update(InputArray image)
//更新跟踪器状态,更新结果保存在MUltiTracker对象内部的跟踪器成员中
bool::cv::MultiTracker::update(InputArray image,std::vector<Rect2d> &boundingBoxes)
//更新一组跟踪器状态,这是必须要进行的操作,否则跟踪器会不知道当前目标的位置

选择ROI区域,并获得包围框

void selectROIs(String &windowName, InputArray image,DetectionBasedTracker::TrackedObject::PositionsVector &boundindBoxes, bool showCrosshair = true, bool fromCenter = false);
//这是获得多个roi框,你需要vector<Rect2d> bboxes;⚠️没有返回值

Rect selectROI(String &windowName, InputArray image, bool showCrosshair = true, bool fromCenter = false);
//获得一个roi框,你需要一个Rect bbox;⚠️返回的是该Rect框

第七章 其他

时间函数

// 利用getTickCount()
// 利用getTickFrequency()
// 时间计算逻辑是:
    // 获得操作系统启动到当前的计时周期数, 获得CPU频率,单位是 重复次数/秒
    // 根据周期数差值/CPU频率,可以得到秒数

int start_time = getTickCount();
for (int i=0;i<100;i++){
    cout<<“nothing”<<endl;
}
int end_time = (getTickCount()-start_time)/getTickFrequency();
cout<<end_time<<“s”<<endl;

 

猜你喜欢

转载自blog.csdn.net/Mrsherlock_/article/details/109555748