C/C++开发,opencv轮廓提取实现

news2024/9/20 17:58:21

一、cv::findContours轮廓提取函数

       1.1  cv::findContours函数简介

   cv::findContours 函数是用于从二值图像(灰度图)中检索轮廓。这个函数在OpenCV的不同版本中参数可能有所不同,但基本概念保持一致。特别是在OpenCV 3.x和4.x版本中,cv::findContours 的参数主要包括输入图像、输出轮廓、轮廓检索模式(mode)和轮廓近似方法(method)。

void cv::findContours(InputOutputArray image,  
                      OutputArrayOfArrays contours,  
                      OutputArray hierarchy,  
                      int mode,  
                      int method,  
                      Point offset = Point());

//image:输入图像,必须是8位单通道图像,且通常是通过阈值处理(如cv::threshold或cv::adaptiveThreshold)或边缘检测(如cv::Canny)得到的二值图像。
//hierarchy:轮廓的层次结构信息,可选参数。
//mode:轮廓检索模式,可以是cv::RETR_EXTERNAL、cv::RETR_LIST、cv::RETR_CCOMP或cv::RETR_TREE。
//method:轮廓逼近方法,可以是cv::CHAIN_APPROX_NONE、cv::CHAIN_APPROX_SIMPLE、cv::CHAIN_APPROX_TC89_L1或cv::CHAIN_APPROX_TC89_KCOS。
//offset:可选的偏移量,所有找到的轮廓点都会移动这个偏移量。默认值为Point()。

轮廓检索模式(Mode)

轮廓检索模式决定了如何检索轮廓。OpenCV提供了以下几种模式:

  • cv::RETR_EXTERNAL:只检索最外层的轮廓,忽略轮廓内部的孔洞。
  • cv::RETR_LIST:检索所有的轮廓,但不创建任何父子关系。
  • cv::RETR_CCOMP:检索所有的轮廓,并将它们组织为两层:外层轮廓和它们的内层轮廓(孔洞)。
  • cv::RETR_TREE:检索所有的轮廓,并重新建立完整的轮廓层次结构。

轮廓近似方法(Method)

轮廓近似方法定义了轮廓如何被近似表示。主要有以下几种方法:

  • cv::CHAIN_APPROX_NONE:存储轮廓的每一个点,即轮廓的精确表示。
  • cv::CHAIN_APPROX_SIMPLE(或cv::CHAIN_APPROX_TC89_L1, cv::CHAIN_APPROX_TC89_KCOS):压缩水平、垂直和对角线段,只留下它们的端点。对于大多数应用来说,这种近似是足够的,并且可以显著减少轮廓点的数量。
  • cv::CHAIN_APPROX_TC89_LKB:使用Teh-Chin链近似算法的一个变种。
1.2 需要将图像转为二值图

        图像轮廓检测,通常需要将图像转换为灰度图(二值图像),并应用阈值操作。

// 读取图像  
    cv::Mat src = cv::imread(argv[1]);  
	// cv::Mat src = cv::imread("path_to_image.jpg");  
    if (src.empty()) {  
        std::cerr << "Could not read the image" << std::endl;  
        return 1;  
    }else{
		// 显示结果  
    	cv::imshow("orgimg", src);  
		cv::waitKey(0);  
	}  

// 应用阈值 ,提取需要像素,主要是针对轮廓部分的值范围,例如215~235 
    cv::Mat binary;  
	cv::threshold(src, binary, 215, 235, cv::THRESH_BINARY);   
	if (binary.empty())
	{
		std::cerr << "Could show the image" << std::endl;  
        return 1; 
	}else{
		// 显示结果  
    	cv::imshow("binaryimg", binary);  
		cv::waitKey(0);  
	}
1.3 从二值图像中查找轮廓实现代码

        假设存在一个cv::Mat binary二值图像(PS:确保你的输入图像是二值的,即图像中的像素值只有0和255),采用cv::findContours函数查询轮廓代码实现如下,不同参数组合成效不同:

// 查找轮廓  
    std::vector<std::vector<cv::Point>> contours;  
	cv::Mat hierarchy; 
    // 查找轮廓
	// cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_NONE);  
    cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);  
	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);  
	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  
	// cv::findContours(binary, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); 
	// cv::findContours(binary, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE); 

	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_L1); 
	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS); 
	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS
	// 	,cv::Point(binary.cols/2,binary.rows/2)); 
 
1.4 绘制轮廓

   cv::drawContours 函数用于在图像上绘制轮廓。这个函数非常有用,特别是在进行图像处理和计算机视觉任务时,如边缘检测、形状分析等。cv::drawContours 可以将找到的轮廓绘制在原始图像、空白图像或任何其他图像上。

//函数原型:
void cv::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());
/*
参数说明:
    image: 目标图像,轮廓将被绘制在这个图像上。它应该是单通道(如灰度图)或三通道(如彩色图)的图像。
    contours: 一个包含轮廓点的点的向量向量(std::vector<std::vector<cv::Point>>)。这通常是通过findContours函数获得的。
    contourIdx: 指定要绘制的轮廓的索引。如果它是负数,则绘制所有轮廓。
    color: 轮廓的颜色。对于灰度图像,它是一个表示灰度级的标量。对于彩色图像,它是一个表示BGR颜色的三元素向量。
    thickness: 轮廓线的厚度。如果它是负数(如FILLED),则轮廓内部将被填充。
    lineType: 线条的类型。它可以是LINE_8、LINE_AA等。LINE_8表示8连通的线条,而LINE_AA表示抗锯齿线条,这会使线条看起来更平滑。
    hierarchy: 可选的,包含轮廓层次结构的向量。这是findContours函数的输出之一,但在绘制轮廓时通常不需要。
    maxLevel: 绘制轮廓的最大层级。只有当hierarchy非空时才有效。
    offset: 轮廓点从原图像到目标图像的偏移量。
*/

        将已经提取的轮廓点绘制图像代码如下:

// 轮廓点存放  
    std::vector<std::vector<cv::Point>> contours;  
// 查找轮廓 
......  
// 绘制轮廓  
    cv::Mat contoursImg = cv::Mat::zeros(binary.size(), CV_8UC1); 
    for (size_t i = 0; i < contours.size(); i++) {  
        cv::Scalar color = cv::Scalar(255); //这里为白色 
        cv::drawContours(contoursImg, contours, static_cast<int>(i), color, 2, cv::LINE_8, hierarchy, 0);  
    } 
	
	if (contoursImg.empty())
	{
		std::cerr << "Could show the image" << std::endl;  
        return 1; 
	}
	
    // 显示结果  
    cv::imshow("Contours", contoursImg);  
    cv::waitKey(0); 

二、在指定图像区域查找轮廓

2.1指定一个cv::Rect区域

在OpenCV中,如果你想在图像的指定区域内查找轮廓,通常使用ROI(Region of Interest,感兴趣区域)的概念。首先需要定义这个ROI区域,然后,你可以在这个ROI上应用轮廓查找函数。

// 读取图像  
    cv::Mat src = cv::imread(argv[1]); 
//指定检测区域
	int x = 180; // ROI左上角x坐标 
	int y = 135; // ROI左上角y坐标  
	int width = 210; // ROI宽度  
	int height = 320; // ROI高度  
	cv::Rect roi(x, y, width, height);  
	cv::Mat roiImg = src(roi);

        上述代码中先读取一个图像,然后在该图像上规划处一个矩形区域作为轮廓查找的ROI。

2.2 指定多边形区域提取图像

        在OpenCV中,从一幅图像中根据指定的多边形区域提取图像,使用cv::fillPoly函数来绘制这个多边形区域,并填充它,或者使用cv::fillConvexPoly(如果多边形是凸的,这会更高效),然后结合图像掩码(mask)来提取该区域内的图像。

        通常做法是:

  • 定义多边形:定义多边形顶点。这些顶点定义了你要从图像中提取的区域。

  • 创建掩码:基于多边形顶点,创建一个与原图同样大小的掩码图像,并填充多边形区域为白色(或任何其他与背景不同的颜色),其余部分为黑色。

  • 应用掩码:最后,使用掩码来提取多边形区域内的图像。

	// 定义多边形顶点(注意:OpenCV中的坐标系统是(x,y))  
	//指定范围内提取轮廓
    std::vector<cv::Point> pts;  
    pts.push_back(cv::Point(207, 209));  
    pts.push_back(cv::Point(279, 138));  
	pts.push_back(cv::Point(317, 139)); 
    pts.push_back(cv::Point(357, 196));  
	pts.push_back(cv::Point(325, 228));  
	pts.push_back(cv::Point(328, 299)); 
	pts.push_back(cv::Point(303, 443)); 
	pts.push_back(cv::Point(234, 444)); 
	pts.push_back(cv::Point(238, 353)); 
	pts.push_back(cv::Point(242, 297)); 
	pts.push_back(cv::Point(247, 236)); 
	pts.push_back(cv::Point(212, 232)); 
    pts.push_back(cv::Point(207, 209)); // 闭合多边形  

    // 创建掩码,大小与原图相同,初始化为全黑  
    cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1); 
    // 填充多边形区域为白色  
    const cv::Point* ppt[1] = { &pts[0] };  
    int npt[] = { static_cast<int>(pts.size()) };  
    fillPoly(mask, ppt, npt, 1, cv::Scalar(255), 8, 0); 
    // 应用掩码提取多边形区域内的图像  
    cv::Mat result;
    bitwise_and(gray, gray, result, mask);  

三、轮廓提取的预处理方法

3.1 降噪处理

        在C++中使用OpenCV进行轮廓提取之前,进行降噪预处理是一个常见的步骤,以提高轮廓检测的准确性和鲁棒性。降噪处理可以减少图像中的噪声,使轮廓更加清晰和易于检测。

1. 高斯模糊(Gaussian Blur)

        高斯模糊是一种常用的图像平滑技术,它通过应用高斯函数来减少图像中的噪声和细节。高斯模糊对于去除高斯噪声特别有效。


// 读取图像  
    cv::Mat src = cv::imread(argv[1]);  
//高斯模糊
    cv::Mat result;
    cv::GaussianBlur(src, result, cv::Size(5, 5), 1.4); 

2. 中值模糊(Median Blur)

        中值模糊是一种非线性滤波技术,它用像素邻域内的中值替换该像素的值。中值模糊对于去除椒盐噪声特别有效。

// 读取图像  
    cv::Mat src = cv::imread(argv[1]);  
//中值模糊
    cv::Mat result;
    cv::medianBlur(src, result, 5); 

3. 双边滤波(Bilateral Filter)

        双边滤波在平滑图像的同时,还能保持边缘的锐利。它同时考虑像素间的空间距离和像素值的差异。

// 读取图像  
    cv::Mat src = cv::imread(argv[1]);  
//中值模糊
    cv::Mat result;
    cv::bilateralFilter(src, result, 5, 20, 20);

4. 形态学操作

        形态学操作如开运算(先腐蚀后膨胀)和闭运算(先膨胀后腐蚀)也可以用于降噪和去除小的噪声点。这些操作对于二值化图像尤其有用。

	// 读取图像  
    cv::Mat src = cv::imread(argv[1]);  
    // 应用阈值  
    cv::Mat binary;  
	cv::threshold(src, binary, 215, 235, cv::THRESH_BINARY); 
    // 使用形态学开运算去除小的白噪声  
	cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));  
	cv::morphologyEx(binary, binary/*result*/, cv::MORPH_OPEN, kernel); 
3.2 图像缩放

        在OpenCV中,轮廓提取时,不少图像是高清图像,由于显示屏幕限制,处理结果会存在显示不全的尴尬问题。cv::resize函数是一个非常常用的函数,用于调整图像的大小。这个函数可以根据指定的尺寸或缩放比例来改变图像的大小。它定义在opencv2/imgproc.hpp头文件中,并属于imgproc模块。

//函数原型:
void cv::resize(InputArray src, OutputArray dst,  
                Size dsize, double fx = 0, double fy = 0,  
                int interpolation = INTER_LINEAR );
/*
参数说明:
    src: 输入图像。
    dst: 输出图像,其大小和类型由dsize、src.type()和fx、fy决定。
    dsize: 输出图像的大小。如果它是零,那么大小是通过src.size()、fx和fy计算得到的(两者都不能为零)。
    fx: 沿水平轴的比例因子(宽度)。如果它是零,那么它会自动计算为 (double)dsize.width/src.cols。
    fy: 沿垂直轴的比例因子(高度)。如果它是零,那么它会自动计算为 (double)dsize.height/src.rows。
    interpolation: 插值方法。插值算法决定了在调整图像大小时使用的算法。常用的插值方法包括:
        INTER_NEAREST - 最近邻插值
        INTER_LINEAR - 双线性插值(默认)
        INTER_AREA - 使用像素区域关系进行重采样(图像缩小时使用)
        INTER_CUBIC - 双三次插值(图像放大时使用)
        INTER_LANCZOS4 - Lanczos插值
*/

函数应用示例:

if(contoursImg.cols>1000 || contoursImg.rows>1000){
	// 假设我们想要将图片宽高都缩小为原来的1/2
		cv::Mat dst;  
		cv::Size size(contoursImg.cols / 2, contoursImg.rows / 2); // 目标尺寸  
		// 使用resize函数进行缩放  
		// 第一个参数是源图像,第二个参数是目标图像,第三个参数是目标尺寸  
		// 第四个参数是插值方法,这里使用INTER_LINEAR(双线性插值)  
		cv::resize(contoursImg, dst, size, 0, 0, cv::INTER_LINEAR);  
		if (contoursImg.empty())
		{
			std::cerr << "Could show the image" << std::endl;  
			return 1; 
		}
		// 显示原图和处理后的图片  
		cv::imshow("dst_Contours", dst);  
		// 等待任意键盘按键  
		cv::waitKey(0);  
   }

四、完整案例

        将前面涉及方法代码集成测试案例。

4.1 下载提取轮廓的图片

        如图,提取图中人物轮廓,需要跟进该图片设置ROI或多边形轮廓,详细见程序代码。

4.2 程序代码

        main.cpp实现

#include <opencv2/opencv.hpp>  
#include <vector>  
  
int main( int argc,char** argv )
{
	if(argc< 2){ 
		std::cout <<"specify input image" << std::endl; 
		return -1;
	}
    // 读取图像  
    cv::Mat src = cv::imread(argv[1]);  
	// cv::Mat src = cv::imread("path_to_image.jpg");  
    if (src.empty()) {  
        std::cerr << "Could not read the image" << std::endl;  
        return 1;  
    }else{
		// 显示结果  
    	cv::imshow("orgimg", src);  
		cv::waitKey(0);  
	}  
	//指定检测区域
	int x = 180; // ROI左上角x坐标 
	int y = 135; // ROI左上角y坐标  
	int width = 210; // ROI宽度  
	int height = 320; // ROI高度  
	cv::Rect roi(x, y, width, height);  
	cv::Mat roiImg = src(roi);
  
    // 转换为灰度图  
    cv::Mat gray;  
    // cv::cvtColor(roiImg, gray, cv::COLOR_BGR2GRAY);  
	cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);  
	if (gray.empty())
	{
		std::cerr << "Could show the image" << std::endl;  
        return 1; 
	}else{
		// 显示结果  
    	cv::imshow("grayimg", gray);  
		cv::waitKey(0);  
	}
	// 定义多边形顶点(注意:OpenCV中的坐标系统是(x,y))  
	//指定范围内提取轮廓
    std::vector<cv::Point> pts;  
    pts.push_back(cv::Point(207, 209));  
    pts.push_back(cv::Point(279, 138));  
	pts.push_back(cv::Point(317, 139)); 
    pts.push_back(cv::Point(357, 196));  
	pts.push_back(cv::Point(325, 228));  
	pts.push_back(cv::Point(328, 299)); 
	pts.push_back(cv::Point(303, 443)); 
	pts.push_back(cv::Point(234, 444)); 
	pts.push_back(cv::Point(238, 353)); 
	pts.push_back(cv::Point(242, 297)); 
	pts.push_back(cv::Point(247, 236)); 
	pts.push_back(cv::Point(212, 232)); 
    pts.push_back(cv::Point(207, 209)); // 闭合多边形  

    // 创建掩码,大小与原图相同,初始化为全黑  
    cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1); 
    // 填充多边形区域为白色  
    const cv::Point* ppt[1] = { &pts[0] };  
    int npt[] = { static_cast<int>(pts.size()) };  
    fillPoly(mask, ppt, npt, 1, cv::Scalar(255), 8, 0); 
    // 应用掩码提取多边形区域内的图像  
    cv::Mat result;
    bitwise_and(gray, gray, result, mask);  

	cv::Mat Filter_dst; 
	// cv::GaussianBlur(result, result, cv::Size(5, 5), 1.4); 
	// cv::medianBlur(result, result, 5); 
	cv::bilateralFilter(result, Filter_dst, 5, 20, 20);
    // 应用阈值  
    cv::Mat binary;  
	cv::threshold(Filter_dst, binary, 215, 235, cv::THRESH_BINARY);  
    // cv::threshold(result, binary, 215, 235, cv::THRESH_BINARY);  
	if (binary.empty())
	{
		std::cerr << "Could show the image" << std::endl;  
        return 1; 
	}else{
		// 显示结果  
    	cv::imshow("binaryimg", binary);  
		cv::waitKey(0);  
	}
	// // 使用形态学开运算去除小的白噪声  
	// cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));  
	// cv::morphologyEx(binary, binary, cv::MORPH_OPEN, kernel); 

    // 查找轮廓  
    std::vector<std::vector<cv::Point>> contours;  
	cv::Mat hierarchy; 
    // 查找轮廓
	// cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_NONE);  
    cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);  
	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);  
	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  
	// cv::findContours(binary, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); 
	// cv::findContours(binary, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE); 

	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_L1); 
	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS); 
	// cv::findContours(binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS
	// 	,cv::Point(binary.cols/2,binary.rows/2)); 

    // 绘制轮廓  
    cv::Mat contoursImg = cv::Mat::zeros(binary.size(), CV_8UC1); 
    for (size_t i = 0; i < contours.size(); i++) {  
        cv::Scalar color = cv::Scalar(255); //这里为白色 
        cv::drawContours(contoursImg, contours, static_cast<int>(i), color, 2, cv::LINE_8, hierarchy, 0);  
    } 
	
	if (contoursImg.empty())
	{
		std::cerr << "Could show the image" << std::endl;  
        return 1; 
	}
	
    // 显示结果  
    cv::imshow("Contours", contoursImg);  
    cv::waitKey(0);  
  
   if(contoursImg.cols>1000 || contoursImg.rows>1000){
	// 假设我们想要将图片宽高都缩小为原来的1/2
		cv::Mat dst;  
		cv::Size size(contoursImg.cols / 2, contoursImg.rows / 2); // 目标尺寸  
		// 使用resize函数进行缩放  
		// 第一个参数是源图像,第二个参数是目标图像,第三个参数是目标尺寸  
		// 第四个参数是插值方法,这里使用INTER_LINEAR(双线性插值)  
		cv::resize(contoursImg, dst, size, 0, 0, cv::INTER_LINEAR);  
		if (contoursImg.empty())
		{
			std::cerr << "Could show the image" << std::endl;  
			return 1; 
		}
		// 显示原图和处理后的图片  
		cv::imshow("dst_Contours", dst);  
		// 等待任意键盘按键  
		cv::waitKey(0);  
   }
    return 0;  
}
4.3 程序编译及运行

        本文是采用win系统下,opencv采用MinGW编译的静态库(C/C++开发,win下OpenCV+MinGW编译环境搭建_opencv mingw-CSDN博客),建立makefile:

#/bin/sh
#win32
CX= g++ -DWIN32 
#linux
#CX= g++ -Dlinux 

BIN 		:= ./
TARGET      := contours_img01.exe
FLAGS		:= -std=c++11 -static
SRCDIR 		:= ./
#INCLUDES
INCLUDEDIR 	:= -I"../../opencv_MinGW/include" -I"./"
#-I"$(SRCDIR)"
staticDir   := ../../opencv_MinGW/x64/mingw/staticlib/
#LIBDIR		:= $(staticDir)/libopencv_world460.a\
#			   $(staticDir)/libade.a \
#			   $(staticDir)/libIlmImf.a \
#			   $(staticDir)/libquirc.a \
#			   $(staticDir)/libzlib.a \
#			   $(wildcard $(staticDir)/liblib*.a) \
#			   -lgdi32 -lComDlg32 -lOleAut32 -lOle32 -luuid 
#opencv_world放弃前,然后是opencv依赖的第三方库,后面的库是MinGW编译工具的库

LIBDIR 	    := -L $(staticDir) -lopencv_world460 -lade -lIlmImf -lquirc -lzlib \
				-llibjpeg-turbo -llibopenjp2 -llibpng -llibprotobuf -llibtiff -llibwebp \
				-lgdi32 -lComDlg32 -lOleAut32 -lOle32 -luuid 
source		:= $(wildcard $(SRCDIR)/*.cpp) 

$(TARGET) :
	$(CX) $(FLAGS) $(INCLUDEDIR) $(source)  -o $(BIN)/$(TARGET) $(LIBDIR)

clean:
	rm  $(BIN)/$(TARGET)

编译如下:

程序运行输出如下:

各位读者可以自由调节各个参数来观察其效果,或更换你需要的图片来验证。

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

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

相关文章

贪吃蛇(使用QT)

贪吃蛇小游戏 一.项目介绍**[贪吃蛇项目地址](https://gitee.com/strandingzy/QT/tree/zyy/snake)**界面一&#xff1a;游戏大厅界面二&#xff1a;关卡选择界面界面三&#xff1a;游戏界面 二.项目实现2.1 游戏大厅2.2关卡选择界面2.3 游戏房间2.3.1 封装贪吃蛇数据结构2.3.2 …

如何通过谷歌外链快速增加网站流量?

利用谷歌外链提升流量的方法非常直接&#xff0c;但实际上&#xff0c;外链影响的是关键词排名&#xff0c;关键词排名提升了&#xff0c;自然就会有流量&#xff0c;所以谷歌外链不是直接能提升网站流量&#xff0c;而是间接的&#xff0c;下面&#xff0c;我会详细介绍几种有…

验收测试:确保软件符合业务需求和合同要求

目录 前言1. 验收测试的概念1.1 用户验收测试&#xff08;UAT&#xff09;1.2 操作验收测试&#xff08;OAT&#xff09; 2. 验收测试的主要作用2.1 确认业务需求的满足2.2 验证合同要求的实现2.3 提升用户信心 3. 验收测试在整个测试中的地位3.1 测试的最后一道关卡3.2 用户与…

niushop逻辑漏洞

niushop逻辑漏洞 安装环境 这里控制不了 抓包在数据包中可以改数据

非线性系统稳定控制器设及案例仿真(s-function函数)

目录 前沿一、案例11. 系统模型 二、案例21. 系统模型2. 稳定性分析3. 仿真(包含代码)1. 仿真效果2. 仿真结果3. 仿真剖析4. 仿真图与代码 前沿 定义一个系统 x ˙ f ( x , u ) \dot{x} f(x,u) x˙f(x,u), 其中 x x x 为状态变量&#xff0c; u u u 为系统输入&#xff0c…

跨平台终端工具Tabby安装配置与远程ssh连接Linux_ubuntu详细教程

文章目录 前言1. Tabby下载安装2. Tabby相关配置3. Tabby简单操作4. ssh连接Linux4.1 ubuntu系统安装ssh4.2 Tabby远程ssh连接ubuntu 5. 安装内网穿透工具5.1 创建公网地址5.2 使用公网地址远程ssh连接 6. 配置固定公网地址 前言 今天和大家分享一下如何在Windows系统使用Tabb…

构建LVS负载均衡群集

构建LVS负载均衡群集 实验环境实验环境 201:客户端 101:调度器 (需要用到2个网卡) 102:web 103:web 104:NFS存储 101:(添加一个网卡) 2 [root@localhost ~]# cd /etc/sysconfig/network-scripts/ 3 4 //查看另一个网卡的名字ens36 5 [root@localhost network-scrip…

人工智能深度学习系列—深度学习中的相似性追求:Triplet Loss 全解析

文章目录 1. 背景介绍2. Loss计算公式3. 使用场景4. 代码样例5. 总结 1. 背景介绍 在机器学习和模式识别领域&#xff0c;相似性度量是核心问题之一。Triplet Loss&#xff0c;作为一种特殊的损失函数&#xff0c;被设计用来学习数据的相对距离&#xff0c;从而使得相似样本更…

5.C_Demo_排序

冒泡排序法 原理&#xff1a; 依次比较相邻的两个元素&#xff0c;如果顺序错误就交换。 思路&#xff1a; 这种方法&#xff0c;显然需要很多轮才能完成&#xff0c;每一轮只能排序一个最大值或最小值(第一层for)&#xff0c;将全部的数据排序完成&#xff0c;需要很多轮(第…

第三期书生大模型实战营之书生大模型全链路开源开放体系

一. Introduction 大模型是发展通用人工智能的重要途经 二. 开源历程以及InternLM2 2024年1月17日 InternLM2 2开源 三. 书生浦语2.0的主要亮点 3.1 超长上下文 3.2 综合性能全面提升 3.3 优秀的对话和创作体验 3.4 工具调用能力整体升级 3.5 突出的数理能力和实用的…

Among Us 私服的制作之路

文章目录 Among Us 私服的制作之路这游戏通常包括以下核心元素&#xff1a;角色设定&#xff1a;游戏机制&#xff1a;游戏界面&#xff1a; 第四步&#xff1a;添加社交特性第五&#xff1a;测试与优化方面 十分基础的框架(Web)注意事项 Among Us 私服的制作之路 作者正在准备…

嵌入式:简单的UI框架

1&#xff1a;UI框架简介 除了服务框架外&#xff0c;我们还需要对外显示UI&#xff0c;所以我们就需要一个UI的框架&#xff0c;跟服务框架一样&#xff0c;不用这个UI框架我们也是可以实现&#xff0c;但是这样每个人写的UI都会有差异&#xff0c;需要的事件&#xff0c;数据…

牛客JS题(二十)判断斐波那契数组

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 循环判断斐波那契数列组递归判断斐波那契数列组合法性判断 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charset"utf-8" /></head><body><scrip…

嵌入式数据库 sqlite3

数据库文件与普通文件区别: 1.普通文件对数据管理(增删改查)效率低 2.数据库对数据管理效率高,使用方便 常用数据库: 1.关系型数据库 将复杂的数据结构简化为二维表格形式 大型:Oracle、DB2 中型:MySql、SQLServer 小型:Sqlite …

c# .net core项目角色授权机制

前言 角色授权机制是确保应用程序安全性的重要组成部分,它允许开发者根据用户的角色来限制对应用程序中不同资源的访问。 基本概念: 角色授权基于用户角色的访问控制,即根据用户所属的角色来决定其能够访问的资源或执行的操作。在.NET Core中,这通常与身份认证(Authent…

怎么配置一个axios来拦截前后端请求

首先创建一个axios.js文件 导入我们所需要的依赖 import axios from "axios"; import Element from element-ui import router from "./router"; 设置请求头和它的类型和地址 注意先注释这个url,还没有解决跨域问题,不然会出现跨域 // axios.defaults.…

Python Sklearn库SVM算法应用

SVM 是一种有监督学习分类算法&#xff0c;输入值为样本特征值向量和其对应的类别标签&#xff0c;输出具有预测分类功能的模型&#xff0c;当给该模型喂入特征值时&#xff0c;该模型可以它对应的类别标签&#xff0c;从而实现分类。 Sklearn库SVM算法 下面我看一下 Python …

CSS学习 - 选择器

基础选择器 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 类型选择器&…

牛逼,两百行Python代码带你打造一款《天天酷跑》游戏!(附源码)

《天天酷跑》是一款广受欢迎的跑酷类手机游戏&#xff0c;玩家需要控制角色在赛道上奔跑&#xff0c;躲避障碍物&#xff0c;收集金币和道具&#xff0c;以获取高分。虽然完全复现这款游戏的复杂度和图形效果在简单的Python环境中难以实现&#xff08;特别是游戏图形和动画&…

市电220V

概念 市电 220V 是指在中国及许多其他国家使用的标准交流电压,该值是电压的有效值(RMS值,Root Mean Square)。有效值是交流电压或电流的一个测量方式,它表示在一个周期内,交流电的平方平均值等于直流电压(或电流)的值。 有效值在交流电中具有特殊意义,因为交流电的瞬…