【opencv】第8章 图像轮廓与图像分割修复
8.1 查找并绘制轮廓
一个轮廓一般对应一系列的点,也就是图像中的一条曲线。其表示方法可能 根据不同的情况而有所不同。在OpenCV 中,可以用findContours()函数从二值图 像中查找轮廓
8.1.1 寻找轮廓: findContours() 函数
findContours) 函数用于在二值图像中寻找轮廓。
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, intmethod, Point offset = Point())
- 第一个参数,InputArray类型的image, 输入图像,即源图像,填Mat 类的 对象即可,且需为8位单通道图像。图像的非零像素被视为1,0像素值被 保留为0,所以图像为二进制。我们可以使用 compare() 、inrange()、
threshold() 、adaptivethreshold() 、cannyO 等函数由灰度图或彩色图创建二进 制图像。此函数会在提取图像轮廓的同时修改图像的内容。 - 第二个参数,OutputArrayOfArrays 类 型 的contours、检测到的轮廓、函数 调用后的运算结果存在这里。每个轮廓存储为一个点向量,即用point 类 型 的 vector表示。
- 第三个参数,OutputArray 类型的hierarchy,可选的输出向量,包含图像的 拓扑信息。其作为轮廓数量的表示,包含了许多元素。每个轮廓contours[i] 对 应 4 个hierarchy 元 素hierarchy[i][0]~hierarchy[i][3], 分别表示后一 个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果没有对应项,
对应的hierarchy[i] 值设置为负数。 - 第四个参数,int 类型的mode, 轮廓检索模式,取值如表8.1所示。
表8.1 findContours函数可选的轮廓检索模式
标识符 | 含义 |
---|---|
RETR_EXTERNAL | 表示只检测最外层轮廓。对所有轮廓,设置 hierarchy[i][2]=hierarchy[i][3]=-1 |
RETR_LIST | 提取所有轮廓,并且放置在list中。检测的轮廓 不建立等级关系 |
RETR_CCOMP | 提取所有轮廓,并且将其组织为双层结构(two-level hierarchy:顶层为连通域的外围边界, 次层为孔的内层边界 |
RETR_TREE | 提取所有轮廓,并重新建立网状的轮廓结构 |
- 第五个参数,int类 型 的method, 为轮廓的近似办法,取值如表8.2所示。
表8.2 findContours函数可选的轮廓近似办法
标识符 | 含义 |
---|---|
CHAIN_APPROX NONE | 获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过 1,即max(abs(xl-x2),abs(y2-y1))=1 |
CHAIN_APPROX_SIMPLE | 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向 的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息 |
CHAIN_APPROX_TC89_L1 ,CHAIN_APPROX_TC89_KCOS | 使用Teh-Chinl链逼近算法中的一个 |
同样地,在表8.1和8.2中列出的宏之前加上”CV_” 前缀,便是OpenCV2 中可以使用的宏。如"RETR_CCOMP” 宏 的OpenCV2 版 为“CV_RETR_CCOMP"。
- 第六个参数,Point 类 型 的 offset,每个轮廓点的可选偏移量,有默认值 Point()。对 ROI 图像中找出的轮廓,并要在整个图像中进行分析时,这个 参数便可排上用场。
findContours 经 常 与drawContours 配合使用一使用用findContours (函数检测 到图像的轮廓后,便可以用drawContours (函数将检测到的轮廓绘制出来。接下来, 让我们一起看看drawContours() 函数的用法。
8.1.2 绘 制 轮 廓 :drawContours ()函 数
drawContours() 函数用于在图像中绘制外部或内部轮廓。
void drawContours(InputoutputArray image, InputArrayofArrays contours, int contourIdx, const Scalar &color, int thickness = 1, int lineType = 8, InputArray hierarchy = noArray(0), int maxLevel = INT_MAX, Point offset = Point())
- 第 一 个参数,InputArray类 型 的image, 目标图像,填Mat 类的对象即可。
- 第二个参数,InputArrayOfArrays类型的contours,所有的输入轮廓。每个 轮廓存储为一个点向量,即用point类 型 的vector表示。
- 第三个参数,int类 型 的contourldx,轮廓绘制的指示变量。如果其为负值, 则绘制所有轮廓。
- 第四个参数,const Scalar&类型的color, 轮廓的颜色。
- 第五个参数,int thickness,轮廓线条的粗细度,有默认值1。如果其为负 值(如 thickness=cv_filled), 便会绘制在轮廓的内部。可选为FILLED 宏(OpenCV2版为CV_FILLED)。
- 第六个参数,int 类型的lineType,线条的类型,有默认值8。取值类型如 表8 . 3所示。
表8.3 可选线性
lineType线性 | 含义 |
---|---|
8(默认值) | 8连通线型 |
4 | 4连通线型 |
LINE_AA(OpenCV2版为CV_AA) | 抗锯齿线型 |
- 第七个参数,InputArray 类型的hierarchy,可选的层次结构信息,有默认 值noArray()。
- 第八个参数,int类型的maxLevel,表示用于绘制轮廓的最大等级,有默认 值INT_MAX
- 第九个参数,Point类型的offset,可选的轮廓偏移参数,用指定的偏移量 offset=(dx,dy) 偏移需要绘制的轮廓,有默认值Point()。
下面是 一 个调用小示例。 //在白色图像上绘制黑色轮廓
Mat result(image.size(),CV_8U,cv::Scalar(255));
drawContours(result,contours,- 1,Scalar(0),3);
8.1.3 基础示例程序:轮廓查找
void Test52() {Mat srcImage = imread("image.jpg", 0); //灰度图读入imshow("src", srcImage);Mat dstImage = Mat::zeros(srcImage.rows, srcImage.cols, CV_8UC3);srcImage = srcImage > 119;imshow("mid", srcImage); //取阈值后的图像//定义轮廓和层次结构std::vector<std::vector<Point>>contours;std::vector<Vec4i>hierachy;//查找轮廓findContours(srcImage, contours, hierachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);//遍历所有顶层的罗坤,随机颜色绘制出每个连接组件颜色int index = 0;for (; index >= 0; index = hierachy[index][0]) {Scalar color(rand() & 255, rand() & 255, rand() & 255);drawContours(dstImage, contours, index, color, FILLED, 8, hierachy);imshow("dst", dstImage);}waitKey(0);
}
8.1.4 综合示例程序:查找并绘制轮廓
除了上述这个精简版的示例程序,还为大家准备了一个更加复杂一些的关 于查找并绘制轮廓的综合示例程序。此程序利用了图像平滑技术(blur() 函数)和边缘检测技术(cannyO 函数),根据滑动条的调节,可以动态地检测出图形的 轮 廓 。
namespace test53 {Mat g_srcImage, g_grayImage;int g_nThresh = 80;int g_nThresh_max = 255;RNG g_rng(12345);Mat g_cannyMat_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;void on_ThreshChange(int, void*) {Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3); //Canny算子边缘检测findContours(g_cannyMat_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//轮廓提取Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//随机值drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point());}imshow("drawing", drawing);}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3)); //降噪namedWindow("window1");imshow("window1", g_srcImage);createTrackbar("value", "window1", &g_nThresh, g_nThresh_max, on_ThreshChange);on_ThreshChange(0, 0);waitKey(0);}
}void Test53() {test53::Test();
}
8.2 寻找物体的凸包
8.2.1 凸 包
凸包(Convex Hull) 是一个计算几何(图形学)中常见的概念。简单来说, 给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它是 能包含点集中所有点的。理解物体形状或轮廓的一种比较有用的方法便是计算一 个物体的凸包,然后计算其凸缺陷(convexity defects)。很多复杂物体的特性能很 好地被这种缺陷表现出来。
如图8.9所示,我们用人手图来举例说明凸缺陷这一概念。手周围深色的线 描画出了凸包,A 到 H 被标出的区域是凸包的各个“缺陷”。正如看到的,这些 凸度缺陷提供了手以及手状态的特征表现的方法。
新版OpenCV 中 ,convexHull 函数用于寻找图像点集中的凸包,我们一起来 看一下这个函数。
8.2.2 寻找凸包:convexHullO函数
上文已经提到过,convexHullO 函数用于寻找图像点集中的凸包,其原型声明 如 下 。
void convexHull(InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true)
- 第一个参数,InputArray类型的points,输入的二维点集,可以填Mat 类型 或者std::vector。
- 第二个参数,OutputArray类型的hull,输出参数,函数调用后找到的凸包。
- 第三个参数,bool 类型的clockwise,操作方向标识符。当此标识符为真时, 输出的凸包为顺时针方向。否则,就为逆时针方向。并且是假定坐标系的 x 轴指向右,y 轴指向上方。
- 第四个参数,bool 类型的returnPoints,操作标志符,默认值true。当 标 志 符为真时,函数返回各凸包的各个点。否则,它返回凸包各点的指数。当 输出数组是std::vector 时,此标志被忽略。
8.2.3 基础示例程序:凸包检测基础
为了理解凸包检测的运用方法,下面放出一个完整的示例程序。程序中会首 先随机生成3~103个坐标值随机的彩色点,然后利用convexHull, 对由这些点链 接起来的图形求凸包。
void Test54() {Mat image(600, 600, CV_8UC3);RNG& rng = theRNG();while (1) {char key;int count = (unsigned)rng % 100 + 3;std::vector<Point>points;//随机生成坐标for (int i = 0; i < count; ++i) {Point point;point.x = rng.uniform(image.cols / 4, image.cols * 3 / 4);point.y = rng.uniform(image.rows / 4, image.rows * 3 / 4);points.push_back(point);}//检测凸包std::vector<int>hull;convexHull(Mat(points), hull, true);//绘制出随机颜色的点image = Scalar::all(0);for (int i = 0; i < count; ++i) {circle(image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),FILLED,LINE_AA);}int hullcount = hull.size();Point point0 = points[hull.back()];//依次连接凸包的点for (int i = 0; i < hullcount; ++i) {Point point = points[hull[i]];line(image, point0, point, Scalar(255, 255, 255), 2, LINE_AA);point0 = point;}imshow("hull", image);key = waitKey(0);if (key == 27) break;}}
8.2.4 综合示例程序:寻找和绘制物体的凸包
这一节的综合示例程序,依然是结合滑动条,通过滑动条控制阈值,来得到 不同的凸包检测效果图。程序详细注释的源代码如下。
namespace test55 {Mat g_srcImage, g_grayImage;int g_nThresh = 50;int g_maxThresh = 255;RNG g_rng(12345);Mat srcImage_copy = g_srcImage.clone();Mat g_thresholdImage_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;void on_ThreshChange(int, void*) {// 二值化threshold(g_grayImage, g_thresholdImage_output, g_nThresh, 255, THRESH_BINARY);findContours(g_thresholdImage_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); //轮廓std::vector<std::vector<Point>>hull(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {convexHull(Mat(g_vContours[i]), hull[i], false);}//绘制出轮廓及凸包Mat drawing = Mat::zeros(g_thresholdImage_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255)); drawContours(drawing, g_vContours, i, color, 1, 8, std::vector<Vec4i>(), 0, Point()); //轮廓drawContours(drawing, hull, i, color, 1, 8, std::vector<Vec4i>(), 0, Point()); //凸包}imshow("drawing", drawing);}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));//降噪//创建窗口namedWindow("src");imshow("src", g_srcImage);//创建滚动条createTrackbar("value", "src", &g_nThresh, g_maxThresh, on_ThreshChange);on_ThreshChange(0, nullptr);waitKey(0);}
}void Test55() {test55::Test();
}
8.3 使用多边形将轮廓包围
在实际应用中,常常会有将检测到的轮廓用多边形表示出来的需求。本节就 为大家讲解如何用多边形来表示出轮廓,或者说如何根据轮廓提取出多边形。先 让我们一起学习用OpenCV 创建包围轮廓的多边形边界时会接触到的一些函数。
8.3.1 返回外部矩形边界:boundingRect(O函数
此函数计算并返回指定点集最外面(up-right)的矩形边界。 C++:Rect boundingRect(InputArray points)
其唯一的一个参数为输入的二维点集,可以是std::vector 或 Mat 类型。
8.3.2 寻找最小包围矩形:minAreaRect(函数
此函数用于对给定的2D 点集,寻找可旋转的最小面积的包围矩形。 C++:RotatedRect minAreaRect(InputArray points)
其唯一的一个参数为输入的二维点集,可以为std::vector> 或 Mat 类型。
8.3.3寻找最小包围圆形:minEnclosingCircle(函数
minEnclosingCircle函数的功能是利用一种迭代算法,对给定的2D 点集,去 寻找面积最小的可包围它们的圆形。
void minEnclosingCircle(InputArray points, Point2f ¢er, float &radius)
- 第 一 个 参 数 ,InputArray 类 型 的points,输入的二维点集,可以为std::vector◇ 或Mat 类型。
- 第二个参数,Point2f&类型的center,圆的输出圆心。
- 第三个参数,float&类型的radius,圆的输出半径。
8.3.4 用椭圆拟合二维点集:fitEllipse ( 函 数
此函数的作用是用椭圆拟合二维点集。
RotatedRect fitEllipse(InputArray points)
其唯一的一个参数为输入的二维点集,可以为std::vector<>或Mat 类型。
8.3.5 逼近多边形曲线: approxPolyDPO 函 数
approxPolyDP函数的作用是用指定精度逼近多边形曲线。
void approxPolyDP(InputArray curve, OutputArray approxCurve,double epsilon, bool closed)
- 第一个参数,InputArray类型的curve,输入的二维点集,可以为std::vecto 或Mat 类型。
- 第二个参数,OutputArray类型的approxCurve,多边形逼近的结果,其类 型应该和输入的二维点集的类型 一 致。
- 第三个参数,double类型的epsilon,逼近的精度,为原始曲线和即近似曲 线间的最大值。
- 第四个参数,bool 类型的closed,如果其为真,则近似的曲线为封闭曲线 (第一个顶点和最后一个顶点相连),否则,近似的曲线曲线不封闭。
8.4 图像的矩
矩函数在图像分析中有着广泛的应用,如模式识别、目标分类、目标识别与 方位估计、图像编码与重构等。一个从一幅数字图形中计算出来的矩集,通常描 述了该图像形状的全局特征,并提供了大量的关于该图像不同类型的几何特性信 息,比如大小、位置、方向及形状等。图像矩的这种特性描述能力被广泛地应用 在各种图像处理、计算机视觉和机器人技术领域的目标识别与方位估计中。一阶 矩与形状有关,二阶矩显示曲线围绕直线平均值的扩展程度,三阶矩则是关于平 均值的对称性的测量。由二阶矩和三阶矩可以导出一组共7个不变矩。而不变矩 是图像的统计特性,满足平移、伸缩、旋转均不变的不变性,在图像识别领域得 到了广泛的应用。
那 么 , 在OpenCV 中,如何计算 一个图像的矩呢? 一般由 moments、 contourArea 、arcLength 这三个函数配合求取。
- 使用moments 计算图像所有的矩(最高到3阶)
- 使用contourArea来计算轮廓面积
- 使用arcLength来计算轮廓或曲线长度 下面对其进行一一剖析。
8.4.1 矩的计算:momentsO函数
moments()函数用于计算多边形和光栅形状的最高达三阶的所有矩。矩用来计 算形状的重心、面积,主轴和其他形状特征,如7Hu不变量等。
Moments moments(InputArray array, bool binaryImage = false)
- 第一个参数,InputArray类型的array,输入参数,可以是光栅图像(单通 道,8位或浮点的二维数组)或二维数组 (IN 或 N1)。
- 第二个参数,bool类型的binaryImage,有默认值false。若此参数取 true, 则所有非零像素为1。此参数仅对于图像使用。
需要注意的是,此参数的返回值返回运行后的结果。
8.4.2计算轮廓面积:contourArea(函数
contourArea()函数用于计算整个轮廓或部分轮廓的面积
double contourArea(InputArray contour,bool oriented=false)
- 第一个参数,InputArray类型的contour,输入的向量,二维点(轮廓顶点), 可以为std::vector 或 Mat 类型。
- 第二个参数,bool类型的oriented,面向区域标识符。若其为true,该函数 返回一个带符号的面积值,其正负取决于轮廓的方向(顺时针还是逆时针)。 根据这个特性我们可以根据面积的符号来确定轮廓的位置。需要注意的是, 这个参数有默认值false, 表示以绝对值返回,不带符号。
8.4.3 计算轮廓长度:arcLengthO函数
arcLength(函数用于计算封闭轮廓的周长或曲线的长度。
double arcLength(InputArray curve,bool closed)
- 第一个参数,InputArray类型的curve,输入的二维点集,可以为std:vector或Mat 类型。
- 第二个参数,bool 类型的closed, 一个用于指示曲线是否封闭的标识符, 有默认值closed, 表示曲线封闭。
8.4.4 综合示例程序:查找和绘制图像轮廓矩
学习完函数的讲解,让我们一起通过一个综合的示例程序,真正了解本节内 容的实战用法 。
namespace test56{Mat g_srcImage, g_grayImage;int g_nThresh = 100;int g_nMaxThresh = 255;RNG g_rng(12345);Mat g_cannyMat_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;//回调函数void on_ThreshChange(int, void*) {Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2); //边缘检测findContours(g_cannyMat_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); //找到轮廓//计算矩std::vector<Moments>mu(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {mu[i] = moments(g_vContours[i], false);}//计算中心矩std::vector<Point2f>mc(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {mc[i] = Point2f(static_cast<float>(mu[i].m10 / mu[i].m00), static_cast<float>(mu[i].m01 / mu[i].m00));}//绘制轮廓Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point()); //绘制内层和外层轮廓circle(drawing, mc[i], 4, color, -1, 8, 0);//绘制圆}imshow("drawing", drawing);//计算轮廓面积std::cout << "\t";for (int i = 0; i < g_vContours.size(); ++i) {std::cout << ">Through m00 Areas[" << i << "]:" << mu[i].m00 << "Length = "<< arcLength(g_vContours[i], true)<< std::endl;std::cout << ">Through OpenCV m_00 Areas[" << i << "]" <<contourArea(g_vContours[i]) <<"Length = " << arcLength(g_vContours[i], true) << std::endl;Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point());circle(drawing, mc[i], 4, color, -1, 8, 0);}}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));namedWindow("srcImage");imshow("srcImage", g_srcImage);createTrackbar("value:", "srcImage", &g_nThresh, g_nMaxThresh,on_ThreshChange);waitKey(0);}}void Test56() {test56::Test();
}
8.5 分水岭算法
在许多实际运用中,我们需要分割图像,但无法从背景图像中获得有用信 息。分水岭算法 (watershed algorithm) 在这方面往往是非常有效的。此算法可 以将图像中的边缘转化成“山脉”,将均匀区域转化为“山谷”,这样有助于分 割目标。
分水岭算法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想 是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的 海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形 成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明:在每一个局部 极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深, 每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即 形成分水岭。
分水岭的计算过程是一个迭代标注过程。分水岭比较经典的计算方法是由 L.Vincent 提出的。在该算法中,分水岭计算分两个步骤: 一个是排序过程, 一个是淹没过程。首先对每个像素的灰度级进行从低到高的排序,然后在从低 到高实现淹没的过程中,对每一个局部极小值在h 阶高度的影响域采用先进先 出 (FIFO) 结构进行判断及标注。分水岭变换得到的是输入图像的集水盆图像, 集水盆之间的边界点,即为分水岭。显然,分水岭表示的是输入图像的极大值 点。
也就是说,分水岭算法首先计算灰度图像的梯度;这对图像中的“山谷”或 没有纹理的“盆地”(亮度值低的点)的形成是很有效的,也对“山头”或图像中 有主导线段的“山脉”(山脊对应的边缘)的形成有效。然后开始从用户指定点(或 者算法得到点)开始持续“灌注”盆地直到这些区域连成一片。基于这样产生的 标记就可以把区域合并到0一起,合并后的区域又通聚集的方式进行分割,好像 图像被“填充”起来一样。
8.5.1 实现分水岭算法:watershedO 函 数
函 数watershed 实现的分水岭算法是基于标记的分割算法中的 一种。在把图像 传给函数之前,我们需要大致勾画标记出图像中的期望进行分割的区域,它们被 标记为正指数。所以,每 一 个区域都会被标记为像素值1、2、3等,表示成为 一 个或者多个连接组件。这些标记的值可以使用findContours() 函 数 和drawContours()
函数由二进制的掩码检索出来。不难理解,这些标记就是即将绘制出来的分割区 域的“种子”,而没有标记清楚的区域,被置为0。在函数输出中,每 一 个标记中 的像素被设置为“种子”的值,而区域间的值被设置为- 1。
void watershed(InputArray image, InputOutputArray markers)
- 第 一 个 参 数 ,InputArray 类 型 的src, 输入图像,即源图像,填Mat 类 的 对 象
即可,且需为8位三通道的彩色图像。 - 第 二 个 参 数 ,InputOutputArray 类 型 的markers, 函数调用后的运算结果存在 这里,输入/输出32位单通道图像的标记结果。即这个参数用于存放函数调用后 的输出结果,需和源图片有 一 样的尺寸和类型。
8.5.2 综合示例程序:分水岭算法
namespace test57 {Mat g_maskImage, g_srcImage;Point prevPt(-1, -1);static void on_Mouse(int event, int x, int y, int flags, void*) {if (x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows) {return;}if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) {prevPt = Point(-1, -1);}else if (event == EVENT_LBUTTONDOWN) {prevPt = Point(x, y);}else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {Point pt(x, y);if (prevPt.x < 0) {prevPt = pt;}line(g_maskImage, prevPt, pt, Scalar::all(255), 5, 8, 0);line(g_srcImage, prevPt, pt, Scalar::all(255), 5, 8, 0);prevPt = pt;imshow("srcImage", g_srcImage);}}void Test() {g_srcImage = imread("mountain.jpg");imshow("srcImage", g_srcImage);Mat srcImage, grayImage;g_srcImage.copyTo(srcImage);cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY);cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);g_maskImage = Scalar::all(0);setMouseCallback("srcImage", on_Mouse, 0);while (1) {int c = waitKey(1); // 改为 1 来更流畅地显示if (c == 27) {break;}//恢复原图if ((char)c == '2') {g_maskImage = Scalar::all(0);srcImage.copyTo(g_srcImage);imshow("srcImage", g_srcImage);}else if ((char)c == '1') {int i, j, compCount = 0;std::vector<std::vector<Point>>contours;std::vector<Vec4i>hierarchy;//寻找轮廓findContours(g_maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);if (contours.empty()) {continue;}Mat maskImage(g_maskImage.size(), CV_32S); //复制掩膜maskImage = Scalar::all(0);for (int index = 0; index >= 0; index = hierarchy[index][0]) {compCount++;drawContours(maskImage, contours, index, Scalar::all(compCount), -1, 8, hierarchy, INT_MAX);}if (compCount == 0) continue;//生成随机颜色std::vector<Vec3b>colorTab;for (int i = 0; i < compCount; ++i) {uchar b = theRNG().uniform(0, 255);uchar g = theRNG().uniform(0, 255);uchar r = theRNG().uniform(0, 255);colorTab.push_back(Vec3b(b, g, r));}watershed(srcImage, maskImage); //分水岭算法//将分水岭图像遍历存入watershedImage中Mat watershedImage(maskImage.size(), CV_8UC3);for (int i = 0; i < maskImage.rows; ++i) {for (int j = 0; j < maskImage.cols; ++j) {int index = maskImage.at<int>(i, j);if (index == -1) {watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0); }else if (index <= 0 || index > compCount) {watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);}else {watershedImage.at<Vec3b>(i, j) = colorTab[index - 1];}}}//混合灰度图和分水岭效果图watershedImage = watershedImage * 0.5 + grayImage * 0.5;imshow("watershed transform", watershedImage);}}}
}void Test57() {test57::Test();
}
8.6 图像修补
在实际应用中,我们的图像常常会被噪声腐蚀,这些噪声或者是镜头上的灰 尘或水滴,或者是旧照片的划痕,或者由于图像的部分本身已经损坏。而“图像 修复”(Inpainting), 就是妙手回春,解决这些问题的良方。图像修复技术简单来说,就是利用那些已经被破坏区域的边缘,即边缘的颜色和结构,繁殖和混合到 损坏的图像中,以达到图像修补的目的。图8.34~8.36就是示例程序截图,演示 将图像中的字迹移除的效果。
8.6.1 实现图像修补:inpaint ( 函 数
在新版OpenCV 中,图像修补技术由inpaint 函数实现,它可以用来从扫描的 照片中清除灰尘和划痕,或者从静态图像或视频中去除不需要的物体。其原型声 明如下。
C++:void inpaint(InputArray src,InputArray inpaintMask,OutputArray
dst,double inpaintRadius,int flags)
- 第 一 个参数,InputArray类 型 的src, 输入图像,即源图像,填Mat 类的对 象即可,且需为8位单通道或者三通道图像。
- 第二个参数,InputArray类型的inpaintMask, 修复掩膜,为8位的单通道 图像。其中的非零像素表示需要修补的区域。
- 第三个参数,OutputArray 类 型 的dst, 函数调用后的运算结果存在这里, 和源图片有一样的尺寸和类型。
- 第四个参数,double 类型的 inpaintRadius,需要修补的每个点的圆形邻域, 为修复算法的参考半径。
- 第五个参数,int 类型的flags,修补方法的标识符,可以是表8.4所示两者 之一。
标识符 | 说明 |
---|---|
INPAINT_NS | 基于Navier-Stokes方程的方法 |
INPAINT_TELEA | Alexandru Telea方法 |
OpenCV2 中INPAINT_NS 和INPAINT_TELEA 标识符可以分别写作CV_ INPAINT_NS 和 CV_INPAINT_TELEA
8.6.2 综合示例程序:图像修补
函数和概念讲解完毕,下面我们依然是学习 一 个以本节所讲内容为核心的示 例程序,将本节所学内容付诸实践,融会贯通。此示例程序会先让我们在图像中 用鼠标绘制出白色的线条破坏图像,然后按下键盘按键【1】或【SPACE】 进 行 图 像修补操作。且如果对自己的绘制不够满意,可以按下键盘按键【2】恢复原始图 像。
namespace test58 {Mat g_srcImage, inpaintMask,srcImage;Point previousPoint(-1, -1);static void On_Mouse(int event,int x,int y,int flags,void*) {if (x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows) {return;}if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) {previousPoint = Point(-1, -1);}else if (event == EVENT_LBUTTONDOWN) {previousPoint = Point(x, y);}else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {Point pt(x, y);if (previousPoint.x < 0) {previousPoint = pt;}line(inpaintMask, previousPoint, pt, Scalar::all(255), 5, 8, 0);line(g_srcImage, previousPoint, pt, Scalar::all(255), 5, 8, 0);previousPoint = pt;imshow("srcImage", g_srcImage);}}void Test() {g_srcImage = imread("sky.jpg");srcImage = g_srcImage.clone();inpaintMask = Mat::zeros(srcImage.size(), CV_8U);imshow("srcImage", g_srcImage);setMouseCallback("srcImage", On_Mouse, nullptr);while (1) {char c = waitKey(1);if (c == 27) break;if (c == '2') {inpaintMask = Scalar::all(0);srcImage.copyTo(g_srcImage);imshow("srcImage", g_srcImage);}if (c == '1') {Mat inpaintedImage;inpaint(g_srcImage, inpaintMask, inpaintedImage, 3, INPAINT_TELEA); imshow("fixed", inpaintedImage);}}}}void Test58() {test58::Test();
}
8.7 本章小结
本章中,我们先学习了查找轮并绘制轮廓,然后学习了如何寻找到物体的凸 包,接着是使用多边形来包围轮廓,以及计算一个图像的矩。在本章后面几节, 还学习了分水岭算法和图像修补操作的实现方法。
函数名称 | 说明 | 对应讲解章节 |
---|---|---|
BoundingRect | 计算并返回指定点集最外面(up-right)的矩形边 界 | 8.3.1 |
minAreaRect | 寻找可旋转的最小面积的包围矩形 | 8.3.2 |
minEnclosingCircle | 利用一种迭代算法,对给定的2D点集,寻找面 积最小的可包围他们的圆形 | 8.3.3 |
fitEllipse | 用椭圆拟合二维点集 | 8.3.4 |
approxPolyDP | 用指定精度逼近多边形曲线 | 8.3.5 |
moments | 计算多边形和光栅形状的最高达三阶的所有矩 | 8.4.1 |
contourArea | 计算整个轮廓或部分轮廓的面积 | 8.4.2 |
arcLength | 计算封闭轮廓的周长或曲线的长度 | 8.4.3 |
watershed | 实现分水岭算法 | 8.5.1 |
inpaint | 进行图像修补,从扫描的照片中清除灰尘和划痕, 或者从静态图像或视频中去除不需要的物体 | 8.6.1 |
本章示例程序清单
示例程序序号 | 程序说明 | 对应章节 |
---|---|---|
69 | 轮廓查找 | 8.1.3 |
70 | 查找并绘制轮廓 | 8.1.4 |
71 | 凸包检测基础 | 8.2.3 |
72 | 寻找和绘制物体的凸包 | 8.2.4 |
73 | 创建包围轮廓的矩形边界 | 8.3.6 |
74 | 创建包围轮廓的圆形边界 | 8.3.7 |
75 | 使用多边形包围轮廓 | 8.3.8 |
76 | 图像轮廓矩 | 8.4.4 |
77 | 分水岭算法的使用 | 8.5.2 |
78 | 实现图像修补 | 8.6.2 |
相关文章:
【opencv】第8章 图像轮廓与图像分割修复
8.1 查找并绘制轮廓 一个轮廓一般对应一系列的点,也就是图像中的一条曲线。其表示方法可能 根据不同的情况而有所不同。在OpenCV 中,可以用findContours()函数从二值图 像中查找轮廓 8.1.1 寻找轮廓: findContours() 函数 findContours) 函…...
excel VBA 基础教程
这里写目录标题 快捷键选择所有有内容的地方 调试VBA录制宏,打开VBA开发工具录制宏,相当于excel自动写代码(两个表格内容完全一致才可以) 查看宏代码保持含有宏程序的文件xlsm后缀(注意很容易有病毒)宏文件安全设置 使…...
2008-2019年各省城镇人口数据
2008-2019年各省城镇人口数据 1、时间:2008-2019年 2、来源:国家统计局、统计年鉴 3、指标:行政区划代码、地区、年份、城镇人口 4、范围:31省 5、指标解释:城镇人口是指居住在城镇范围内的全部常住人口。 6、下…...
【机器学习】在不确定的光影中:机器学习与概率论的心灵共舞
文章目录 概率与统计基础:解锁机器学习的数据洞察之门前言一、概率论基础1.1 概率的基本概念与性质1.1.1 概率的定义1.1.2 样本空间与事件1.1.3 互斥事件与独立事件1.1.4 概率的计算方法 1.2 条件概率与独立性1.2.1 条件概率1.2.2 独立事件 1.3 随机变量1.3.1 随机变…...
vscode使用Marscode编程助手
下载 vscode 在插件里下载Marscode编程助手 插件完成 在这里点击安装,点击后这里出现AI编程插件。...
谷歌开放语音命令数据集,助力初学者踏入音频识别领域
在人工智能的浪潮中,语音识别技术正逐渐成为我们日常生活的一部分。从智能助手到语音控制设备,语音识别的应用场景越来越广泛。然而,对于初学者来说,进入这一领域往往面临诸多挑战,尤其是缺乏合适的开源数据集和简单的…...
Diffchecker图像比较工具介绍
Diffchecker图像比较工具介绍 网站地址: Diffchecker图像比较 主要功能: 图像差异比较: 该工具允许用户上传两张图片,系统会自动识别并高亮显示这两张图片之间的差异。简单易用: 用户只需将图片拖放到指定区域或点击浏…...
后端开发 Springboot整合Redis Spring Data Redis 模板
目录 redis 配置 RedisConfig 类 完整代码 代码讲解 1. 类定义和注解 2. 定义 RedisTemplate Bean 3. 配置 JSON 序列化 4. 配置 Redis 的 key 和 value 序列化方式 5. 完成配置并返回 RedisTemplate 总结 redis 服务接口实现类 类级别 注入 RedisTemplate 常用 Re…...
极狐GitLab 正式发布安全版本17.7.1、17.6.3、17.5.5
本分分享极狐GitLab 补丁版本 17.7.1, 17.6.3, 17.5.5 的详细内容。这几个版本包含重要的缺陷和安全修复代码,我们强烈建议所有私有化部署用户应该立即升级到上述的某一个版本。对于极狐GitLab SaaS,技术团队已经进行了升级,无需用户采取任何…...
策略模式详解与应用
策略模式(Strategy Pattern),是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以互相替换,而应用程序可以在运行时选择使用哪一个算法。策略模式使得算法的变化独立于使…...
Gateway怎么实现限流的
Gateway怎么实现限流的 在API网关(如Spring Cloud Gateway、Kong、Nginx等)中实现限流是为了控制服务请求的频率,从而避免系统过载,确保稳定性和可用性。限流可以通过多种策略实现,常见的方法包括基于请求次数、时间窗…...
OpenCV实现Kuwahara滤波
Kuwahara滤波是一种非线性的平滑滤波技术,其基本原理在于通过计算图像模板中邻域内的均值和方差,选择图像灰度值较为均匀的区域的均值来替代模板中心像素的灰度值。以下是Kuwahara滤波的详细原理说明: 一、基本思想 Kuwahara滤波的基本思想…...
【DevOps】Jenkins使用Pipeline构建java代码
使用Pipeline发布java项目 文章目录 使用Pipeline发布java项目资源列表基础环境一、准备gitlab1.1、部署gitlab1.2、创建chinanews项目1.3、提交代码1.4、查看上传的代码 二、准备Jenkins2.1、部署Jenkins2.2、安装maven2.3、修改Maven源2.4、准备chinanews 三、Jenkins配置工具…...
【网络云SRE运维开发】2025第2周-每日【2025/01/12】小测-【第12章 rip路由协议】理论和实操考试题
文章目录 选择题理论题 解释RIP协议中的“水平分割”机制,并说明其目的。 可以防止路由器错误地将从邻居学到的路由再发送回给该邻居,从而避免路由环路的发生。实操题 【网络云SRE运维开发】2025第2周-每日【2025/01/12】小测-【第12章 rip路由协议】理论…...
Entity 的材质(棋盘、条纹、网格)
Entity 的材质 普通物体的材质 import { nextTick, onMounted, ref } from vue import * as Cesium from cesium // console.log(Cesium, Cesium)const viewer ref<any>(null)onMounted(() > { ... })let material Cesium.Color.YELLOW.withAlpha(0.5)Cesium.Colo…...
shell脚本编写练习3
1、shell 脚本写出检测 /tmp/size.log 文件如果存在显示它的内容,不存在则创建一个文件将创建时间写入。 #!/bin/bash # 定义文件路径变量 file_path"/tmp/size.log"# 使用if语句检查文件是否存在 if [ -e "$file_path" ] # 检查变量file_path…...
事务的隔离级别和MDL
文章目录 说明不同隔离级别可能发生的现象关键现象解释MDL(元数据锁,Metadata Lock)MDL 的作用MDL 的工作原理MDL 锁的常见场景如何避免 MDL 阻塞 说明 本文章由大模型对话整理而来,如果有错误之处,请在评论区留言指正…...
用户界面软件05
已知应用 几乎所有的流行的用户界面架构都使用这种模式。我在这里举三个例子: 1. Seeheim 用户界面架构的特点是有一个应用核心的领域层和一个用户界面层。后者 被分为两层,叫做表示层和对话控制层。因为这个架构和面向事务系统有渊源,没有…...
基于Springboot + vue实现的办公用品管理系统
🥂(❁◡❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞 💖📕🎉🔥 支持我:点赞👍收藏⭐️留言📝欢迎留言讨论 🔥🔥&…...
17_Redis管道技术
Redis管道(Pipeline)技术是一种在 Redis 客户端与服务器之间进行高效数据交互的技术。 1.Redis管道技术介绍 1.1 传统请求响应模式 在传统的请求-响应模式下,客户端每发送一个命令后会等待服务器返回结果,然后再发送下一个命令。这种方式在网络延迟较高的情况下会导致性…...
【环境搭建】Metersphere v2.x 容器部署教程踩坑总结
前言 Metersphere部署过程中遇到的问题有点多,原因是其容器的架构蛮复杂的,比较容易踩坑,所以记录一下。 介绍 MeterSphere 是开源持续测试平台,遵循 GPL v3 开源许可协议,涵盖测试管理、接口测试、UI 测试和性能测…...
Vue虚拟DOM:如何提高前端开发效率
前言 随着前端技术的不断发展,越来越多的框架和库涌现出来,其中Vue.js成为了最受欢迎的前端框架之一。Vue.js采用了响应式数据绑定和组件化的思想,让开发者可以更加高效地构建交互式的用户界面。而Vue.js的底层原理涉及到许多概念和技术&…...
【C】预处理详解
在上一篇文章中,简单讲解了一个C程序是如何从一句句C代码变为一个个二进制指令,并最终变成可执行程序成功运行。在预处理、编译、汇编、链接四个步骤中,预处理阶段做的事情特别多,接下来我们就来讲解一下在预处理阶段处理的一些预…...
CES Asia 2025:VR/AR/XR引领科技新潮流
在全球科技领域蓬勃发展的大背景下,CES Asia 2025(赛逸展)即将在京盛大开幕,VR/AR/XR技术作为前沿科技的代表,将在本次展会上大放异彩,展现出令人瞩目的发展趋势和巨大潜力,同时政策优势也将为其…...
Lua调用C#
目录 创建C#入口 Lua调用类 Lua调用枚举 Lua调用数组,列表,字典 Lua调用C#拓展方法 Lua调用C#Ref与Out知识 Lua调用C#函数重载 Lua调用C#委托与事件 Lua调用C#二维数组 Lua调用C#中nil与null的差距 Lua调用C#中让系类型与lua能够互相访问 Lua调用…...
EdgeOne安全专项实践:上传文件漏洞攻击详解与防范措施
靶场搭建 当我们考虑到攻击他人服务器属于违法行为时,我们需要思考如何更好地保护我们自己的服务器。为了测试和学习,我们可以搭建一个专门的靶场来模拟文件上传漏洞攻击。以下是我搭建靶场的环境和一些参考资料,供大家学习和参考࿰…...
springboot使用Easy Excel导出列表数据为Excel
springboot使用Easy Excel导出列表数据为Excel Easy Excel官网:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write 主要记录一下引入时候的pom,直接引入会依赖冲突 解决方法: <!-- 引入Easy Excel的依赖 -->&l…...
现代 CPU 的高性能架构与并发安全问题
现代 CPU 的设计(如多级缓存、指令重排)为了提升性能,引入了许多优化机制,但这些机制可能导致并发场景下的安全性问题。并发安全性主要体现在三个方面:原子性、有序性 和 可见性。这些问题在底层通过 CAS(C…...
【数模学习笔记】插值算法和拟合算法
声明:以下笔记中的图片以及内容 均整理自“数学建模学习交流”清风老师的课程资料,仅用作学习交流使用 文章目录 插值算法定义三个类型插值举例插值多项式分段插值三角插值 一般插值多项式原理拉格朗日插值法龙格现象分段线性插值 牛顿插值法 Hermite埃尔…...
JavaScript 数组及其常用方法
1. JavaScript 数组概述 数组是 JavaScript 中用于存储多个值的数据结构。它可以存储不同类型的元素,并提供强大的方法来操作和管理数据。数组的元素按索引(从 0 开始)进行访问。 2. 数组的创建方式 1) 使用数组字面量 let fruits [&quo…...
SQL HAVING 子句深入解析
SQL HAVING 子句深入解析 介绍 SQL(Structured Query Language)是一种用于管理关系数据库管理系统的标准编程语言。在SQL中,HAVING子句是与GROUP BY子句一起使用的,用于筛选分组后的数据。它根据聚合函数的结果对组进行条件过滤…...
vue3+ts的几个bug调试
由于编译问题,把几个type检查给关闭了,否则错误太多。 1)第一个检查出的问题,拼写错误数组的length,写成了lengh。 2)数组的对象引用。 torStatus Array(8).fill({ ...defaultStatus }) as TorStatus[]…...
git: hint:use --reapply-cherry-picks to include skipped commits
问: 当我在feture分支写完功能,切换到dev更新了远端dev代码,切回feture分支,git rebase dev分支后出现报错: warning skipped previously applied commit 709xxxx hint:use --reapply-cherry-picks to include skippe…...
Microsoft Sql Server 2019 数据类型
数据类型 bigint、int、smallint、tinyint 使用整数数据的精确数字数据类型。 若要节省数据库空间,请使用能够可靠包含所有可能值的最小数 据类型。 例如,对于一个人的年龄,tinyint 就足够了,因为没人活到 255 岁以上。 但对于建筑物的 年龄,tinyint 就不再适应,因为建…...
C++实现设计模式---代理模式 (Proxy)
代理模式 (Proxy) 代理模式 是一种结构型设计模式,它为其他对象提供一个代理以控制对该对象的访问。代理模式常用于延迟加载、访问控制、智能引用等场景。 意图 提供对某对象的控制。控制对目标对象的访问,通常用于在不改变目标对象的情况下࿰…...
微信小程序用的SSL证书有什么要求吗?
微信小程序主要建立在手机端使用,然而手机又涉及到各种系统及版本,所以对SSL证书也有要求,如果要小程序可以安全有效的访问需要满足以下要求: 1、原厂SSL证书(原厂封)。 2、DV单域名或者DV通配符。 3、兼…...
Flutter中Get.snackbar和Get.dialog关闭冲突问题记录
背景: 在使用GetX框架时,同时使用了Get.snackbar提示框和Get.dialog加载框,当这两个widget同时存在时,Get.dialog加载框调用Get.back()无法正常关闭。 冲突解释: 之所以会产生冲突,是因为Get.snackbar在关…...
命令模式-Command Pattern
什么是命令模式 命令模式是一种行为类设计模式,核心是将每种请求或操作封装为一个独立的对象,从而可以集中管理这些请求或操作,比如将请求队列化依次执行、或者对操作进行记录和撤销。 命令模式通过将请求的发送者(客户端)和接收者(执行请求…...
【Linux笔记】Day1
基于韩顺平老师课程记录: https://www.bilibili.com/video/BV1Sv411r7vd 安装CentOS 给CentOS手动分区 分为三个区: boot分区(给1G就行) 交换分区(和内存相关,这里和虚拟机的内存2G一致) …...
如何明智地提问
如何明智地提问的重要总结,让我为主要观点添加一些具体的实践建议: 提问前的准备工作 尝试在 Google、Stack Overflow 等平台搜索相似问题阅读相关文档和错误日志尝试自己调试和排查问题记录下已尝试过的解决方案 选择合适的提问平台 Stack Overflow…...
【大前端】Vue3 工程化项目使用详解
目录 一、前言 二、前置准备 2.1 环境准备 2.1.1 create-vue功能 2.1.2 nodejs环境 2.1.3 配置nodejs的环境变量 2.1.4 更换安装包的源 三、工程化项目创建与启动过程 3.1 创建工程化项目 3.2 项目初始化 3.3 项目启动 3.4 核心文件说明 四、VUE两种不同的API风格 …...
window.print()预览时表格显示不全
问题描述:使用element的table组件,表格列宽为自适应,但使用window.print()方法预览的页面会直接按预览宽度截取表格内容进行展示,造成表格可能的显示不全问题 解决方法:添加如下样式 media print {::v-deep {// 表头…...
《解锁鸿蒙Next系统人工智能语音助手开发的关键步骤》
在当今数字化时代,鸿蒙Next系统与人工智能的融合为开发者带来了前所未有的机遇,开发一款人工智能语音助手应用更是备受关注。以下是在鸿蒙Next系统上开发人工智能语音助手应用的关键步骤: 环境搭建与权限申请 安装开发工具:首先需…...
计算机网络之---MAC协议
MAC协议的作用 在数据链路层中,MAC(媒介访问控制)协议负责控制设备如何访问共享的通信介质(如以太网、无线电波等),确保在多台设备共享同一传输媒介时能够有效地进行数据传输,避免冲突、控制流…...
系统思考—要素连接
“改变你的思维,就能改变你的世界”— 诺曼皮尔 世界上的所有事物,都在规律的支配下,以系统的方式运转。显性的部分是我们能看到的“要素”,而那些看不见的力量,正是推动系统运作的要素之间的相互作用。更隐秘的&…...
influxdb 采集node_exporter数据
一、打开Scrapers添加 node_exporter地址:http://192.168.31.135:9100/metrics 查看数据...
数据链路层-STP
生成树协议STP(Spanning Tree Protocol) 它的实现目标是:在包含有物理环路的网络中,构建出一个能够连通全网各节点的树型无环逻辑拓扑。 选举根交换机: 选举根端口: 选举指定端口: 端口名字&…...
学技术学英语:ELK是什么
📢📢📢: 先看关键单词,再看英文,最后看中文总结,再回头看一遍英文原文,效果更佳!! 关键词 aggregate 聚合 /ˈɡrɪɡeɪt/ analytics 分析学 /ˌnəˈl…...
快速实现一个快递物流管理系统:实时更新与状态追踪
物流管理是电商、仓储和配送等行业的重要组成部分。随着电子商务的快速发展,快递物流的高效管理和实时状态更新变得尤为关键。本文将演示如何使用Node.js、Express、MongoDB等技术快速构建一个简单的快递物流管理系统,该系统支持快递订单的实时更新和追踪…...
android分区和root
线刷包内容: 线刷包是一个完整的android镜像,不但包括android、linux和用户数据,还包括recovery等。当然此图中没有recovery,但是我们可以自己刷入一个。 主要分区 system.img 系统分区,包括linux下主要的二进制程序。 boot.img…...