C++ opencv形态学、轮廓查找、特征检测和图像分割

news2024/11/19 16:40:13

C++ opencv形态学、轮廓查找、特征检测和图像分割

  • 形态学基本处理方法
    • 二值化
      • 全局二值化
      • 局部二值化
    • 腐蚀和膨胀
    • 图像形态学运算
      • 开运算
      • 闭运算
      • 顶帽
      • 黑帽
      • 代码
  • 图像轮廓
    • 寻找轮廓
    • 绘画轮廓
    • 轮廓的面积和周长
    • 多边形逼近和凸包
      • 多边形逼近
      • 凸包
    • 外接矩形
      • 最小外接矩形
      • 最大外接矩形
  • 案例
    • 车辆检测(简易)
  • 特征检测
    • Harris角点检测:
    • Shi-Tomasi角点检测
    • SIFT特征点检测
      • SIFT关键点
      • 关键点描述子
    • SURF特征点检测
      • SURF关键点和描述子
      • 继承cv::xfeatures2d::SURF纯抽象类
      • 继承类的使用
    • ORB实时特征检测
      • ORB的关键点和描述子
  • 特征点匹配
    • FLANN 最快邻近区特征匹配方法
  • 图像查找
  • 案例图像拼接
    • 获取单应性矩阵
    • 图像拼接
    • main函数
  • 图像分割
    • 传统的图像分割方法
      • 分水岭法
      • GrabCut法
      • MeanShift法
      • 背景扣除法
  • 人脸识别
  • Mat 矩阵
  • 数据转换
    • Vector 和 Mat 互换
    • 数据类型转换
    • 坐标点类型转换

形态学基本处理方法

基于图像形态进行处理的一些基本方法,这些处理方法基本是对二进制图像进行处理,卷积核决定着图像处理后的效果。
步骤:灰度图像->二值化->形态学运算

二值化

double threshold( InputArray src, OutputArray dst,
                  double thresh, double maxval, int type );
/* @param src 输入数组(多通道,8位或32位浮点)
@param dst 输出数组的大小和类型和通道数量与SRC相同.
@param thresh 阈值.
@param maxval 与#THRESH_BINARY和#THRESH_BINARY_INV阈值类型一起使用的最大值。
@param type 阈值类型 (see #ThresholdTypes).
@return 如果使用 Otsu's 或 Triangle方法,则为计算的阈值。
@sa  自适应阈值,寻找轮廓,比较,最小,最大*/

阈值类型

	THRESH_BINARY     = 0, //! 二值化(低于阈值变黑,高于阈值变为最大值)
    THRESH_BINARY_INV = 1, //!反二值化(与之相反)
    THRESH_TRUNC      = 2, //! 大于阈值时变为阈值量,其他情况不变
    THRESH_TOZERO     = 3, //! 趋于零(仅地于阈值时变0,其他情况不变)
    THRESH_TOZERO_INV = 4, //! 反趋于0(仅高于阈值时变0,其他情况不变)
    THRESH_MASK       = 7,
    THRESH_OTSU       = 8, //!< 使用Otsu algorithm 选择最优阈值
    THRESH_TRIANGLE   = 16 //!< 使用Triangle algorithm 选择最优阈值

在这里插入图片描述

全局二值化

	cv::Mat thresholdImg;
	// OTSU算法获取最优阈值
	double optimal_threshold = cv::threshold(cartoonGray, thresholdImg, 0, 0, cv::THRESH_OTSU);
	// 二值化处理
	cv::threshold(cartoonGray, thresholdImg, optimal_threshold, 255, cv::THRESH_BINARY);
	cv::imshow("cartoonGray", cartoonGray);
	cv::imshow("thresholdImg", thresholdImg);

	cv::waitKey(0);

在这里插入图片描述

局部二值化

void adaptiveThreshold( InputArray src, OutputArray dst,
                                     double maxValue, int adaptiveMethod,
                                     int thresholdType, int blockSize, double C );
@param src 源8位单通道图像。.
@param dst 目标图像的大小和类型与src相同.
@param maxValue 非零值赋给满足条件的像素
@param adaptiveMethod 要使用的自适应阈值算法,请参见#AdaptiveThresholdTypes。#BORDER_REPLICATE | #BORDER_ISOLATED 用于处理边界。
@param thresholdType 阈值类型,必须是其中之一 #THRESH_BINARY or #THRESH_BINARY_INV, see #ThresholdTypes.
@param blockSize 像素邻域的大小,用于计算像素的阈值:357等等。
@param C 常数减去平均值或加权平均值(详见下文)。通常,它是正的,但也可能是零或负的。
@sa  threshold, blur, GaussianBlur
	cv::Mat adaptiveThresholdImg;
	// 自适应阈值二级化处理/局部二值化
	cv::adaptiveThreshold(cartoonGray, adaptiveThresholdImg, 255,
									cv::ADAPTIVE_THRESH_GAUSSIAN_C,
									cv::THRESH_BINARY,3,0);

在这里插入图片描述

腐蚀和膨胀

腐蚀原理:锚点所在的像素点卷积核形状若没有全部覆盖白色区域,则变0(需要根据卷积核决定)。
膨胀原理:锚点所在的像素点卷积核形状若有一个接触到白色区域,则变1(需要根据卷积核决定)。
以下为腐蚀过程,传入一个5x5的全1卷积核,卷积核的锚点所重叠的像素点的颜色取决于,卷积核的形状覆盖全部白色区域时为1,反之则为0。最里面的虚线矩形为最终腐蚀后的图像。
在这里插入图片描述

	cv::Mat erode_img, dilate_img;
	// 获取卷积核(MORPH_CROSS十字形,RECT矩形,ELLIPSE椭圆形)
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(3, 3));
	// 腐蚀
	cv::erode(adaptiveThresholdImg, erode_img, kernel);
	// 膨胀
	cv::dilate(adaptiveThresholdImg, dilate_img, kernel);
	cv::imshow("erode_img", erode_img);
	cv::imshow("dilate_img", dilate_img);

在这里插入图片描述

图像形态学运算

void morphologyEx( InputArray src, OutputArray dst,
                                int op, InputArray kernel,
                                Point anchor = Point(-1,-1), int iterations = 1,
                                int borderType = BORDER_CONSTANT,
                                const Scalar& borderValue = morphologyDefaultBorderValue() );
/* @param src 源图像。通道的数量可以是任意的。深度应为CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
@param dst 目标图像的大小和类型与源图像相同
@param op 形态操作的类型,请参见#MorphTypes
@param kernel 结构元素。可以使用#getStructuringElement.
@param anchor 与内核的锚定位置。负值表示锚点位于核中心
@param iterations 腐蚀和膨胀的次数。
@param borderType 像素外推方法,参见#BorderTypes。#BORDER_WRAP不支持。
@param borderValue 在边界不变的情况下的边界值。缺省值具有特殊含义。
@sa  dilate, erode, getStructuringElement
@note 迭代次数是应用侵蚀或膨胀操作的次数。例如,具有两个迭代的打开操作(#MORPH_OPEN)等价于连续应用: erode -> erode -> dilate -> dilate (and not erode -> dilate -> erode -> dilate)。*/

开运算

先腐蚀再膨胀。作用是对背景去噪

闭运算

先膨胀再腐蚀。作用是对物件内部去噪

顶帽

原图 - 开运算。作用是提取背景的噪声

黑帽

原图 - 闭运算。作用是提取物件内部的噪声

代码

cv::Mat open_img, close_img, tophat_img, blackhat_img;
	// 开运算
	cv::morphologyEx(adaptiveThresholdImg, open_img, cv::MORPH_OPEN, kernel);
	// 闭运算
	cv::morphologyEx(adaptiveThresholdImg, close_img, cv::MORPH_CLOSE, kernel);
	// 顶帽
	cv::morphologyEx(adaptiveThresholdImg, tophat_img, cv::MORPH_TOPHAT, kernel);
	// 黑帽
	cv::morphologyEx(adaptiveThresholdImg, blackhat_img, cv::MORPH_BLACKHAT, kernel);

	cv::imshow("srcImg", adaptiveThresholdImg);
	cv::imshow("open_img", open_img);
	cv::imshow("close_img", close_img);
	cv::imshow("tophat_img", tophat_img);
	cv::imshow("blackhat_img", blackhat_img);
	cv::waitKey(0);

在这里插入图片描述
在这里插入图片描述

图像轮廓

轮廓:具有相同颜色和强度连续点的线条
作用:图形分析、物体的识别和检测

寻找轮廓

void findContours( InputArray image, OutputArrayOfArrays contours,
                              OutputArray hierarchy, int mode,
                              int method, Point offset = Point());
/*@param image 一个8位单通道图像。非零像素被视为1。零像素保持为0,因此图像被视为二值。你可以使用#compare,#inRange, #threshold ,#adaptive #Threshold, #Canny,等,以创建灰度或彩色的二值图像。如果mode等于#RETR_CCOMP或#RETR_FLOODFILL,则输入也可以是标签的32位整数图像(CV_32SC1)。
@param contours 检测到的轮廓。每个轮廓都存储为点的向量(e.g.
std::vector<std::vector<cv::Point> >).
@param hierarchy Optional output vector (e.g. std::vector<cv::Vec4i>), 包含映像拓扑信息。它的元素和等高线的数量一样多. 对于每i个轮廓 contours[i], 元素 hierarchy[i][0] , hierarchy[i][1] , hierarchy[i][2] , 和 hierarchy[i][3] 分别设置为同一层次级别下一个和上一个轮廓、第一个子轮廓和父轮廓的轮廓的基于0的指数。如果对于轮廓i没有下一个、上一个、父或嵌套的轮廓,则hierarchy[i] 对应的元素是负的。
@note 在Python中,层次结构嵌套在顶层数组中。使用hierarchy[0][i]访问第i个等高线的层次元素。
@param mode 轮廓检索模式,参见#RetrievalModes
@param method 轮廓逼近法,参见#ContourApproximationModes
@param offset 可选偏移量,每个等高线点通过该偏移量进行偏移。如果从图像ROI中提取轮廓,然后在整个图像上下文中进行分析,那么这是很有用的。 */
#RetrievalModes
	/** 只检索极端的外部轮廓。它为所有轮廓设置' hierarchy[i][2]=hierarchy[i][3]=-1 '。 */
    RETR_EXTERNAL  = 0,
    /** 检索所有轮廓,而不建立任何层次关系 */
    RETR_LIST      = 1,
    /** 检索所有轮廓并将它们组织到一个两级层次结构中。在顶层,有组件的外部边界。在第二层,有洞的边界。如果连接组件的孔内有另一个轮廓线,它仍然放在顶层。*/
    RETR_CCOMP     = 2,
    /** 检索所有轮廓并重构嵌套轮廓的完整层次结构。*/
    RETR_TREE      = 3,
    RETR_FLOODFILL = 4 //!<
#ContourApproximationModes
	/** 存储了所有的等高线点。也就是说,轮廓线的任意2个后续点(x1,y1)和(x2,y2)要么是水平的,要么是垂直的,要么是对角线,即max(abs(x1-x2),abs(y2-y1))==1。*/
    CHAIN_APPROX_NONE      = 1,
    /** 压缩水平段、垂直段和对角线段,只留下它们的端点。例如,一个由上至右的矩形轮廓用4个点编码。 */
    CHAIN_APPROX_SIMPLE    = 2,
    /** 应用Teh-Chin链近似算法的一种风格@cite TehChin89*/
    CHAIN_APPROX_TC89_L1   = 3,
    /** 应用Teh-Chin链近似算法的一种风格@cite TehChin89*/
    CHAIN_APPROX_TC89_KCOS = 4

绘画轮廓

void drawContours( InputOutputArray image, InputArrayOfArrays contours,
                              int contourIdx, const Scalar& color,
                              int thickness = 1, int lineType = LINE_8,
                              InputArray hierarchy = noArray(),
                              int maxLevel = INT_MAX, Point offset = Point() );
/*@param image 目标图像.
@param contours 所有的输入轮廓。每个轮廓被存储为一个点向量。
@param contourIdx 指示要绘制的轮廓的参数。如果它是负的,就画出所有的等高线。
@param color 轮廓的颜色.
@param thickness 绘制等高线的线条粗细。如果为负值(例如,thickness=#FILLED),则绘制内部轮廓。
@param lineType 线连接度. 参见 #LineTypes
@param hierarchy 关于层次结构的可选信息。只有当你只想画一些等高线时才需要 (see maxLevel ).
@param maxLevel 绘制等高线的最大水平。如果为0,则只绘制指定的轮廓.
如果为1,则该函数绘制轮廓线和所有嵌套的轮廓线。如果是2,则表示函数
绘制等高线、所有嵌套等高线、所有嵌套到嵌套等高线,等等。 只有当存在可用的层次结构时,才会考虑此参数。
@param offset 轮廓移位参数。移动所有绘制的等高线的指定 \f$\texttt{offset}=(dx,dy)\f$ .
@note 当thickness=#FILLED时,该函数被设计为正确处理带有孔的连接组件,即使没有提供层次结构数据。 这是通过使用奇偶法则分析所有的轮廓来完成的。如果你有一个单独检索的轮廓的联合集合,这可能会给出不正确的结果。为了解决这个问题,您需要为每个等高线子组分别调用#drawContours,或者使用contourIdx参数遍历集合。*/
	cv::Mat img_gray,thresholdImg;
	// 转灰度图像
	cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
	// 二值化
	cv::threshold(img_gray, thresholdImg, 180, 255, cv::THRESH_BINARY);
	// 储存轮廓坐标的数组
	std::vector<std::vector<cv::Point>>  ContoursPointArray;
	// 寻找轮廓 
	// RETR_CCOMP 存储顺序是最里面的为顶层,先到左边顶层由外到内编号,再到左边顶层由外到内编号;接着到第二层,先到左边第二层由外到内编号,再到左边第二层由外到内编号;由此类推。
	// RETR_TREE 存储顺序是先右边由外向内,再往左边由外向内,依次编号(较常用)
	// RETR_LIST 存储顺序是先找右边顶层的内框,再找左边顶层的内框,接着右外,再到左外;下一层亦是如此。
	cv::findContours(thresholdImg, ContoursPointArray, cv::RETR_TREE, cv::CHAIN_APPROX_TC89_KCOS);
	//std::cout << ContoursPointArray[0];
	// 绘画轮廓 
	cv::drawContours(img, ContoursPointArray, -1, { 0,0,255 }, 1,16);
	cv::imshow("img_gray", img_gray);
	cv::imshow("src", img);
	cv::imshow("thresholdImg", thresholdImg);
	cv::waitKey(0);

RETR_CCOMP 存储顺序是最里面的为顶层,先到左边顶层由外到内编号,再到左边顶层由外到内编号;接着到第二层,先到左边第二层由外到内编号,再到左边第二层由外到内编号;由此类推。
在这里插入图片描述
RETR_TREE 存储顺序是先右边由外向内,再往左边由外向内,依次编号(较常用)
在这里插入图片描述
RETR_LIST 存储顺序是先找右边顶层的内框,再找左边顶层的内框,接着右外,再到左外;下一层亦是如此。
在这里插入图片描述

轮廓的面积和周长

/*@param contour 二维点(轮廓顶点)的输入向量,存储在std::vector或Mat中
@param oriented 定向区域标志。如果为true,函数将返回一个带符号的面积值,该值取决于轮廓方向(顺时针或逆时针)。使用这个特性,您可以通过取一个区域的符号来确定轮廓的方向。缺省情况下,该参数为false,即返回绝对值。*/
double contourArea( InputArray contour, bool oriented = false );
/*@param curve 二维点(轮廓顶点)的输入向量,存储在std::vector或Mat中
@param closed 指示曲线是否关闭的标志。*/
double arcLength( InputArray curve, bool closed );

多边形逼近和凸包

多边形逼近

void approxPolyDP( InputArray curve,
                                OutputArray approxCurve,
                                double epsilon, bool closed );
/*@param curve 二维点的输入向量存储在std::vector或Mat中
@param approxCurve 近似的结果。类型应该与输入曲线的类型相匹配.
@param epsilon 指定近似精度的参数。这是原始曲线与其近似值之间的最大距离。
@param closed 如果为真,则近似曲线是闭合的(它的第一个顶点和最后一个顶点是相连的)。否则,它没有关闭。*/
	cv::Mat hand = cv::imread("C:\\Users\\10358\\Desktop\\img\\hand.jpg");
	cv::Mat hand_gray, handThresholdImg;
	// 转灰度图像
	cv::cvtColor(hand, hand_gray, cv::COLOR_BGR2GRAY);
	// 二值化 (阈值要恰当)
	cv::threshold(hand_gray, handThresholdImg, 230, 255, cv::THRESH_BINARY_INV);
	std::vector<std::vector<cv::Point>>  handContoursPointArray;
	std::vector<cv::Point>  approxArray;
	// 寻找轮廓
	cv::findContours(handThresholdImg, handContoursPointArray,cv::RETR_TREE,cv::CHAIN_APPROX_SIMPLE);
	// 多边形逼近
	cv::approxPolyDP(handContoursPointArray[0], approxArray, 10, true);
	// 绘画轮廓
	cv::polylines(hand, approxArray, true, { 0,0,255 }, 3);

	cv::imshow("handImg", hand);
	cv::imshow("handThresholdImg", handThresholdImg);
	cv::waitKey(0);

在这里插入图片描述

凸包

void convexHull( InputArray points, OutputArray hull,
                              bool clockwise = false, bool returnPoints = true );
/*@param points 输入2D点集,存储在std::vector或Mat中。
@param hull 输出凸包。它要么是指数的整数向量,要么是点的整数向量。在第一种情况下,包体元素是原始数组中凸包点的基于0的索引(因为凸包点集是原始点集的子集)。第二种情况,hull元素就是hull点本身。
@param clockwise 定位标志。如果为真,则输出凸包为顺时针方向。否则,它是逆时针方向的。假设的坐标系X轴指向右,Y轴指向上。
@param returnPoints 操作标记。对于矩阵,当标志为真时,函数返回凸包点。否则,它返回凸包点的索引。 当输出数组为 std::vector, 该标志被忽略。输出取决于向量的类型:std::vector<int>表示returnPoints=false, std::vector<Point>表示returnPoints=true。

@note ' points '和' hull '应该是不同的数组,不支持就地处理*/
	cv::Mat hand = cv::imread("C:\\Users\\10358\\Desktop\\img\\hand.jpg");
	cv::Mat hand_gray, handThresholdImg;
	// 转灰度图像
	cv::cvtColor(hand, hand_gray, cv::COLOR_BGR2GRAY);
	// 二值化 (阈值要恰当)
	cv::threshold(hand_gray, handThresholdImg, 230, 255, cv::THRESH_BINARY_INV);
	std::vector<std::vector<cv::Point>>  handContoursPointArray;
	std::vector<cv::Point>  approxArray, convexHullArray;
	// 寻找轮廓
	cv::findContours(handThresholdImg, handContoursPointArray,cv::RETR_TREE,cv::CHAIN_APPROX_SIMPLE);
	// 多边形逼近
	cv::approxPolyDP(handContoursPointArray[0], approxArray, 10, true);
	// 绘画多边形逼近轮廓
	cv::polylines(hand, approxArray, true, { 0,0,255 }, 3);
	// 凸包
	cv::convexHull(handContoursPointArray[0], convexHullArray);
	// 绘画凸包轮廓
	cv::polylines(hand, convexHullArray, true, { 0,0,255 }, 3);
	
	cv::imshow("handImg", hand);
	cv::imshow("handThresholdImg", handThresholdImg);
	cv::waitKey(0);

在这里插入图片描述

外接矩形

最小外接矩形

	cv::Mat img_gray,thresholdImg;
	// 转灰度图像
	cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
	// 二值化
	cv::threshold(img_gray, thresholdImg, 180, 255, cv::THRESH_BINARY);
	std::vector<std::vector<cv::Point>>  ContoursPointArray;
	// 寻找轮廓 
	cv::findContours(thresholdImg, ContoursPointArray, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
	// 最小外接矩阵
	cv::RotatedRect min_rect = cv::minAreaRect(ContoursPointArray[0]);
	//cv::boxPoints(min_rect, boxArray);
	// 绘制圆心
	cv::circle(img, cv::Point(min_rect.center.x, min_rect.center.y), 5, cv::Scalar(0, 255, 255), -1);
	cv::Point2f rect[4];
	// 存储点集
	min_rect.points(rect);
	for (int j = 0; j < 4; j++) {
		line(img, rect[j], rect[(j + 1) % 4], cv::Scalar(0, 0, 255), 2, 8);  //绘制最小外接矩形每条边
	}
	
	cv::imshow("src", img);
	cv::imshow("thresholdImg", thresholdImg);

在这里插入图片描述

最大外接矩形

	cv::Mat img_gray,thresholdImg;
	// 转灰度图像
	cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
	// 二值化
	cv::threshold(img_gray, thresholdImg, 180, 255, cv::THRESH_BINARY);
	std::vector<std::vector<cv::Point>>  ContoursPointArray;
	// 寻找轮廓 
	cv::findContours(thresholdImg, ContoursPointArray, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
	// 最大外接矩阵
	cv::Rect max_rect = cv::boundingRect(ContoursPointArray[0]);
	cv::rectangle(img, max_rect, cv::Scalar(0, 255, 255), 1, 16);
	
	cv::imshow("src", img);
	cv::imshow("thresholdImg", thresholdImg);

在这里插入图片描述

案例

车辆检测(简易)

	// 检测线高度
	int lineHight = 550;
	// 检测偏移量
	int offSet = 4;
	// 车辆统计
	int count = 0;
	int ESC = 27;
	cv::VideoCapture video = cv::VideoCapture("C:\\Download\\video\\videovideo.mp4");
	cv::Mat frame, frameGray, frameThreshold, GausBlur,mask,erode_mask, dilate_mask, close_mask;
	// 创建背景
	cv::Ptr<cv::BackgroundSubtractorMOG2> bgsubmog2 = cv::createBackgroundSubtractorMOG2();
	// 卷积核
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(7, 7));
	// 存储视频对象
	//cv::VideoWriter vw = cv::VideoWriter("C:\\Download\\video\\img.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 25, cv::Size(1280, 720));
	//cv::VideoWriter vw = cv::VideoWriter("C:\\Download\\video\\car.mp4", cv::VideoWriter::fourcc('m', 'p', '4', 'v'), 25, cv::Size(1280, 720));
	std::vector<cv::Point> cars = {};
	while (true)
	{
		video.read(frame);
		if (frame.empty()) {
			break;
		}
		// 灰度
		cv::cvtColor(frame, frameGray, cv::COLOR_RGB2GRAY);
		//double optimal_threshold = cv::threshold(frameGray, frameThreshold, 0, 0, cv::THRESH_OTSU);
		//cv::threshold(frameGray, frameThreshold, 80, 255, cv::THRESH_BINARY);
		// 去噪
		cv::GaussianBlur(frameGray, GausBlur, cv::Size(3, 3), 10);
		// 去背景
		bgsubmog2->apply(GausBlur, mask);
		// 腐蚀	
		cv::erode(mask, erode_mask, kernel);
		// 膨胀	
		cv::dilate(erode_mask, dilate_mask, kernel, cv::Point(-1, -1),3);
		// 闭运算 祛除物体内噪点
		cv::morphologyEx(dilate_mask, close_mask, cv::MORPH_CLOSE, kernel);
		cv::morphologyEx(close_mask, close_mask, cv::MORPH_CLOSE, kernel);
		// 寻找轮廓
		std::vector<std::vector<cv::Point>> carCts;
		cv::findContours(close_mask, carCts, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
		// frame[1336 x 754]
		cv::line(frame, cv::Point(10, lineHight), cv::Point(1300, lineHight), cv::Scalar(255, 255, 0), 3, 16);
		for (size_t i = 0; i < carCts.size(); i++)
		{
			// 最大外接矩形
			cv::Rect max_rect = cv::boundingRect(carCts[i]);
			// 绘画目标矩形
			if (max_rect.width > 110 && max_rect.height > 110)
			{
				cv::rectangle(frame, max_rect, cv::Scalar(0, 0, 255), 1, 16);
				// 将目标车辆的中心点加入数组
				cv::Point cPoint((int)(max_rect.x + max_rect.width / 2), (int)(max_rect.y + max_rect.height / 2));
				cars.push_back(cPoint);
			}
			// 检测通过车辆
			for (cv::Point point : cars) {
				if (point.y < lineHight + offSet && point.y > lineHight - offSet) {
					count += 1;
					cars.clear();
				}
			}
		}
		cv::putText(frame, "Cars Count: " + std::to_string(count), cv::Point(600, 60), cv::FONT_HERSHEY_SIMPLEX, 2, cv::Scalar(255, 255, 0), 3, 16);
		// 写入帧
		/*cv::resize(frame, frame, cv::Size(1280, 720));
		vw.write(frame);*/
		cv::imshow("close_mask", close_mask);
		cv::imshow("frame", frame);
		if (cv::waitKey(1) == ESC) {
			break;
		}
	}
	video.release();
	//vw.release();
	cv::destroyAllWindows();

特征检测

图像特征就是指有意义的图像区域具有独特性、易于识别性,比如角点、斑点以及高密度区。
角点:在特征中最重要的是角点,灰度梯度的最大值对应的像素,两条线的交点,极值点(一阶导数最大值,但二阶导数为0)

Harris角点检测:

光滑地区,无论向哪里移动,衡量系数不变。
边缘地址,垂直边缘移动时,衡量系统变化具烈。
在交点处,往那个方向移动,衡量系统都变化具烈。

void cornerHarris( InputArray src, OutputArray dst, int blockSize,
                                int ksize, double k,
                                int borderType = BORDER_DEFAULT );
/*
@param src 输入单通道8位或浮点图像。
@param dst 存储Harris检测器响应的图像。它的类型为CV_32FC1,大小与src相同。
@param blockSize 邻域大小,即扫描窗口的大小。
@param ksize Sobel算子的孔径参数。
@param k Harris探测器自由参数。参见上面的公式。
@param borderType 像素外推法。看到#BorderTypes。不支持#BORDER_WRAP。
*/
	// 灰度图像
	cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
	// harris角点检测
	cv::cornerHarris(img_gray, harris_img, 2, 3, 0.04);
	// 获取mat最大值最小值及其坐标
	double minVal, maxVal;
	cv::Point minPoint, maxPoint;
	cv::minMaxLoc(harris_img, &minVal, &maxVal, &minPoint, &maxPoint);
	//std::cout << "minVal" << minVal << "maxVal" << maxVal;
	// 找出角点区域(BGR)
	for (size_t i = 0; i < img.rows; i++)
	{
		uchar* tmp = img.ptr<uchar>(i);
		float_t* tmp2 = harris_img.ptr<float_t>(i);
		for (size_t j = 0, z=0; j < (size_t)img.cols*3; z++,j+=3)
		{
			if ((float_t)tmp2[z] > maxVal*0.01)
			{
				//std::cout << "tmp2[z]" << (float_t)tmp2[z] << "maxVal" << maxVal;
				//tmp[j] = 255;
				//tmp[j+1]= 0;
				tmp[j+2] = 255;
			}
		}
	}
	//找出角点区域(二值图)
	/*double threshold = 0.0001;
	cv::threshold(harris_img, harris_img,
		threshold, 255, cv::THRESH_BINARY_INV);*/
	cv::imshow("img", img);
	cv::imshow("img_gray", img_gray);
	cv::waitKey(0);

在这里插入图片描述

Shi-Tomasi角点检测

Shi-Tomasi是基于Harris角点检测所改进的。Harris 角点检测算的稳定性和k有关,而k是个经验值,不好设定最佳值。

void goodFeaturesToTrack( InputArray image, OutputArray corners,
                                     int maxCorners, double qualityLevel, double minDistance,
                                     InputArray mask = noArray(), int blockSize = 3,
                                     bool useHarrisDetector = false, double k = 0.04 );
/*
@param image 输入8位或浮点32位,单通道图像。
@param corners 检测角的输出向量。
@param maxCorners 返回的最大拐角数。如果找到的角比找到的多,就把其中最强的那个退回去。' maxCorners <= 0 '表示未设置最大值限制,并返回所有检测到的角。
@param qualityLevel 表征图像角的最小可接受质量的参数。参数值乘以最佳角质量度量值,即最小特征值(见#cornerMinEigenVal)或Harris函数响应(见#cornerHarris)。质量指标低于产品质量的拐角被拒收。例如,如果最佳角的质量度量值=1500,而qualityLevel=0.01,那么所有质量度量值小于15的角都将被拒绝。
@param minDistance 返回角之间的最小可能欧氏距离。
@param mask 可选的兴趣区域。如果图像不是空的(它需要有类型CV_8UC1并且与图像大小相同),它指定检测角的区域。
@param blockSize 在每个像素邻域上计算导数协变矩阵的平均块的大小。 See cornerEigenValsAndVecs
@param useHarrisDetector 是否使用Harris探测器的参数(see #cornerHarris)or #cornerMinEigenVal
@param k Harris探测器的自由参数。
*/
	// 灰度图像
	cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
	// Shi-Tomasi角点检测
	std::vector<cv::Point> Tomasi_array;
	cv::goodFeaturesToTrack(img_gray, Tomasi_array, 1000, 0.01, 10);
	for (cv::Point point: Tomasi_array)
	{
		cv::circle(img, point, 2, cv::Scalar(0, 0, 255), -1);
	}
	cv::imshow("img", img);
	cv::imshow("img_gray", img_gray);
	cv::waitKey(0);

在这里插入图片描述

SIFT特征点检测

关键点:位置,大小和方向
关键点描述子:记录了关键点周围对其有贡献的像素点的一组向量值,其不受仿射变换、光照变换等影响
优点:关键点检测准确和描述子十分详细
缺点:检测速度慢

SIFT关键点

// SIFT特征点检测
	cv::Ptr<cv::SIFT> sift = cv::SIFT().create();		// 创建SIFT
	std::vector<cv::KeyPoint> siftArray;
	// 获取关键点
	// detect(单通道图像,KeyPoint数组)
	sift->detect(img_gray, siftArray);
	// 绘画关键点
	// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
	cv::drawKeypoints(img_gray, siftArray, img);
	
		
	cv::imshow("img", img);
	cv::imshow("img_gray", img_gray);
	cv::waitKey(0);

在这里插入图片描述

关键点描述子

// SIFT特征点检测
	//cv::Ptr<cv::SIFT> sift = cv::SIFT().create();		// 实例后创建SIFT
	cv::Ptr<cv::SIFT> sift = cv::SIFT::create();		// 创建SIFT
	std::vector<cv::KeyPoint> siftArray;
	cv::Mat descriptorsArray;
	// 获取关键点和描述子
	// detectAndCompute(单通道图像,mask,KeyPoint数组, Mat数组)
	sift->detectAndCompute(img_gray, cv::Mat(),siftArray, descriptorsArray);
	// 第一个关键点的描述子
	std::cout << descriptorsArray(cv::Range(0,1),cv::Range(0,descriptorsArray.cols));
	// 绘画关键点
	// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
	cv::drawKeypoints(img_gray, siftArray, img);
	
	cv::imshow("img", img);
	cv::imshow("img_gray", img_gray);

在这里插入图片描述

SURF特征点检测

优点:检测速度得到了优化
缺点:准确性没SIFT高

SURF关键点和描述子

	// SURF特征点检测
	cv::Ptr<cv::xfeatures2d::SURF> surf = cv::xfeatures2d::SURF::create();
	std::vector<cv::KeyPoint> surfArray;
	cv::Mat descriptorsArray;
	// 获取关键点和描述子
	// detectAndCompute(单通道图像,mask,KeyPoint数组, Mat数组)
	surf->detectAndCompute(img_gray, cv::Mat(), surfArray, descriptorsArray);
	// 绘画关键点
	// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
	cv::drawKeypoints(img_gray, surfArray, img);
	
	cv::imshow("img", img);
	cv::imshow("img_gray", img_gray);
	cv::waitKey(0);
	

在这里插入图片描述

继承cv::xfeatures2d::SURF纯抽象类

抽象类: 成员函数中有纯虚函数,这种类叫抽象类,抽象类不能实例化 (不能创建对象)。 抽象类必须被继承且纯虚函数被覆盖后,由子类实例化对象。 如果继承抽象类,但没有覆盖纯虚函数,那么子类也将称为抽象类,不能实例化。
纯抽象类: 所有成员函数都是纯虚函数,这种只能被继承的类叫纯抽象类。 这种类一般用来设计接口,这种类在子类被替换后不需要被修改,或少量的修改即可继续使用。

	class MySURF: public cv::xfeatures2d::SURF
	{
	public:
		MySURF() {
			hessianThreshold = 0;
			nOctaves = 0;
			nOctaveLayers = 0;
			extended = 0;
			upright = 0;
		};
		
	protected:
		virtual void setHessianThreshold(double hessianThreshold) {
			this->hessianThreshold = hessianThreshold;
		};
		virtual double getHessianThreshold() const {
			return this->hessianThreshold;
		};

		virtual void setNOctaves(int nOctaves) {
			this->nOctaves = nOctaves;
		};
		virtual int getNOctaves() const{
			return this->nOctaves;
		};

		virtual void setNOctaveLayers(int nOctaveLayers) {
			this->nOctaveLayers = nOctaveLayers;
		};
		virtual int getNOctaveLayers() const {
			return this->nOctaveLayers;
		};

		virtual void setExtended(bool extended)  {
			this->extended = extended;
		};
		virtual bool getExtended() const {
			return this->extended;
		};

		virtual void setUpright(bool upright)  {
			this->upright = upright;
		};
		virtual bool getUpright() const {
			return upright;
		};
	private:
		double hessianThreshold;
		int nOctaves, nOctaveLayers;
		bool extended, upright;
	};

继承类的使用

	// SURF特征点检测
	cv::Ptr<cv::xfeatures2d::SURF> surf = MySURF().create();
	std::vector<cv::KeyPoint> surfArray;
	cv::Mat descriptorsArray;
	// 获取关键点和描述子
	// detectAndCompute(单通道图像,mask,KeyPoint数组, Mat数组)
	surf->detectAndCompute(img_gray, cv::Mat(), surfArray, descriptorsArray);
	// 绘画关键点
	// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
	cv::drawKeypoints(img_gray, surfArray, img);
	
	cv::imshow("img", img);
	cv::imshow("img_gray", img_gray);
	cv::waitKey(0);

在这里插入图片描述

ORB实时特征检测

ORB = Oriented FAST + Rotated BRIEF
优点:可以做到实时检测
缺点:准确性不如SURF和SIFT

ORB的关键点和描述子

// ORB实时特征点检测
	cv::Ptr<cv::ORB> orb = cv::ORB::create();
	std::vector<cv::KeyPoint> ORBArray;
	cv::Mat descriptorsArray;
	// 获取关键点和描述子
	// detectAndCompute(单通道图像,mask,KeyPoint数组, Mat数组)
	orb->detectAndCompute(img_gray, cv::Mat(), ORBArray, descriptorsArray);
	// 绘画关键点
	// drawKeypoints(单通道图像,KeyPoint数组,输出图像)
	cv::drawKeypoints(img_gray, ORBArray, img);
	
	cv::imshow("img", img);
	cv::imshow("img_gray", img_gray);
	cv::waitKey(0);

在这里插入图片描述

特征点匹配

## BF暴力匹配方法 原理:它使用第一组中的每个特征的描述子,与第二组中的所有特征描术子进行匹配计算它们之间的差距,然后将最接近一个匹配返回
	void match( InputArray queryDescriptors, InputArray trainDescriptors,
                CV_OUT std::vector<DMatch>& matches, InputArray mask=noArray() ) const;
/*	@param queryDescriptors 描述子的查询集。
    @param trainDescriptors  描述子的训练集。 这个集合不会被添加到存储在类对象中的描述子的训练集合中.
    @param matches 匹配结果集. 如果描述子的查询集在mask中被屏蔽,则不为该描述子添加匹配。因此,匹配结果集大小可能小于描述子的查询集计数。
    @param mask 可以指定在输入的查询集和描述子的训练集之间允许的匹配
*/
void drawMatches( InputArray img1, const std::vector<KeyPoint>& keypoints1,
                             InputArray img2, const std::vector<KeyPoint>& keypoints2,
                             const std::vector<DMatch>& matches1to2, InputOutputArray outImg,
                             const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1),
                             const std::vector<char>& matchesMask=std::vector<char>(), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );
/*
@param img1 第一张源图像.
@param keypoints1 第一个源图像的关键点.
@param img2 第二源图像.
@param keypoints2 第二个源图像的关键点.
@param matches1to2 从第一张图像匹配到第二张图像,这意味着keypoints1[i]在keypoints2[Matches [i]]中有一个对应的点。
@param outImg 输出图像。它的内容取决于定义在输出图像中绘制内容的flags值。请参阅下面可能的flags值。
@param matchColor 匹配的颜色(线和连接的关键点)。 如果matchColor==Scalar::all(-1), 颜色是随机生成的。
@param singlePointColor 单关键点的颜色 (圆圈), 这意味着关键点没有匹配. 如果singlePointColor==Scalar::all(-1) , 颜色是随机生成的。
@param matchesMask 掩码。决定绘制哪些匹配. 如果掩码为空,则绘制所有匹配项。
@param flags 标志设置绘图功能。可能的标志位值由DrawMatchesFlags定义。

这个函数从输出图像中的两个图像中提取关键点的匹配。匹配是连接两个关键点(圆)的一条线。参考cv::DrawMatchesFlags。
*/
	cv::Mat orig_gray, search_gray,result_img;
	// 灰度化
	cv::cvtColor(orig_img, orig_gray, cv::COLOR_BGR2GRAY);
	cv::cvtColor(search_img, search_gray, cv::COLOR_BGR2GRAY);
	// 创建SIFT
	cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
	std::vector<cv::KeyPoint> origImgSiftArray, searchImgSiftArray;
	cv::Mat origImgDescriptorsArray, searchImgDescriptorsArray;
	// 获取两张图的关键点和描述子
	sift->detectAndCompute(orig_gray, cv::Mat(), origImgSiftArray, origImgDescriptorsArray);
	sift->detectAndCompute(search_gray, cv::Mat(), searchImgSiftArray, searchImgDescriptorsArray);
	// BF特征点匹配
	cv::BFMatcher bf = cv::BFMatcher();
	std::vector<cv::DMatch> matchArray;
	// 获取每个描述子最佳的匹配向量
	bf.match(origImgDescriptorsArray, searchImgDescriptorsArray, matchArray);
	// 绘画匹配集
	cv::drawMatches(orig_img, origImgSiftArray, search_img, searchImgSiftArray, matchArray, result_img);

	cv::imshow("result_img", result_img);
	cv::waitKey(0);

在这里插入图片描述

FLANN 最快邻近区特征匹配方法

速度快,精确差

void knnMatch( InputArray queryDescriptors, InputArray trainDescriptors,
                   CV_OUT std::vector<std::vector<DMatch> >& matches, int k,
                   InputArray mask=noArray(), bool compactResult=false ) const;
/** @brief 从查询集中为每个描述子查找k个最佳匹配。

    @param queryDescriptors 描述子的查询集.
    @param trainDescriptors 描述子的训练集. 这个集合不会被添加到存储在类对象中的描述子的训练集合中。
    @param mask Mask 可以指定在输入的查询集和描述子的训练集之间允许的匹配
    @param matches Matches. 对于同一个查询描述子,每个Matches[i]是k个或更少的匹配。
    @param k 每个查询描述子找到的最佳匹配的计数,如果查询描述符的可能匹配总数少于k,则更少。
    @param compactResult 当mask(或多个mask)不为空时使用的参数。如果compactResult为false,则匹配向量的大小与queryDescriptors行相同。如果compactResult为true,则匹配向量不包含完全屏蔽查询描述符的匹配。
    这些DescriptorMatcher::match方法的扩展变体为每个查询描述符找到几个最佳匹配。匹配按距离递增顺序返回。有关查询和训练描述符的详细信息,请参见DescriptorMatcher::match
     */
	cv::Mat orig_gray, search_gray,result_img;
	// 灰度化
	cv::cvtColor(orig_img, orig_gray, cv::COLOR_BGR2GRAY);
	cv::cvtColor(search_img, search_gray, cv::COLOR_BGR2GRAY);
	// 创建SIFT
	cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
	std::vector<cv::KeyPoint> origImgSiftArray, searchImgSiftArray;
	cv::Mat origImgDescriptorsArray, searchImgDescriptorsArray;
	// 获取两张图的关键点和描述子
	sift->detectAndCompute(orig_gray, cv::Mat(), origImgSiftArray, origImgDescriptorsArray);
	sift->detectAndCompute(search_gray, cv::Mat(), searchImgSiftArray, searchImgDescriptorsArray);
	std::vector<std::vector<cv::DMatch>> matches;
	std::vector<cv::DMatch> goodmatch;
	cv::FlannBasedMatcher flann = cv::FlannBasedMatcher();
	// knnMatch 返回k个最匹配的向量
	flann.knnMatch(origImgDescriptorsArray, searchImgDescriptorsArray, matches, 2);
	// 过滤,寻找较好的匹配向量
	for (size_t i = 0; i < matches.size(); i++)
	{
		if (matches[i][0].distance < 0.7 * matches[i][1].distance) {
			goodmatch.push_back(matches[i][0]);
			}
	}
	// 绘画匹配集
	cv::drawMatches(orig_img, origImgSiftArray, search_img, searchImgSiftArray, goodmatch, result_img);
	cv::imshow("result_img", result_img);
	cv::waitKey(0);

在这里插入图片描述

图像查找

图像查找 = 特征匹配 + 单应性矩阵
单应性矩阵的作用:矫正图片, 透视变换的变换矩阵

Mat findHomography( InputArray srcPoints, InputArray dstPoints,
                                 int method = 0, double ransacReprojThreshold = 3,
                                 OutputArray mask=noArray(), const int maxIters = 2000,
                                 const double confidence = 0.995);
/* @brief 查找两个平面之间的透视变换。
@param srcPoints 原始平面中各点的坐标,类型为CV_32FC2 or vector<Point2f>的矩阵。
@param dstPoints 原始平面中各点在目标平面上的坐标, 类型为CV_32FC2 or vector<Point2f>的矩阵。
@param method 用于计算单应矩阵的方法。有以下几种方法:
-   **0** - 一种使用所有点的常规方法,即最小二乘法
-   @ref RANSAC - RANSAC-基于鲁棒的方法
-   @ref LMEDS - Least-中位数鲁棒法
-   @ref RHO - PROSAC-基于鲁棒的方法
@param ransacReprojThreshold 将点对视为内嵌函数时允许的最大重投影误差(仅在RANSAC和RHO方法中使用)。
@param mask 可选的输出掩码由鲁棒的方法设置( RANSAC or LMeDS ). 注意,输入掩码值将被忽略
@param maxIters RANSAC迭代的最大次数。
@param confidence 置信度,在0到1之间。
*/
CV_EXPORTS_W void perspectiveTransform(InputArray src, OutputArray dst, InputArray m );
/*
@brief 执行向量的透视矩阵变换	
函数cv::perspective tivetransform通过将src的每个元素作为2D或3D向量进行转换,即在搜索图中找到原图的坐标点。
@note 函数转换一个稀疏的2D或3D向量集合。如果你想使用透视转换来转换图像,请使用arpPerspective . 如果你有一个逆问题,也就是说,你想从几对对应的点中计算最可能的透视转换,你可以使用getPerspectiveTransform or findHomography.
@param src 输入双通道或三通道浮点数组;每一个元素是要转换的2D/3D向量。
@param dst 输出数组的大小和类型与src相同。
@param m 3x3或4x4浮点变换矩阵。
@sa  transform, warpPerspective, getPerspectiveTransform, findHomography
*/

void warpPerspective( InputArray src, OutputArray dst,
                                   InputArray M, Size dsize,
                                   int flags = INTER_LINEAR,
                                   int borderMode = BORDER_CONSTANT,
                                   const Scalar& borderValue = Scalar());
/* 
@brief 将透视转换应用于图像。warpPerspective函数使用指定的矩阵转换源图像:
@param src 输入图像
@param dst 输出图像,大小为dsize,类型与src相同
@param M 变换矩阵
@param dsize 输出图像的大小。
@param flags 插值方法的组合(#INTER_LINEAR或#INTER_NEAREST)和可选标志#WARP_INVERSE_MAP,它将M设置为逆变换 
@param borderMode 像素外推法 (#BORDER_CONSTANT or #BORDER_REPLICATE).
@param borderValue 用于固定边框的情况;缺省情况下,它等于0。
@sa  warpAffine, resize, remap, getRectSubPix, perspectiveTransform
 */
	cv::Mat orig_gray, search_gray,result_img;
	// 灰度化
	cv::cvtColor(orig_img, orig_gray, cv::COLOR_BGR2GRAY);
	cv::cvtColor(search_img, search_gray, cv::COLOR_BGR2GRAY);
	// 创建SIFT
	cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
	std::vector<cv::KeyPoint> origImgSiftArray, searchImgSiftArray;
	cv::Mat origImgDescriptorsArray, searchImgDescriptorsArray;
	// 获取两张图的关键点和描述子
	sift->detectAndCompute(orig_gray, cv::Mat(), origImgSiftArray, origImgDescriptorsArray);
	sift->detectAndCompute(search_gray, cv::Mat(), searchImgSiftArray, searchImgDescriptorsArray);
	std::vector<std::vector<cv::DMatch>> matches;
	std::vector<cv::DMatch> goodmatch;
	cv::FlannBasedMatcher flann = cv::FlannBasedMatcher();
	// knnMatch 返回k个最匹配的向量
	flann.knnMatch(origImgDescriptorsArray, searchImgDescriptorsArray, matches, 2);
	// 过滤,寻找较好的匹配向量
	for (size_t i = 0; i < matches.size(); i++)
	{
		if (matches[i][0].distance < 0.7 * matches[i][1].distance) {
			goodmatch.push_back(matches[i][0]);
			}
	}
	// 有足够的匹配向量,才需要寻找
	if (goodmatch.size() >= 4)
	{
		std::vector<cv::Point2f> srcPoints, searchPoints;
		// 获取匹配向量中原图和搜索图相关的坐标
		for (size_t i = 0; i < matches.size(); i++)
		{
			// 获取匹配向量中原图的坐标
			srcPoints.push_back(origImgSiftArray[matches[i][0].queryIdx].pt);
			// 获取匹配向量中搜索图的坐标
			searchPoints.push_back(searchImgSiftArray[matches[i][0].trainIdx].pt);
		}
		// 单应性矩阵
		// RANSAC 随机抽样算法
		cv::Mat homoMat = cv::findHomography(srcPoints, searchPoints, cv::RANSAC, 5.0);
		//cv::Mat homoMat = cv::getPerspectiveTransform(srcPoints, dstPoints, cv::RANSAC);
		std::vector<cv::Point2f> origBorderP = { {0,0},{0,(float)(orig_img.rows - 1)},{(float)(orig_img.cols - 1),(float)(orig_img.rows - 1)},{(float)(orig_img.cols - 1),0} };
		std::vector<cv::Point2f> origOnSearchP_f;
		std::vector<cv::Point2i> origOnSearchP_i;
		// 透视变换 cv::perspectiveTransform(原图坐标点,原图在搜索图上的坐标点,单应性矩阵) 在搜索图中找到原图的坐标点
		cv::perspectiveTransform(origBorderP, origOnSearchP_f, homoMat);
		//cv::warpPerspective(orig_img, search_img, homoMat, search_img.size());
		/*类型转换 mat to vector
		cv::Mat mat;
		std::vector<cv::Point2f> origOnSearchP_f = cv::Mat_<cv::Point2f>(mat);
		*/
		// 数据类型转换 std::vector<cv::Point2f> to std::vector<cv::Point2i>
		cv::Mat(origOnSearchP_f).convertTo(origOnSearchP_i, CV_32SC1);
		cv::polylines(search_img, origOnSearchP_i, true, cv::Scalar(0, 0, 255), 2, 16);
	}
	// 绘画匹配集
	cv::drawMatches(orig_img, origImgSiftArray, search_img, searchImgSiftArray, goodmatch, result_img);
	cv::imshow("result_img", result_img);
	cv::waitKey(0);

cv::perspectiveTransform 效果图
在这里插入图片描述
cv::warpPerspective 效果图
在这里插入图片描述
在这里插入图片描述

案例图像拼接

获取单应性矩阵

cv::Mat findHomo(cv::Mat img1, cv::Mat img2, double confineValue=0.8)

cv::Mat findHomo(cv::Mat img1, cv::Mat img2, double confineValue=0.8) {
	// 灰度化
	cv::Mat img1_gray, img2_gray;
	cv::cvtColor(img1, img1_gray, cv::COLOR_BGR2GRAY);
	cv::cvtColor(img2, img2_gray, cv::COLOR_BGR2GRAY);
	// 特征点检测
	cv::Ptr<cv::SIFT> sift = cv::SIFT::create();
	// 获取关键点和描述子
	std::vector<cv::KeyPoint> kp1, kp2;
	cv::Mat dp1, dp2;
	sift->detectAndCompute(img1_gray, cv::Mat(), kp1, dp1);
	sift->detectAndCompute(img2_gray, cv::Mat(), kp2, dp2);
	// 特征匹配
	cv::BFMatcher bfM = cv::BFMatcher();
	// 获取描述子的k个匹配向量
	std::vector< std::vector<cv::DMatch>> matchesArray;
	bfM.knnMatch(dp1, dp2, matchesArray, 2);
	// 过滤匹配向量
	std::vector<cv::DMatch> goodMatches;
	for (std::vector<cv::DMatch> m: matchesArray)
	{
		if (m[0].distance < confineValue * m[1].distance) {
			goodMatches.push_back(m[0]);
		}
	}
	// 确保有足够的匹配向量
	if (goodMatches.size() >= 8) {
		// 根据匹配向量获取相关坐标
		std::vector<cv::Point2f> cp1, cp2;
		for(cv::DMatch e: goodMatches)
		{
			cp1.push_back(kp1[e.queryIdx].pt);
			cp2.push_back(kp2[e.trainIdx].pt);
		}
		return cv::findHomography(cp1, cp2, cv::RANSAC, 5.0);
	}
	else {
		std::cout << "There are not enough matching vectors \n";
		exit(-1);
	}
}

图像拼接

cv::Mat stitchImg(cv::Mat img1, cv::Mat img2, cv::Mat homo)

/// <summary>
/// 图像拼接
/// </summary>
/// <param name="img1">搜索集</param>
/// <param name="img2">训练集</param>
/// <param name="homo1">img1 on img2单应性矩阵</param>
/// <returns></returns>
cv::Mat stitchImg(cv::Mat img1, cv::Mat img2, cv::Mat homo) {
	std::vector<cv::Point2f> img1Dims = { {0,0},{0,(float)img1.rows},{(float)img1.cols,(float)img1.rows},{(float)img1.cols,0} };
	std::vector<cv::Point2f> img2Dims = { {0,0},{0,(float)img2.rows},{(float)img2.cols,(float)img2.rows},{(float)img2.cols,0} };
	std::vector<cv::Point2f> resultImgDims;
	cv::perspectiveTransform(img2Dims, resultImgDims, homo);
	
	// 拼接数组
	//resultImgDims.insert(resultImgDims.begin(), { {0,0},{0,(float)img1.rows},{(float)img1.cols,(float)img1.rows},{(float)img1.cols,0} });
	//for (cv::Point2f point : img1Dims) resultImgDims.push_back(point);
	nc::NdArray<cv::Point2f> result = resultImgDims;
	nc::NdArray<cv::Point2f> result1 = img1Dims;
	nc::NdArray<cv::Point2f> concatenateP = nc::concatenate({ result,result1 });
	// 获取最大,最小的x和y
	nc::NdArray<float> tempArray = { concatenateP[0].x, concatenateP[0].y };
	for (cv::Point2f point : concatenateP) tempArray = nc::append(tempArray, { point.x, point.y }).reshape(-1,2);
	int maxP[] = {(int)(tempArray.max(nc::Axis::ROW)[0]+ 0.5),(int)(tempArray.max(nc::Axis::ROW)[1] + 0.5) };
	int minP[] = {(int)(tempArray.min(nc::Axis::ROW)[0]- 0.5),(int)(tempArray.min(nc::Axis::ROW)[1] - 0.5) };
	std::cout << tempArray;
	std::cout << maxP[0] << maxP[1] <<"\n";
	std::cout << minP[0] << minP[1] << "\n";

	// 平移透视之后的图片,定义平移变换矩阵
	cv::Mat transformArray = cv::Mat::eye(3,3,CV_64FC1);
	transformArray.at<double>(0, 2) = std::abs((double)minP[0]);
	transformArray.at<double>(1, 2) = std::abs((double)minP[1]);
	cv::Mat resultImg;
	//转换图像,将搜索集放于训练集适当位置
	cv::warpPerspective(img1, resultImg,  transformArray*homo, cv::Size(maxP[0] + std::abs(minP[0]), maxP[1] + std::abs(minP[1])));
	img2.copyTo(resultImg({ std::abs(minP[1]), std::abs(minP[1]) + img2.rows },
							{ std::abs(minP[0]), std::abs(minP[0]) + img2.cols }));
	return resultImg;
}

main函数

int main(){
	cv::Mat img1 = cv::imread(".\\img\\mountain1.jpg");
	cv::Mat img2 = cv::imread(".\\img\\mountain2.jpg");
	// 两张图像大小需一致
	cv::resize(img1, img1, cv::Size(640, 480));
	cv::resize(img2, img2, cv::Size(640, 480));
	// 获取img1 on img2单应性矩阵
	cv::Mat homo1 = findHomo(img1, img2);
	// 获取拼接后的图像
	cv::Mat resultImg1 = stitchImg(img1, img2, homo1);
	//获取img2 on img1单应性矩阵
	//cv::Mat homo2 = findHomo(img2, img1);
	//cv::Mat resultImg2 = stitchImg(img2, img1, homo2);

	cv::imshow("resultImg1", resultImg1);
	cv::imshow("img1", img1);
	cv::imshow("img2", img2);
	cv::waitKey(0);
	return 0;
}

在这里插入图片描述

图像分割

传统的图像分割方法

分水岭法

void distanceTransform( InputArray src, OutputArray dst,
                        OutputArray labels, int distanceType, int maskSize,
                        int labelType = DIST_LABEL_CCOMP );
void distanceTransform( InputArray src, OutputArray dst,
                        int distanceType, int maskSize, int dstType=CV_32F);
/** @brief 计算源图像中每个像素到最近的零像素的距离。
函数cv::distanceTransform计算二值图像中每个像素到最近的零像素的近似或精确距离。对于零像素的图像,距离显然为零。
当maskSize == #DIST_MASK_PRECISE和distanceType == #DIST_L2时,该函数将运行@cite Felzenszwalb04中描述的算法。该算法利用TBB库实现了并行化。
@param src 8位,单通道(二进制)源图像。
@param dst Output 具有计算距离的图像。它是一个8位或32位浮点的单通道映像,大小与src相同。
@param labels 输出二维标签数组(离散Voronoi图)。它的类型为CV_32SC1,大小与src相同。
@param distanceType 距离类型,请参见#DistanceTypes
@param maskSize 距离转换掩码的大小,参见#DistanceTransformMasks。在#DIST_L1或#DIST_C距离类型的情况下,参数被迫为3,因为3×3掩码给出的结果与5×5或任何更大的掩码相同。
@param dstType 输出图像的类型。请参见#DistanceTransformLabelTypes.它可以是CV_8U或CV_32F。类型CV_8U只能用于函数的第一个变体,并且distanceType == #DIST_L1
*/

int connectedComponents(InputArray image, OutputArray labels,
                        int connectivity, int ltype, int ccltype);
int connectedComponents(InputArray image, OutputArray labels,
                        int connectivity = 8, int ltype = CV_32S);
/** @brief 计算布尔图像的标记图像的连接组件

@param image 要标记的8位单通道图像
@param labels 目的地标记图像
@param connectivity 8或4分别为8路或4路连接
@param ltype 输出图像标签类型。目前支持CV_32S和CV_16U。
@param ccltype 连接组件的算法类型(参见#ConnectedComponentsAlgorithmsTypes).
*/
	cv::Mat pillThreshold,pillGray,pillOpen,pillBGM, pillDist, pillFG,pillUnknow,pillMask;
	// 灰度化
	cv::cvtColor(pill, pillGray, cv::COLOR_BGR2GRAY);
	// 二值化
	cv::threshold(pillGray, pillThreshold, 0, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);
	// 获取卷积核(MORPH_CROSS十字形,RECT矩形,ELLIPSE椭圆形)
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
	// 开运算()
	cv::morphologyEx(pillThreshold, pillOpen, cv::MORPH_OPEN, kernel,cv::Point(-1,-1));
	// 获取背景
	cv::dilate(pillOpen, pillBGM, kernel);
	// 获取前景
	cv::distanceTransform(pillOpen, pillDist, cv::DIST_L2, 5);
	double distMax;
	cv::minMaxLoc(pillDist, 0, &distMax);
	cv::threshold(pillDist, pillFG, 0.55 * distMax, 255, cv::THRESH_BINARY);
	// 更改type(只允许相同通道)
	pillFG.convertTo(pillFG, CV_8UC1,255);
	//std::cout << pillBGM.type() << "\n" << pillFG.type();
	 获取未知区域
	cv::subtract(pillBGM, pillFG, pillUnknow);
	创建连通域
	cv::connectedComponents(pillFG, pillMask);
	//pillMask += 1;
	for (size_t i = 0; i < pillUnknow.rows; i++)
	{
		for (size_t j = 0; j < pillUnknow.cols; j++)
		{
			if (pillUnknow.at<uchar>(i,j) == 255)
			{
				pillMask.at<std::int32_t>(i, j) = 0;
			}
		}
	}
	// 分水岭算法 轮廓标配-1
	cv::watershed(pill, pillMask);
	for (size_t i = 0; i < pillMask.rows; i++)
	{
		for (size_t j = 0; j < pillMask.cols; j++)
		{
			if (pillMask.at<std::int32_t>(i, j) == -1)
			{
				pill.at<uchar>(i, j*3) = 0;
				pill.at<uchar>(i, j*3+1) = 0;
				pill.at<uchar>(i, j*3+2) = 255;
			}
		}
	}
	cv::imshow("pill", pill);
	cv::imshow("pillOpen", pillOpen);
	cv::imshow("pillDist", pillDist);
	cv::imshow("pillBGM", pillBGM);
	cv::imshow("pillFG", pillFG);
	cv::imshow("pillUnknow", pillUnknow);	
	cv::waitKey(0);

在这里插入图片描述

GrabCut法

static class GrabCutTest {
private:
	cv::Mat imgout, bgMask, fgMask,tem1,tem2;
	nc::NdArray<UINT8> ncMask;
public:
	static int startX;
	static int startY;
	static cv::Mat img;
	static bool flag_rectangle;
	static cv::Rect rect;
	static cv::Mat mask;
	GrabCutTest() {
		imgout = imgout.zeros(img.size(), CV_8UC3);
		//mask = mask.zeros(img.size(), CV_8UC1);
		tem1.create(img.size(), CV_8UC1);
		
		ncMask = nc::zeros<UINT8>(nc::Shape(img.rows, img.cols));
	};
	~GrabCutTest() {
		NULL;
	};
	// 静态函数 属于类的方法,不属于对象,可直接调用
	static void mouseCallback(int event, int x, int y, int flags, void* userdata = 0) {
		std::cout << "onmouse\n";
		
		if (event == cv::EVENT_LBUTTONDOWN) {
			//std::cout << "down\n";
			startX = x;
			startY = y;
			flag_rectangle = true;
			
		}
		else if (event == cv::EVENT_LBUTTONUP) {
			//std::cout << "up\n";
			flag_rectangle = false;
			cv::rectangle(img, cv::Point(startX, startY), cv::Point(x, y), cv::Scalar(0, 0, 255), 2, 16);
			//mask.setTo(cv::Scalar::all(cv::GC_BGD));
			rect = {std::min(startX,x),std::min(startY,y), std::abs(x-startX),std::abs(y-startY)};
			//mask(rect).setTo(cv::Scalar(cv::GC_PR_FGD));
		}
		else if (event == cv::EVENT_MOUSEMOVE) {
			std::cout << "move\n";

			if (flag_rectangle) {
				cv::Mat img2;
				img.copyTo(img2);
				cv::rectangle(img2, cv::Point(startX, startY), cv::Point(x, y), cv::Scalar(0, 255, 255), 2, 16);
				cv::imshow("input", img2);
				cv::waitKey(100);
			}
		}
		//std::cout << event << " " << x << " " << y << " " << flags << " " << userdata << std::endl;
	};
	void run() {
		std::cout << "run\n";
		cv::namedWindow("input");
		cv::setMouseCallback("input", mouseCallback);
		while (true)
		{
			cv::imshow("input", img);
			cv::imshow("output", imgout);
			int key = cv::waitKey(100);
			if (key == int('q'))	break;
			if (key == int('g')) {
				// GC_FGD = 1       // 属于前景色的像素
				// GC_BGD =0;       // 属于背景色的像素
				// GC_PR_FGD = 3    // 可能属于前景的像素
				// GC_PR_BGD = 2    // 可能属于背景的像素
				cv::grabCut(img, mask, rect, bgMask, fgMask, 1,cv::GC_INIT_WITH_RECT);
			}
			/*// 比较两个Mat矩阵是否相等
			if (memcmp(mask.data, tem1.setTo(cv::Scalar::all(cv::GC_FGD)).data, mask.total() * mask.elemSize()) == 0
				|| memcmp(mask.data, tem1.setTo(cv::Scalar::all(cv::GC_PR_FGD)).data, mask.total() * mask.elemSize()) == 0) {
			}*/
			for (size_t i = 0; i < mask.total() * mask.elemSize(); i++)
			{
				uchar* temp = mask.data;
				if (!(*(temp + i) == 1 || *(temp + i) == 3)) {
					*(temp + i) = 0;
				}
				
			}
			cv::bitwise_and(img, img, imgout, mask);
			//img.copyTo(imgout, mask);

		}
		
	};
};
int GrabCutTest::startX = 0;
int GrabCutTest::startY = 0;
bool GrabCutTest::flag_rectangle = false;
cv::Mat GrabCutTest::img = cv::imread("./img/3.jpg");
cv::Rect GrabCutTest::rect = cv::Rect();
cv::Mat GrabCutTest::mask = mask.zeros(img.size(), CV_8UC1);

MeanShift法

void pyrMeanShiftFiltering( InputArray src, OutputArray dst,
                                         double sp, double sr, int maxLevel = 1,
                                         TermCriteria termcrit=TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS,5,1) );
/** 
@brief 执行图像的meanshift分割的初始步骤.
该函数实现了meanshift分割的滤波阶段,即函数的输出是经过滤波后的经过颜色渐变和细粒纹理压平的“分色”图像。
在输入图像(或缩小的输入图像,见下图)的每一个像素(X,Y)处,函数执行meanshift迭代,即考虑联合空间-颜色多维空间中的像素(X,Y)邻域:

\f[(x,y): X- \texttt{sp} \le x  \le X+ \texttt{sp} , Y- \texttt{sp} \le y  \le Y+ \texttt{sp} , ||(R,G,B)-(r,g,b)||   \le \texttt{sr}\f]

其中(R,G,B)(R,G,B)分别是颜色分量在(X,Y)(X,Y)处的向量(不过,算法不依赖于所使用的颜色空间,所以可以使用任何3分量的颜色空间)。在邻域上找到平均空间值(X',Y')和平均颜色向量(R',G',B'),并在下一次迭代中充当邻域中心:

\f[(X,Y)~(X',Y'), (R,G,B)~(R',G',B').\f]

迭代结束后,初始像素(即迭代开始的像素)的颜色组件被设置为最终值(最后一次迭代的平均颜色):

\f[I(X,Y) <- (R*,G*,B*)\f]

当maxLevel>0时,构建maxLevel+1层的高斯金字塔,并先在最小的层上运行上述过程。在此之后,结果被传播到较大的层,迭代只在那些层颜色与金字塔的低分辨率层的差异超过sr的像素上再次运行。这使得颜色区域的边界更加清晰。注意,结果实际上与在原始图像上运行meanshift过程得到的结果不同(即当maxLevel==0)。

@param src 8位3通道图像。
@param dst 与源图像具有相同格式和相同大小的目标图像。
@param sp 空间窗口半径。
@param sr 颜色窗口半径。
@param maxLevel 用于分割的金字塔的最大水平。
@param termcrit 终止标准:何时停止meanshift迭代
 */
void Canny( InputArray image, OutputArray edges,
                         double threshold1, double threshold2,
                         int apertureSize = 3, bool L2gradient = false 
/*
@param image 8位输入图象.
@param edges 输出边图;单通道8位图像,与图像大小相同。
@param threshold1 迟滞过程的第一个阈值。
@param threshold2 迟滞过程的第二个阈值。
@param apertureSize Sobel算子的孔径大小。
@param L2gradient 一个标志,表示是否有更精确的L_2范数
 */
);
cv::Mat meanshiftImg,cannyImg;
	// pyrMeanShiftFiltering(输入图,输出图,空间窗口半径,颜色窗口半径):空间窗口半径和颜色窗口半径都需要调整
	// 空间窗口半径越大,轮廓越清晰,不过计算量大,耗时久;颜色窗口半径越大,物体的原有越模糊
	cv::pyrMeanShiftFiltering(img, meanshiftImg, 100, 30);
	cv::Canny(meanshiftImg, cannyImg, 150, 300);
	std::vector<std::vector<cv::Point>>  ContoursPointArray;
	cv::findContours(cannyImg, ContoursPointArray, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
	cv::drawContours(img, ContoursPointArray, -1, cv::Scalar(0, 0, 255), 2, 16);

	cv::imshow("src", img);
	cv::imshow("meanshiftImg", meanshiftImg);

在这里插入图片描述

背景扣除法

cv::VideoCapture video = cv::VideoCapture("./video.mp4");
	cv::Ptr<cv::BackgroundSubtractorMOG2> mog = cv::createBackgroundSubtractorMOG2();
	/*createBackgroundSubtractorKNN(int history=500, double dist2Threshold=400.0,
                                   bool detectShadows=true);
	history是用于构建背景统计模型的帧数。该值越小,模型将越快考虑背景的变化,从而将其视为背景。反之亦然。
	dist2Threshold 是一个阈值,用于定义像素是否与背景不同。该值越小,运动检测越灵敏。反之亦然。
	detectShadows : 如果设置为 true,阴影将在生成的蒙版上以灰色显示*/
	cv::Ptr<cv::BackgroundSubtractorKNN> knn = cv::createBackgroundSubtractorKNN();
	
	while (true)
	{
		cv::Mat frame,frameMask;
		video.read(frame);
		knn->apply(frame, frameMask);
		cv::imshow("video", frameMask);
		if (cv::waitKey(10) == (int)'q')
			break;

	}
	video.release();
	cv::destroyAllWindows();

人脸识别

	// haar级联分类
	cv::CascadeClassifier HaarFaceCascade = cv::CascadeClassifier("./haarcascades/haarcascade_frontalface_alt.xml");
	// lbp级联分类 
	cv::CascadeClassifier lbpFaceCascade = cv::CascadeClassifier("D:/vsOpencv/opencv/sources/data/lbpcascades/lbpcascade_frontalface.xml");
	cv::Mat src = cv::imread("./img/face.jpeg");
	cv::Mat src2 = cv::imread("./img/face.jpeg");
	cv::Mat srcGray;
	cv::cvtColor(src, srcGray, cv::COLOR_RGB2GRAY);
	cv::equalizeHist(srcGray, srcGray);//直方图均值化,提升对比度,提升图像特征提取的准确率
	std::vector<cv::Rect> faces,faces2;
	// haar
	HaarFaceCascade.detectMultiScale(srcGray, faces);
	// lbp
	lbpFaceCascade.detectMultiScale(srcGray, faces2);
	for (size_t i = 0; i < faces.size(); i++)
	{
		cv::rectangle(src, faces[i], cv::Scalar(0, 0, 255), 1, 16);
		
	}
	for (size_t i = 0; i < faces2.size(); i++)
	{
		cv::rectangle(src2, faces2[i], cv::Scalar(0, 0, 255), 1, 16);

	}
	cv::putText(src, "face Count: " + std::to_string(faces.size()), cv::Point(30, 30), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 0), 2, 16);
	cv::putText(src2, "face Count: " + std::to_string(faces2.size()), cv::Point(30, 30), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 0), 2, 16);
	cv::imshow("haar", src);
	cv::imshow("lbp", src2);
	cv::waitKey(0);

在这里插入图片描述

Mat 矩阵

数据转换

Vector 和 Mat 互换

// Mat to Vector
std::vector<uchar> arr_u = (std::vector<uchar>)img.reshape(1,1);		// reshape(新的通道数,新的row数)

//vector to mat
cv::Mat array2Img(nc::NdArray<uchar> array, int n) {
	size_t h = array.numRows();
	size_t w = array.numCols();
	cv::Mat img(h, (size_t)(w / n), CV_8UC(n));     //保存为RGB
	for (size_t i = 0; i < h; i++)
	{
		uchar* tmp = img.ptr<uchar>(i);
		for (size_t j = 0; j < w; j++)
		{
			tmp[j] = array(i, j);
		}
	}
	return img;
}

数据类型转换

// Mat to Vector
std::vector<uchar> arr_u = (std::vector<uchar>)img.reshape(1,1);		// reshape(新的通道数,新的row数)
// vector数据类型转换 1
cv::Mat(arr_u).convertTo(arr_i, CV_32SC1);
// vector数据类型转换 2
// std::vector<int> arr_i = std::vector<int>(arr_u.begin(), arr_u.end());
// vector数据类型转换 3
//for (auto ele : arr_u)	arr_i.push_back(static_cast<int>(ele));

nc::NdArray<uchar> nArr_uc = arr_u;
// NdArray数据类型转换
nc::NdArray<nc::int16> nArr_i16 = nArr_uc.astype<nc::int16>();

坐标点类型转换

// 数据类型转换 std::vector<cv::Point2f> to std::vector<cv::Point2i>
std::vector<cv::Point2f> origOnSearchP_f;
std::vector<cv::Point2i> origOnSearchP_i;
cv::Mat(origOnSearchP_f).convertTo(origOnSearchP_i, CV_32SC1);

// 类型转换 mat to vector
cv::Mat mat;
std::vector<cv::Point2f> origOnSearchP_f = cv::Mat_<cv::Point2f>(mat);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

召回和排序模型中的用户行为序列的建模

1. 概述 用户在使用一个APP或者浏览网页的过程中&#xff0c;都是由一些行为构成的&#xff0c;以资讯类为例&#xff0c;通常对一个帖子感兴趣&#xff0c;对于感兴趣的帖子&#xff0c;通常会点击进入查看&#xff0c;或者点击收藏或者对其进行评论&#xff0c;这一系列行为…

电脑录屏怎么录全屏?win10电脑如何录屏

随着互联网的发展&#xff0c;线上办公以及网课越来越频繁&#xff1b;线上办公或者网课都是使用电脑来完成工作和学习的&#xff1b;那Win10电脑如何录屏以及电脑录屏怎么录屏全屏呢&#xff1f;接下来给大家分享三个电脑录制全屏的教程方法给大家&#xff1b; win10录制全屏方…

【MySQL】绿色版下载配置教程(Windows)

文章目录下载MySQL压缩包1 创建my.ini文件2 配置mysql环境变量3 在终端执行命令下载MySQL压缩包 https://dev.mysql.com/downloads/mysql/ 1 创建my.ini文件 模板内容如下 [mysqld] basedir D:\\Program Files\\mysql # 设置mysql的安装目录 datadir D:\\Program Files\\m…

红黑树简介

一、红黑树 1、概念 红黑树&#xff08;Red Black Tree&#xff09; 是一种自平衡二叉搜索树。它在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是 Red或 Black。 通过任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其…

为什么生产MES系统对工厂管理如此重要?

随着生产业务、计算机技术的发展&#xff0c;MES管理系统的定义也是在不断的变化。但是&#xff0c;计划调度、质量管理、生产执行以及数据&#xff08;设备&#xff0c;产品信息&#xff09;采集&#xff0c;一直都是MES的核心功能。 工业物联网&#xff0c;对于制造业来说并…

数据结构--顺序表、链表、栈、队列、树、文件(visual studio可运行)

顺序表的顺序存储&#xff08;增删查&#xff09; #include <stdio.h> #include <stdlib.h> #define MaxSize 50 typedef int ElemType;//保证顺序表可以存储任何类型 //静态分配 typedef struct{ ElemType data[MaxSize];//定义的数组&#xff0c;用来存元素…

MergeTree写入查询流程分析

基础概念回顾 前面几节我们分别详细分析了分区&#xff0c;索引&#xff0c;数据存储相关原理&#xff0c;这些组件配合在一起给Clickhouse数据库带来非常高效的查询性能。前面的文章也单独介绍了这几个组件。接下来&#xff0c;就分别从写入过程、查询过程&#xff0c;以及数…

数字孪生水电站,三维组态助力发电流程优化

从大禹治水到三峡大坝的建造&#xff0c;人类为控制和调配自然界的地表水和地下水&#xff0c;修建了许多的水利工程。对水资源进行了广泛的开发利用&#xff0c;诸如农业灌溉、工业和生活用水、水力发电、航运、港口运输、淡水养殖、旅游等。 将图扑软件与 GIS、粒子仿真、虚拟…

在re:Invent 2022大会打球、喝酒?没错!

编辑&#xff5c;阿冒虽然距离去拉斯维加斯参加亚马逊云科技re:Invent 2022大会&#xff0c;差不多已经过去了一个月&#xff0c;不过时不时仍有熟稔的朋友来问我&#xff0c;你在展区球场上如何如何&#xff0c;巴拉巴拉……为啥他们对我的行动如此了解&#xff1f;其实&#…

effective C++读书笔记

目录 用const,enum,inline去替换#define 尽可能去使用const 确保对象使用前已被初始化 这是effective C中的第一大章节&#xff1a;让自己习惯c 用const,enum,inline去替换#define 当用使用这样的代码&#xff1a; #define ASPECT RATIO 1.653 记号名称ASPECT RATIO可能未…

图像风格迁移---基于多适应网络的任意风格传输

ABSTRACT 任意风格转换是一个具有研究价值和应用前景的重要课题。给定一个内容图像和引用的风格绘画&#xff0c;一个所需的风格转换将使用风格绘画的色彩色调和生动的笔画模式渲染内容图像&#xff0c;同时保持详细的内容结构信息。风格迁移方法首先学习内容和内容和风格引用…

推荐系统的矩阵分解

0 序言 推荐系统中基于内容的协调过滤算法通过用户之间的相似性或者物品之间的相似性&#xff0c;通过相似性来为用户做决策和推荐&#xff1b;基于内容的协调过滤算法在实际生产环境中&#xff0c;User或Item的数据量非常大&#xff08;百万级别&#xff09;&#xff0c;存储…

操作系统期末考试必会题库3——处理机调度

1、假设一个系统中有5个进程&#xff0c;它们处于就绪状态的时刻和估计运行时间如下表所示&#xff0c;忽略I/O以及其它开销时间&#xff0c;若分别按先来先服务&#xff0c;最短进程优先&#xff0c;最短剩余时间优先、响应比优先、时间片轮转&#xff08;时间片&#xff1d;1…

世界杯 | 其实世界杯结束之前,卡塔尔就开拆体育场了...

大家好&#xff0c;这里又是建模助手。 世界杯已经圆满落幕&#xff0c;梅老板满载而归&#xff0c;但阿根廷与法国的这场世界杯决赛注定载入史册&#xff0c;成为永恒经典&#xff0c;或许将会是世界杯历史上最精彩的决赛之一。 &#xff08;梅老板捧起大力神杯那一刻&#x…

人工鱼群算法参数寻优及可视化(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 人工鱼群优化算法是一种基于模拟鱼群行为的优化算法&#xff0c;是由李晓磊等在2002年提出的一种新型的寻优算法。人工鱼群主要…

黑马Hive+Spark离线数仓工业项目--数仓事实层DWB层构建(1)

整体目标&#xff1a;构建数仓中的DWB&#xff1a;主题事务事实表 核心的主题事实的构建&#xff1a;SQL实现 主题的指标 原始事务事实数据【DWD】&#xff1a;订单数据 主题事务事实数据【DWB】&#xff1a;订单主题 - 主题周期快照事实表&#xff1a;数据应用层【ST&#…

一场4800亿“锂电”战事,瑞浦兰钧射出三支“价值之箭”

顺势而为&#xff0c;是一家企业成功突围的最佳利器。 如今&#xff0c;几乎所有企业都被“碳达峰碳中和”的国家级战略所影响。在此大势的推动之下&#xff0c;锂离子电池、清洁能源等产业也随之进入高速发展期。工信部数据显示&#xff0c;上半年全国锂离子电池产量超过280G…

HNU编译原理实验二cminus_compiler-2022-fall

前言&#xff1a;个人感觉比第一次的难&#xff0c;借鉴了前辈的报告才勉强看懂在干嘛 lab2实验报告实验要求 本次实验需要先将自己的 lab1 的词法部分复制到 /src/parser 目录的 lexical_analyzer.l并合理修改相应部分&#xff0c;然后根据 cminus-f 的语法补全 syntax_analy…

3.3 直接耦合放大电路

工业控制中的很多物理量均为模拟量&#xff0c;如温度、流量、压力、液面、长度等&#xff0c;它们通过各种不同传感器转化成电量后也均为缓慢变化的非周期性信号&#xff0c;而且比较微弱&#xff0c;因而这类信号一般均需通过直接耦合放大电路后才能驱动负载。 一、直接耦合…

二叉树,红黑树,B树、B+树的区别

树的概念 树的演变 二叉搜索树 二叉搜索树可以提高查询效率&#xff0c;左小右大&#xff0c;但是他不好掌握根节点的数字是哪个&#xff0c;容易一边倒&#xff0c;导致层数变多&#xff0c;降低效率 平衡二叉搜索树 平衡二叉搜索树在二叉搜索树的基础上,通过控制任意一个节…