OpenCV实战(11)——形态学变换详解

news2024/11/18 0:37:25

OpenCV实战(11)——形态学变换详解

    • 0. 前言
    • 1. 腐蚀和膨胀运算
      • 1.1 腐蚀和膨胀基础
      • 1.2 使用形态学滤波器执行图像腐蚀和膨胀运算
    • 2. 开运算和闭运算
      • 2.1 使用形态学滤波器执行图像开运算和闭运算
    • 3. 形态学变换应用
      • 3.1 使用形态学滤波器检测边缘
      • 3.2 使用形态学滤波器检测边缘和角点
    • 4. 完整代码
    • 小结
    • 系列链接

0. 前言

形态学变换( Morphological transformations )通常是在二值图像上执行、基于图像形状的操作。其具体的操作由核结构元素决定,它决定了操作的性质。膨胀和腐蚀是形态学变换领域的两个基本算子,此外,开运算和闭运算是两个重要的运算,它们可以通过上述两个运算(膨胀和腐蚀)获得。

1. 腐蚀和膨胀运算

1.1 腐蚀和膨胀基础

腐蚀 (Erosion) 和膨胀 (Dilation) 是最基本的形态学算子,因此,我们将首先介绍这两个基本算子。数学形态学的基本组成部分是结构元素,结构元素可以简单地理解为定义原点(也称为锚点)的像素配置,如下图中的正方形。应用形态学滤波器需要使用此结构元素检测图像的每个像素,当结构元素的原点与给定像素对齐时,它与图像的交集定义了一组像素,在这些像素(下图中的九个阴影像素)上应用了特定的形态学操作。原则上,结构元素可以是任何形状,但出于效率考虑,通常使用简单的形状,例如正方形、圆形或以原点为中心的菱形。

结构元素

1.2 使用形态学滤波器执行图像腐蚀和膨胀运算

由于形态学滤波器通常作用于二值图像,因此我们使用通过阈值化创建的二值图像。然而,由于在形态学中通常用白色像素值表示前景对象,用黑色像素值表示背景对象,因此我们需要对原二值图像求补(即用黑色像素值表示前景对象,用白色像素值表示背景对象)。在形态学中,下图可以认为是由阈值化创建的二值图像的补:

二值图像
与其他形态学滤波器一样,腐蚀和膨胀两个滤波器对由结构元素定义的每个像素周围的像素集(或邻域)进行操作,当应用于给定像素时,结构元素的锚点与该像素位置对齐,并且与结构元素相交的所有像素都包含在当前集合中。腐蚀使用在定义的像素集中找到的最小像素值替换当前像素。膨胀是腐蚀的互补算子,它使用定义的像素集中找到的最大像素值替换当前像素。由于输入二值图像仅包含黑色 (0) 和白色 (255) 像素,因此每个像素都被替换为白色或黑色像素。腐蚀和膨胀在 OpenCV 中分别使用 cv::erodecv::dilate 函数实现。

(1) 首先,读取二值图片:

// 读取二值图像
cv::Mat binary;
binary = cv::imread("binary.png", 0);

(2) 应用 cv::erode 函数:

// 腐蚀图像
cv::Mat eroded;
cv::erode(image, eroded, cv::Mat());

应用腐蚀形态学滤波器后,可以得到以下结果:

腐蚀
(3) 使用 cv::dilate 膨胀图像:

// 膨胀图像
cv::Mat dilated;
cv::dilate(image, dilated, cv::Mat());

应用膨胀形态学滤波器后,可以得到以下结果:

膨胀
接下来,我们从这两个运算符的执行效果进行理解,对于腐蚀算子,如果结构元素置于给定像素位置与背景相接触(即,相交集中的像素之一是黑色),则该像素将被赋值为黑色并归属至背景;在膨胀算子中,如果背景像素上的结构元素接触前景对象,则该像素将被赋值为白色值。
这就解释了为什么在腐蚀图像中物体的尺寸会减小(形状被腐蚀),而一些小物体由于可能被认为是嘈杂的背景像素会被完全消除;而膨胀运算后的物体将会变大,并且物体内部的一些孔洞会被填满。默认情况下,OpenCV 使用 3 x 3 方形结构元素,当调用函数时第三个参数指定为空矩阵(即 cv::Mat() )时,将使用此默认结构元素,我们可以通过使用非零元素定义结构元素矩阵,指定所需大小(和形状)的结构元素。例如,我们可以使用以下代码定义 7 x 7 结构元素:

cv::Mat element(7, 7, CV_8U, cv::Scalar(1));
cv::erode(image, eroded, element);

在这种情况下,效果更加明显,如下图所示:

腐蚀操作

我们可以简单的在图像上重复应用相同的结构元素,cv::erodecv::dilate 这两个函数都有一个可选参数来指定重复次数:

// 腐蚀同一图像三次
cv::erode(image, eroded, cv::Mat(), cv::Point(-1, -1), 3);

原点参数 cv::Point(-1,-1) 表示原点位于矩阵的中心(默认);原点也可以定义在结构元素的其他位置。使用以上代码得到的图像与我们使用 7 x 7 结构元素获得的图像相同。实际上,腐蚀图像两次类似于腐蚀结构元素尺寸扩大的图像,这一规律也适用于膨胀算子。
由于背景/前景的概念是相对的,用结构元素腐蚀前景对象可也以看作是图像背景部分的膨胀,这是腐蚀/膨胀算子的基本属性。换句话说:

  • 图像的腐蚀等价于互补图像膨胀的补
  • 图像的膨胀等价于互补图像腐蚀的补
    虽然我们在本节主要介绍了如何将形态学滤波器应用于二值图像,但这些滤波器同样也可以应用于灰度或彩色图像。
    OpenCV 形态学函数支持就地处理,即可以使用输入图像作为目标图像:
cv::erode(image,image,cv::Mat());

OpenCV 在函数内部创建所需的临时图像,以使其正常工作。

2. 开运算和闭运算

2.1 使用形态学滤波器执行图像开运算和闭运算

上一节中,我们介绍了两个基本的形态学算子——膨胀和腐蚀。利用这些基本算子,我们可以定义其他运算符,本节将介绍开运算和闭运算。为了应用高级形态学滤波器,需要使用 cv::morphologyEx 函数。

(1) 要创建闭运算或开运算符,必须创建一个 cv::Mat 元素用作开/闭运算核:

cv::Mat element5(5, 5, CV_8U, cv::Scalar(1));

(2) 创建另一个 cv::Mat 存储应用形态学运算符后的结果:

cv::Mat closed;
cv::Mat opened;

(3) 最后,应用闭或开运算符。为了创建闭运算符,我们使用 cv::MORPH_CLOSE 参数调用 cv::morphologyEx 函数:

cv::morphologyEx(image, closed, // 输入和输出图像
        cv::MORPH_CLOSE,        // 操作算子
        element5);              // 结构元素

如果我们使用二值图像作为输入,结果如下图所示:

闭运算

(4) 使用 cv::MORPH_OPEN 作为参数调用 cv::morphologyEx 函数,可以创建开运算:

cv::morphologyEx(image, opened, cv::MORPH_OPEN, element5);

应用形态学开运算可以得到以下图像:

开运算

(5) 开和闭滤波器是根据基本腐蚀和膨胀操作定义的,闭运算被定义为膨胀图像后腐蚀,开运算被定义为腐蚀图像后膨胀。因此,可以使用以下代码计算图像的闭:

// 1. 膨胀原始图像
cv::Mat result;
cv::dilate(image, result, element5);
// 2. 腐蚀膨胀后的图像
cv::erode(result, result, element5);

可以通过交换这两个函数的调用顺序得到开滤波器。在使用闭滤波器的结果图像中,可以看到白色前景对象中的小孔被填充,滤波器还会将几个相邻的对象连接在一起。实际上,任何无法完全包含结构元素的过小的孔或间隙都将被滤波器消除。相反的,开滤波器会消除图像中的小物体,所有无法包含结构元素的过小对象都会被消除。
开/闭滤波器通常用于对象检测,闭滤波器可以连接被错误分割成小块的对象,而开滤波器可以去除由图像噪声引入的小斑点。因此,根据具体应用可以按不同顺序使用它们。如果二值图像连续应用闭和开运算,将得到一个仅显示场景中主要对象的图像,如下图所示。如果我们希望优先过滤噪声,也可以在闭滤波器之前应用开滤波器,但这会消除一些碎片化对象:

在闭滤波器之前应用开滤波器
在图像上多次应用相同的开(或闭)运算符不会产生任何效果。实际上,由于孔已被第一个开滤波器填充,因此附加应用相同的滤波器不会对图像产生其他变化。在数学上,这些运算符被称为幂等运算符。

3. 形态学变换应用

形态学滤波器也可用于检测图像中的特定特征。在本节中,我们将学习如何检测灰度图像中的轮廓和角点。

3.1 使用形态学滤波器检测边缘

(1) 通过使用适当滤波器参数调用 cv::morphologyEx 函数提取要检测的图像的边缘:

// 使用 3x3 结构元素获取梯度图像
cv::Mat result;
cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());
// 使用阈值获取二值图像
int threshold(80);
cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY);

可以得到以下结果图像:

检测图像边缘

理解形态学算子对灰度图像的影响时,可以将图像视为拓扑浮雕,其中灰度与高程(或海拔)相对应。从这个角度而言,明亮的区域对应山脉,而黑暗的区域对应山谷。此外,由于边缘对应于暗像素和亮像素之间的快速过渡,因此可以将其理解为陡峭的悬崖。如果在这样的地形上应用腐蚀算子,最终将用邻域中的最低值替换每个像素,从而降低其高度。因此,随着山谷的扩大,悬崖将被腐蚀。膨胀具有完全相反的效果;也就是说,悬崖将膨胀而山谷相应的会缩小。但在这两种情况下,高原(即强度恒定的区域)均将保持相对不变。
基于此,我们可以得到一种检测图像边缘(悬崖)的简单方法,即计算膨胀和腐蚀图像之间的差。由于这两种变换后的得到的图像主要在与边缘位置不同,因此相减将得到图像边缘。我们可以使用 cv::MORPH_GRADIENT 参数调用 cv::morphologyEx 函数完成以上操作。显然,结构元素越大,检测到的边缘就越宽,这种边缘检测算子也称为 Beucher 梯度(之后的学习中将更详细地讨论图像梯度的概念)。通过简单地从膨胀图像中减去原始图像或从原始图像中减去图像图像,也可以获得类似的结果,但产生的边缘会更细。

3.2 使用形态学滤波器检测边缘和角点

(1) 为了使用形态学检测角点,我们可以定义 MorphoFeatures 类:

class MorphoFeatures {
    private:
        // 用于产生二值图像的阈值
        int threshold;
        // 用于角点检测的结构元素
        cv::Mat_<uchar> cross;
        cv::Mat_<uchar> diamond;
        cv::Mat_<uchar> square;
        cv::Mat_<uchar> x;

(2) 使用形态学角点检测角点需要连续应用几个不同的形态学滤波器,为此我们需要使用非方形结构元素,实际上,在构造函数中定义了四个不同的结构元素,形状分别为正方形、菱形、十字形和 X 形,为简单起见,这些结构元素尺寸均为 5 x 5

public:
    MorphoFeatures() : threshold(-1), cross(5, 5), diamond(5, 5),
                    square(5, 5), x(5, 5) {
        // 创建十字形结构元素
        cross <<    0, 0, 1, 0, 0,
                    0, 0, 1, 0, 0,
                    1, 1, 1, 1, 1,
                    0, 0, 1, 0, 0,
                    0, 0, 1, 0, 0;
        // 菱形结构元素
        diamond <<  0, 0, 1, 0, 0,
                    0, 1, 1, 1, 0,
                    1, 1, 1, 1, 1,
                    0, 1, 1, 1, 0,
                    0, 0, 1, 0, 0;
        // 方形结构元素
        square <<   1, 1, 1, 1, 1,
                    1, 1, 1, 1, 1,
                    1, 1, 1, 1, 1,
                    1, 1, 1, 1, 1,
                    1, 1, 1, 1, 1;
        // x形结构元素
        x <<        1, 0, 0, 0, 1,
                    0, 1, 0, 1, 0,
                    0, 0, 1, 0, 0,
                    0, 1, 0, 1, 0,
                    1, 0, 0, 0, 1;
                    }

(4) 在角点特征检测中,顺序应用这些结构元素以获得结果角点图:

// 角点检测
cv::Mat getCorners(const cv::Mat& image) {
    cv::Mat result;
    // 膨胀
    cv::dilate(image, result, cross);
    // 腐蚀
    cv::erode(result, result, diamond);
    cv::Mat result2;
    // 膨胀
    cv::dilate(image, result2, x);
    // 腐蚀
    cv::erode(result2, result2, square);
    // 角点
    cv::absdiff(result2, result, result);
    applyThreshold(result);
    return result;
}

(5) 在图像上检测角点:

MorphoFeatures morpho;
morpho.setThreshold(35);
// 角点检测
cv::Mat corners;
corners = morpho.getCorners(gray);
// 在图像中显示角点
morpho.drawOnImage(corners, gray);
cv::namedWindow("Corners on Image");
cv::imshow("Corners on Image", gray);

在图像中,检测到的角点显示为圆圈,如下图所示:

角点

角点检测相较更为复杂,因为它需要使用四种不同的结构元素。在 OpenCV 中并没有直接实现角点检测算子,但我们可以通过定义和组合不同形状的结构元素实现。通过用两种不同的结构元素来膨胀和腐蚀图像实现图像闭运算。使用这些元素令图像中直线保持不变,但不同结构元素也会影响角点处的边。以单个白色方块组成的简单图像为例介绍这种非对称闭操作的效果:

非对称操作

上图中,第一个方块是原始图像。当用十字形结构元素执行膨胀操作时,方块边缘会被扩展,除了十字形不碰到方形的角点,如上图中间的方块所示。然后,膨胀后的图像被菱形结构元素腐蚀,腐蚀操作会令大多数边缘恢复到原来的位置,但由于方形的角没有被膨胀,所以其会被进一步腐蚀,如上图中最右边的正方形所示,可以看到其角已经消失。使用 X 形和方形结构元素重复以上过程,由于 X 形是十字形结构元素的旋转版本,因此可以捕获 45 度方向的角。最后,对两个结果进行差分便可以提取角点特征。

4. 完整代码

头文件 (morphoFeatures.h) 完整代码如下:

#if !defined MFEATURES
#define MFEATURES

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

class MorphoFeatures {
    private:
        // 用于产生二值图像的阈值
        int threshold;
        // 用于角点检测的结构元素
        cv::Mat_<uchar> cross;
        cv::Mat_<uchar> diamond;
        cv::Mat_<uchar> square;
        cv::Mat_<uchar> x;
    public:
        MorphoFeatures() : threshold(-1), cross(5, 5), diamond(5, 5),
                        square(5, 5), x(5, 5) {
            // 创建十字形结构元素
            cross <<    0, 0, 1, 0, 0,
                        0, 0, 1, 0, 0,
                        1, 1, 1, 1, 1,
                        0, 0, 1, 0, 0,
                        0, 0, 1, 0, 0;
            // 菱形结构元素
            diamond <<  0, 0, 1, 0, 0,
                        0, 1, 1, 1, 0,
                        1, 1, 1, 1, 1,
                        0, 1, 1, 1, 0,
                        0, 0, 1, 0, 0;
            // 方形结构元素
            square <<   1, 1, 1, 1, 1,
                        1, 1, 1, 1, 1,
                        1, 1, 1, 1, 1,
                        1, 1, 1, 1, 1,
                        1, 1, 1, 1, 1;
            // x形结构元素
            x <<        1, 0, 0, 0, 1,
                        0, 1, 0, 1, 0,
                        0, 0, 1, 0, 0,
                        0, 1, 0, 1, 0,
                        1, 0, 0, 0, 1;
                        }
        void setThreshold(int t) {
            threshold = t;
        }
        int getThreshold() {
            return threshold;
        }
        void applyThreshold(cv::Mat& result) {
            if (threshold > 0) {
                cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY_INV);
            }
        }
        // 直线检测
        cv::Mat getEdges(const cv::Mat& image) {
            cv::Mat result;
            cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());
            applyThreshold(result);
            return result;
        }
        // 角点检测
        cv::Mat getCorners(const cv::Mat& image) {
            cv::Mat result;
            // 膨胀
            cv::dilate(image, result, cross);
            // 腐蚀
            cv::erode(result, result, diamond);
            cv::Mat result2;
            // 膨胀
            cv::dilate(image, result2, x);
            // 腐蚀
            cv::erode(result2, result2, square);
            // 角点
            cv::absdiff(result2, result, result);
            applyThreshold(result);
            return result;
        }
        void drawOnImage(const cv::Mat& binary, cv::Mat& image) {
            cv::Mat_<uchar>::const_iterator it = binary.begin<uchar>();
            cv::Mat_<uchar>::const_iterator itend = binary.end<uchar>();
            for (int i=0; it!=itend; ++it, ++i) {
                if (!*it) {
                    cv::circle(image, 
                            cv::Point(i%image.step, i/image.step),
                            5,
                            cv::Scalar(255, 0, 0));
                }
            }
        }
};

#endif

主文件 (morphology.cpp) 完整代码如下所示:

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int main() {
    // 读取图像
    cv::Mat image = cv::imread("binary.png");
    if (!image.data) return 0;
    cv::namedWindow("Image");
    cv::imshow("Image", image);
    // 腐蚀图像
    cv::Mat eroded;
    cv::erode(image, eroded, cv::Mat());
    cv::namedWindow("Eroded Image");
    cv::imshow("Eroded Image", eroded);
    // 膨胀图像
    cv::Mat dilated;
    cv::dilate(image, dilated, cv::Mat());
    cv::namedWindow("Dilated Image");
    cv::imshow("Dilated Image",dilated);
    // 使用一个较大的结构元素腐蚀图像
    cv::Mat element(7, 7, CV_8U, cv::Scalar(1));
    cv::erode(image, eroded, element);
    cv::namedWindow("Eroded Image (7x7)");
    cv::imshow("Eroded Image (7x7)",eroded);
    // 腐蚀同一图像三次
    cv::erode(image, eroded, cv::Mat(), cv::Point(-1, -1), 3);
    cv::namedWindow("Eroded Image (3 times)");
    cv::imshow("Eroded Image (3 times)",eroded);
    // 图像闭运算
    cv::Mat element5(5, 5, CV_8U, cv::Scalar(1));
    cv::Mat closed;
    cv::morphologyEx(image, closed, // 输入和输出图像
            cv::MORPH_CLOSE,        // 操作算子
            element5);              // 结构元素
    cv::namedWindow("Closed Image");
    cv::imshow("Closed Image",closed);
    // 图像开运算
    cv::Mat opened;
    cv::morphologyEx(image, opened, cv::MORPH_OPEN, element5);
    cv::namedWindow("Opened Image");
    cv::imshow("Opened Image",opened);
    // 闭运算分解
    // 1. 膨胀原始图像
    cv::Mat result;
    cv::dilate(image, result, element5);
    // 2. 腐蚀膨胀后的图像
    cv::erode(result, result, element5);
    cv::namedWindow("Closed Image (2)");
    cv::imshow("Closed Image (2)", result);
    // 闭-开
    cv::morphologyEx(image, image, cv::MORPH_CLOSE, element5);
    cv::morphologyEx(image, image, cv::MORPH_OPEN, element5);
    cv::namedWindow("Closed|Opened Image");
    cv::imshow("Closed|Opened Image", image);
    cv::imwrite("binaryGroup.png", image);
    // 开-闭
    image = cv::imread("binary.png");
    cv::morphologyEx(image, image, cv::MORPH_OPEN, element5);
    cv::morphologyEx(image, image, cv::MORPH_CLOSE, element5);
    cv::namedWindow("Opened|Closed Image");
    cv::imshow("Opened|Closed Image",image);
    // 读取输入图像
    image = cv::imread("1.png", 0);
    if (!image.data) return 0;
    // 使用 3x3 结构元素获取梯度图像
    cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());
    cv::namedWindow("Edge Image");
    cv::imshow("Edge Image", 255 - result);
    // 使用阈值获取二值图像
    int threshold(80);
    cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY);
    cv::namedWindow("Thresholded Edge Image");
    cv::imshow("Thresholded Edge Image", result);
    // 使用 3x3 结构元素获取梯度图像
    cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());
    // 读取输入图像
    image = cv::imread("10.png", 0);
    if (!image.data) return 0;
    cv::transpose(image, image);
    cv::flip(image, image, 0);
    // 使用 7x7 结构元素应用黑色顶帽变换 
    cv::Mat element7(7, 7, CV_8U, cv::Scalar(1));
    cv::morphologyEx(image, result, cv::MORPH_BLACKHAT, element7);
    cv::namedWindow("7x7 Black Top-hat Image");
    cv::imshow("7x7 Black Top-hat Image", 255-result);
    cv::waitKey();
    return 0;
}

主文件 (cornersDetection.cpp) 完整代码如下所示:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

#include "morphoFeatures.h"

int main() {
    cv::Mat image = cv::imread("3.png");
    cv::Mat gray;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
    cv::namedWindow("Image");
    cv::imshow("Image", gray);
    MorphoFeatures morpho;
    morpho.setThreshold(35);
    // 角点检测
    cv::Mat corners;
    corners = morpho.getCorners(gray);
    // 在图像中显示角点
    morpho.drawOnImage(corners, gray);
    cv::namedWindow("Corners on Image");
    cv::imshow("Corners on Image", gray);
    cv::waitKey();
    return 0;
}

小结

形态学变换( Morphological transformations )通常是在二值图像上执行、基于图像形状的操作。本节,首先介绍了基本形态学算子,腐蚀与膨胀,并介绍了根据基本算子组合得到的开运算与闭运算,最后,利用形态学算子实现了经典的边缘/角点检测图像处理应用。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解

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

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

相关文章

跨平台应用开发进阶(五十五):uni-app 实现内容分享

文章目录一、前言二、系统分享组件三、uniShare SDK调用四、拓展阅读一、前言 APP开发过程中&#xff0c;需要实现分享功能。 常用的分享实现方法包括&#xff1a; 系统分享组件&#xff1b;uniShare SDK调用&#xff1b; 二、系统分享组件 uni.shareWithSystem(OBJECT)调…

jvisualvm安装Visual GC插件以及连接远程应用监控jvm【杭州多测师_王sir】【杭州多测师】...

一)jvisualvm工具安装Visual GC插件 1、在本地jdk安装路径找到jvisualvm.exe双击打开 2、选择工具-插件-勾选visual GC 如果显示重试&#xff0c;先点击设置-编辑-选择你本地对应的JDK版本的URL&#xff1a;https://visualvm.github.io/pluginscenters.html 3、点击远程-添加远…

CSS系统学习总结

目录 CSS边框 CSS背景 CSS3渐变 线性渐变&#xff08;Linear Gradients&#xff09;- 向下/向上/向左/向右/对角方向 语法 线性渐变&#xff08;从上到下&#xff09; 线性渐变&#xff08;从左到右&#xff09; 线性渐变&#xff08;对角&#xff09; 使用角度 使用多…

博视像元获近5000万元融资,主攻半导体前道及锂电高端部件供应

这两年各大车企与电池厂商都在快速新建产能&#xff0c;尤其上游原材料成本大增&#xff0c;反映到产业链上巨头都在寻求增效&#xff0c;高端制造技术投入也大幅增长。比如这家&#xff0c;高端工业相机提供商「博视像元」近期宣布完成近5000万的天使加轮融资&#xff0c;投资…

指针——“C”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰学习的内容是指针&#xff0c;这次只会讲一些很简单的知识点&#xff0c;更详细的指针知识会在以后的博客中逐步剖析清楚&#xff0c;那么现在&#xff0c;就让我们进入指针的世界吧 指针是什么 指针和指针类型 野指…

Spring 如何解决循环依赖?

什么是循环依赖 &#xff1f; 一个或多个对象之间存在直接或间接的依赖关系&#xff0c;这种依赖关系构成一个环形调用&#xff0c;有下面 3 种方式。 我们看一个简单的 Demo&#xff0c;对标“情况 2”。 Service public class Louzai1 {Autowiredprivate Louzai2 louzai2;…

基于OpenAI搭建自己的ChatGPT环境1

基于OpenAI搭建自己的ChatGPT环境1基于OpenAI搭建自己的ChatGPT环境注册账号生成访问密钥创建虚拟环境安装openai模块环境体验笔者初次接触人工智能领域&#xff0c;文章中错误的地方还望各位大佬指正&#xff01; 基于OpenAI搭建自己的ChatGPT环境 ChatGPT是OpenAI研发的人机…

Java基础之网络编程介绍详尽笔记

目录初识网络编程网络传输模型网络传输协议UDPUDP通信程序UDP的三种通信方式TCPTCP通信协议TCP的三次握手TCP的四次挥手初识网络编程 网络编程三要素 IP 设备在网络中的地址&#xff0c;是唯一的标识。 端口号 应用程序在设备中唯一的标识。 协议 数据在网络中传输的规则&…

童年回忆--扫雷(包括标记功能和递归展开)--万字讲解让你学会扫雷制作

魔王的介绍&#xff1a;&#x1f636;‍&#x1f32b;️一名双非本科大一小白。魔王的目标&#xff1a;&#x1f92f;努力赶上周围卷王的脚步。魔王的主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王.&#x1f525;&#x1f525;&#x1f525; ❤️‍&#x1…

第九章:创建用户和用户权限

Windows&#xff1a;创建用户&#xff1a;第一种方法创建用户&#xff1a;先点右上角的工具&#xff0c;然后点击AD用户和计算机双击skills.com打开目录&#xff0c;再双击Users&#xff0c;进入文件夹中在右框中右击空白处&#xff0c;新建用户填充好用户信息后点击下一步然后…

Sophos防火墙日志管理

每天&#xff0c;Sophos防火墙都会生成大量的syslog数据&#xff0c;很难独自监控它们。借助EventLog Analyzer&#xff0c;您可以存档系统日志以满足合规性要求&#xff0c;并进行彻底的取证调查&#xff0c;以在发生任何问题&#xff08;例如网络入侵&#xff09;时获得宝贵的…

MySQL用户管理

文章目录MySQL用户管理用户用户信息创建用户修改用户密码删除用户数据库的权限MySQL中的权限给用户授权回收权限MySQL用户管理 与Linux操作系统类似&#xff0c;MySQL中也有超级用户和普通用户之分。如果一个用户只需要访问MySQL中的某一个数据库&#xff0c;甚至数据库中的某…

Unity 资源插件 Agents Navigation 3.1.1.unitypackage

Unity 插件 Agents Navigation 3.1.1.unitypackage 描述 这个软件包包括高性能、模块化和可扩展的代理导航。它是以 DOTS 为核心开发的&#xff0c;因此充分利用了 Unity 的最新技术栈&#xff0c;如 SIMD 数学、Jobs、Burst 编译器和 EntityComponentSystem。此外&#xff0c;…

【ASP.NET】原生JavaScript加Asp.net实现多图片上传

记录一下&#xff0c;Javascript加asp.net实现多文件上传的方法。首先看一下要实现的功能&#xff0c;图片比文字描述更直观。 一、前台代码 前台代码代码分为三个部分&#xff0c;一是HTML代码&#xff0c;二是Style样式代码&#xff0c;三是Javascript代码。 1.html代码 …

亿级高并发电商项目-- 实战篇 --万达商城项目 八(安装FastDFS、安装Nginx、文件服务模块、文件上传功能、商品功能与秒杀商品等功能)

专栏&#xff1a;高并发---分布式项目 &#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支…

C语言进阶——自定义类型:枚举、联合

&#x1f307;个人主页&#xff1a;_麦麦_ &#x1f4da;今日名言&#xff1a;如果不去遍历世界&#xff0c;我们就不知道什么是我们精神和情感的寄托&#xff0c;但我们一旦遍历了世界&#xff0c;却发现我们再也无法回到那美好的地方去了。当我们开始寻求&#xff0c;我们就已…

2023春招java面试题及答案

2023春招java面试题及答案总结1.以下Dubbo服务负载均衡策略中&#xff0c;哪一个策略的功能是相同参数的请求总是发到同一个提供者&#xff08;&#xff09;2.如下代码&#xff1a;请问编译运行的结果是什么&#xff1f;3.给出如下代码&#xff1a;请问编译运行的结果是什么&am…

英国访问学者邀请函范例

下面是知识人网访问学者老师分享的一个英国访问学者邀请函范例&#xff0c;邀请函不要复杂&#xff0c;提供签证官想看到的东西即可。Chen xxxDate of Birth: September 1th , 19xxSchool of Computer and InformationXXXX UniversityNo.X South RoadXXX city, XXX Province, 1…

1.Unity之Shader新手入门

Unity Shader着色器的基本概念如何使用Unity Shader着色器示例&#xff1a;如何使用Unity Shader着色器创建复杂的效果总结 什么是Unity中的Shader着色器&#xff1f; Shader着色器是用来控制物体外观的编程代码&#xff0c;它可以改变物体的颜色、纹理、光照、凹凸等&#xf…

智慧校园综合解决方案

在网络和信息技术的普及和国家教育信息化建设的推动下&#xff0c;以计算机网络为基础&#xff0c;以信息和知识资源的共享为手段&#xff0c;强调合作、分享、传承精神的网络化、数字化、智能化有机结合的新型教育、学习和研究的教育环境建设显得尤为重要。 智慧校园是利用信息…