使用 OpenCV 测量物体尺寸

使用 OpenCV 测量物体尺寸

你是否曾经遇到过这样的问题:想要知道计算器的精确尺寸,但手头又没有专业的测量工具?别担心,今天我们就来教大家一个简单又实用的方法,通过一张A4纸就能估算出计算器的宽度和高度,精确到毫米哦!

该算法的主要思想其实非常简单。请看下面图 1 中我们要处理的样本图像。

图 1.本教程中使用的示例图像。

本教程的目的是估算计算器的宽度和高度(以毫米为单位)。为此,我们需要一个已知尺寸的物体作为参考长度。这基本上就是我们使用白纸作为对象背景的原因。纸张尺寸为 A4,这意味着它的宽度和高度分别为 210 毫米和 297 毫米。然后,我们可以借助这些数字获得估计的计算器尺寸。

在开始之前,我们这篇文章分为几个章节:

  1. 导入模块和参数初始化

  2. 图像加载和预处理

  3. 寻找纸张轮廓

  4. 透视变换

  5. 寻找物体轮廓

  6. 边长计算

  7. 将所有内容放在一个函数中

现在,我们从第一个开始。

1. 导入模块及参数初始化

和其他 Python 项目一样,我要做的第一件事就是导入所有必需的模块。在本例中,我们的大部分工作将使用 和 来完成cv2numpymatplotlib仅用于显示图像。

# Codeblock 1``import cv2``import numpy as np``import matplotlib.pyplot as plt

由于模块已导入,我们将初始化一些参数,这些参数是未来计算所需的,您可以在下面的 Codeblock 2 中看到。变量SCALE主要用于我们不希望生成的图像太小。同时,和PAPER_W表示PAPER_H纸张宽度和高度(以毫米为单位)。

# Codeblock 2``SCALE = 3``PAPER_W = 210 * SCALE``PAPER_H = 297 * SCALE

就这样。第一章到此结束,因为这部分没有什么可说的了。

2.图像加载和预处理

之后,我们将创建一个名为和的函数load_image()show_image()我认为这两个函数的名称是不言自明的。它们的详细信息可以在 Codeblock 3 中看到。您可以在下面看到我定义了参数scale(小写),其中我的目的是使输入图像变得有点小,因为原始图像分辨率非常高。但是,从技术上讲,您也可以通过传递大于 1 的值来使其更大。

show_image()另一方面,该函数实现cv2.cvtColor() 了将颜色通道从 BGR 转换为 RGB 的功能。这种转换是必要的,因为 Matplotlib 在颜色通道顺序方面与 OpenCV 的工作方式不同。

# Codeblock 3``def load_image(path, scale=0.7): img = cv2.imread(path) img_resized = cv2.resize(img, (0,0), None, scale, scale)``return img_resized`` ``def show_image(img): img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.figure(figsize=(6,8)) plt.xticks([]) plt.yticks([]) plt.imshow(img) plt.show()

由于上面的两个函数已经初始化,现在我们可以使用它来实际加载图像。这里我决定将参数保留scale为其默认值(0.7),这将导致加载的图像大小为 1120×840 px(原始大小为 1600×1200 px)。

# Codeblock 4``img_original = load_image(path='images/1.jpeg')``show_image(img_original)``print(img_original.shape)

图 2. 要处理的图像尺寸为 1120 x 840 像素。

我上面显示的图像存储在 中img_original。接下来要做的步骤是使用一系列图像处理技术对该图像进行预处理,即灰度转换(#1)、模糊(#2)、Canny 边缘检测(#3)、扩张(#5)和闭合(#6)。所有这些步骤都包含在preprocess_image()Codeblock 5 中的函数中。

# Codeblock 5``def preprocess_image(img, thresh_1=57, thresh_2=232): img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #1 img_blur = cv2.GaussianBlur(img_gray, (5,5), 1) #2 img_canny = cv2.Canny(img_blur, thresh_1, thresh_2) #3`` kernel = np.ones((3,3)) #4 img_dilated = cv2.dilate(img_canny, kernel, iterations=1) #5 img_closed = cv2.morphologyEx(img_dilated, cv2.MORPH_CLOSE,`` kernel, iterations=4) #6`` img_preprocessed = img_closed.copy()`` img_each_step = {'img_dilated': img_dilated, 'img_canny' : img_canny, 'img_blur' : img_blur, 'img_gray' : img_gray}`` ``return img_preprocessed, img_each_step

在标记为 的行中,和#2的参数分别表示高斯滤波器大小和高斯分布标准差。同时,行中的和是 Canny 边缘检测器用来捕获弱边缘和强边缘的两个阈值。在本例中,我决定将 设置为 57 和232,这些数字是通过反复试验确定的。接下来,我们初始化一个 3×3 内核,其中内核的所有元素都设置为 1(标记为 的行)。然后,这个全 1 内核将在扩张和闭合过程中用作结构元素。(5,5)``1``thresh_1``thresh_2``#3``thresh_1``thresh_2``#4

preprocess_image()函数返回两个值:完全预处理的图像(存储在 中img_preprocessed)和每个预处理阶段的结果(img_each_step以字典形式存储在 中)。该函数的输出如图 3 所示。要进入下一个过程的图像是img_preprocessed(最右边)。

# Codeblock 6img_preprocessed, img_each_step = preprocess_image(img_original)show_image(img_each_step['img_gray'])show_image(img_each_step['img_blur'])show_image(img_each_step['img_canny'])show_image(img_each_step['img_dilated'])show_image(img_preprocessed)

图 3. 从左到右的图像转换序列:灰度、模糊、边缘检测、扩张和闭合。

3. 寻找纸张轮廓

获取预处理后的图像后,接下来要做的是使用 Codeblock 7 中显示的代码查找轮廓。您可以在那里看到我们使用cv2.findContours()( ) 来执行此操作#1。我们还将其cv2.RETR_EXTERNAL作为参数的输入参数传递,因为我们只对捕获图像中检测到的最外层轮廓(即纸张)感兴趣。现在,计算器对象将被忽略。然后,将使用( )mode绘制检测到的轮廓本身。cv2.drawContours()``#2

# Codeblock 7def find_contours(img_preprocessed, img_original, epsilon_param=0.04):contours, hierarchy = cv2.findContours(image=img_preprocessed, mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_NONE)  #1img_contour = img_original.copy()cv2.drawContours(img_contour, contours, -1, (203,192,255), 6)  #2polygons = []for contour in contours:epsilon = epsilon_param * cv2.arcLength(curve=contour, closed=True)  #3polygon = cv2.approxPolyDP(curve=contour, epsilon=epsilon, closed=True)  #4polygon = polygon.reshape(4, 2)  #5polygons.append(polygon)for point in polygon:    img_contour = cv2.circle(img=img_contour, center=point, radius=8, color=(0,240,0), thickness=-1)  #6return polygons, img_contour不仅如此,这里我还尝试使用cv2.approxPolyDP()(#4)来近似轮廓形状。这行代码的目的是获取纸张轮廓的四个角,然后将坐标存储在其中polygon。我们需要注意的一件事是epsilon行中使用的参数#3。较小的 epsilon 值往往会导致检测到更多的角,而另一方面,较大的 epsilon 会导致函数cv2.approxPolyDP()捕获多边形的一般形状,即较少的角。注意 epsilon 值很重要,因为我们需要确保函数将准确捕获四个角点。

需要执行数组重塑 ( #5),因为 的原始输出cv2.approxPolyDP()格式为(number of corners, 1, 2),其中中间轴在我们的例子中无关紧要。因此,我们可以放心地将其丢弃。除了函数之外find_contours(),我们还将使用cv2.circle()( #6) 显示角。然后,此函数将返回角坐标本身 ( polygon) 和具有突出显示轮廓的图像 ( img_contour)。

下面的代码块 8 显示了我如何调用该find_contours()函数。在这里我决定将设置epsilon_param为 0.04。如果您使用自己的图片,则可能需要更改此值,特别是如果光照特性与我的图像不同。下面代码块的输出(显示在图 4 中)显示了轮廓和角落的样子。我也打印了存储在中的坐标polygon[0]。如果您想知道,使用索引器 0 是因为该find_contours()函数本质上能够捕获多个轮廓,而在这种情况下,我们唯一的外部轮廓是纸张本身。除此之外,可能值得注意的是,存储在中的坐标polygons(x,y)格式,而不是(y,x)

# Codeblock 8polygons, img_contours = find_contours(img_preprocessed, img_original,epsilon_param=0.04)show_image(img_contours)polygons[0]

图 4. 轮廓(粉色)和角落(绿色)的样子。

4.透视变换

由于已经检测到纸张角坐标,现在我们要根据这四个点来扭曲图像。这个过程的目的是在我们开始寻找物体轮廓之前将纸张拉直。

重新排序坐标

说到拉直过程,我们需要知道的一件事是,由于函数的性质,检测到的多边形角可能没有排序cv2.approxPolyDP()。为了使这四个点都有序,我们需要创建一个专门的函数来执行此操作。我正在谈论的函数称为,reorder_coords()其详细信息可在 Codeblock 9 中看到。

# Codeblock 9``def reorder_coords(polygon): rect_coords = np.zeros((4, 2))`` ``add = polygon.sum(axis=1) rect_coords[0] = polygon[np.argmin(add)] # Top left rect_coords[3] = polygon[np.argmax(add)] # Bottom right`` subtract = np.diff(polygon, axis=1) rect_coords[1] = polygon[np.argmin(subtract)] # Top right rect_coords[2] = polygon[np.argmax(subtract)] # Bottom left`` ``return rect_coords

上述函数的工作原理是将两个坐标数字相加和相减,得到 argmin 和 argmax。这些代码行神奇地将左上角、右上角、左下角和右下角分别放在索引 0、1、2 和 3 处。然后,我们可以将此函数应用于存储在中的点,polygon[0]如 Codeblock 10 所示。

# Codeblock 10``rect_coords = np.float32(reorder_coords(polygons[0]))``rect_coords

图 5. 我们使用reorder_coords()函数排序的纸张的四个角点。

稍后,上方重新排序的纸张角坐标(rect_coords)将作为变换的源,而变换目标(paper_coords)由实际纸张大小决定,即PAPER_WPAPER_H。查看 Codeblock 11 以了解我如何手动排列 存储的点。这里要记住的一件事是,目标坐标的顺序需要与源坐标的顺序完全匹配,这就是我们实现重新排序源坐标的功能paper_coords的原因。reorder_coords()

# Codeblock 11``paper_coords = np.float32([[0,0], # Top left [PAPER_W,0], # Top right [0,PAPER_H], # Bottom left [PAPER_W,PAPER_H]]) # Bottom right``paper_coords

图 6.用于图像转换的目标坐标。

除了代码块 10 和 11 之外,您可能还注意到我将数组数据类型转换为 float32。事实上,我尝试使用其他数据类型,例如 int32 和 float64,结果发现这两种数据类型都导致cv2.getPerspectiveTransform()后续代码块中的函数返回错误。

创建变换矩阵

此时,我们已经得到了rect_coordspaper_coords。我们现在可以使用它们来扭曲原始图像,使用cv2.getPerspectiveTransform()和,cv2.warpPerspective()如下面的代码块 12 所示。为了让代码更简洁,我将它们放在另一个名为的函数中warp_image()

# Codeblock 12``def warp_image(rect_coords, paper_coords, img_original, pad=5):`` ``matrix = cv2.getPerspectiveTransform(src=rect_coords, dst=paper_coords) #1``img_warped = cv2.warpPerspective(img_original, matrix,``(PAPER_W, PAPER_H)) #2`` ``warped_h = img_warped.shape[0]``warped_w = img_warped.shape[1]``img_warped = img_warped[pad:warped_h-pad, pad:warped_w-pad] #3`` ``return img_warped

让我们深入研究上述函数。我们首先使用cv2.getPerspectiveTransform()( #1) 创建所谓的变换矩阵。变换矩阵本身的尺寸为 3 × 3,它存储了有关如何将图像从一个角度变换到另一个角度的信息。然后,该矩阵将实际用于使用cv2.warpPerspective()( #2) 变换原始图像。然后,我们将变换后的图像存储在名为 的变量中img_warped。除了函数之外warp_image(),我们还将丢弃图像边界附近的区域 ( #3),因为生成的图像可能在边缘处包含一个狭窄的不需要的黑色区域。

函数的使用warp_image()在 Codeblock 13 中演示,其中输出显示在图 7 中。

# Codeblock 13``img_warped = warp_image(rect_coords, paper_coords, img_original)``show_image(img_warped)``print(img_warped.shape)

图 7.扭曲的图像。

5. 查找物体轮廓

由于原始图像已根据纸张形状进行了扭曲,接下来我将执行完全相同的过程以检测计算器对象,即图像预处理和轮廓搜索。由于要执行的过程与之前的过程相同,因此我们可以简单地重用我们之前定义的函数。在这种情况下,我们的内部轮廓(即计算器按钮)将被忽略,这要归功于cv2.RETR_EXTERNAL我们在 Codeblock 7 中实现的轮廓。

现在,Codeblock 14 和 15 展示了如何进行预处理和轮廓查找。

# Codeblock 14``img_warped_preprocessed, _ = preprocess_image(img_warped)``show_image(img_warped_preprocessed)

图 8.已预处理的扭曲图像。

# Codeblock 15``polygons_warped, img_contours_warped = find_contours(img_warped_preprocessed, img_warped,``epsilon_param=0.04)``show_image(img_contours_warped)``polygons_warped[0]

图 9. 检测到的轮廓(粉色)和角落(绿色)。

由于上面两个Codeblocks中的代码都已经运行完毕,现在所有角点坐标(绿色圆圈)都存储在 中polygons_warped,这四个坐标值其实就是用来估算边长的坐标值。

6. 边长计算

在这种情况下,我们假设纸张上方的所有物体都是矩形。因此,可以通过计算物体左上角和左下角之间的欧几里得距离来估计物体的高度(Codeblock 16 #2)。同时,可以通过计算物体左上角和右上角之间的相同距离度量来获得宽度(#3)。此外,我们需要记住,返回的检测到的角find_contours()仍然是无序的。因此,我们需要reorder_coords()事先调用( )。我想在 Codeblock 16 中强调的最后一件事是,估计的高度和宽度现在存储在按相应顺序#1命名的数组中( )sizes``#4

# Codeblock 16``def calculate_sizes(polygons_warped):`` ``rect_coords_list = []``for polygon in polygons_warped:``rect_coords = np.float32(reorder_coords(polygon)) #1``rect_coords_list.append(rect_coords)`` ``heights = []``widths = []``for rect_coords in rect_coords_list:``height = cv2.norm(rect_coords[0], rect_coords[2], cv2.NORM_L2) #2``width = cv2.norm(rect_coords[0], rect_coords[1], cv2.NORM_L2) #3`` ``heights.append(height)``widths.append(width)`` ``heights = np.array(heights).reshape(-1,1)``widths = np.array(widths).reshape(-1,1)`` ``sizes = np.hstack((heights, widths)) #4`` ``return sizes, rect_coords_list`` ``sizes, rect_coords_list = calculate_sizes(polygons_warped)``sizes``

图 10. 计算器的高度(索引 0)和宽度(索引 1)(以像素为单位)。

转换为毫米

但是,我们需要记住,图 10 中的输出仍然是像素数。要将这些值转换为毫米,我们需要为此创建一个单独的函数,我将其命名为convert_to_mm()(Codeblock 17)。此函数通过取纸张长度(以毫米为单位)和像素(以像素为单位)的比率来工作。然后,我们可以通过将比率值(和)与仍以像素为单位的计算器大小(和#1#2相乘来获得实际的毫米长度。scale_h``scale_w``#3``#4

# Codeblock 17``def convert_to_mm(sizes_pixel, img_warped):``warped_h = img_warped.shape[0]``warped_w = img_warped.shape[1]`` ``scale_h = PAPER_H / warped_h #1``scale_w = PAPER_W / warped_w #2`` ``sizes_mm = []`` ``for size_pixel_h, size_pixel_w in sizes_pixel:``size_mm_h = size_pixel_h * scale_h / SCALE #3``size_mm_w = size_pixel_w * scale_w / SCALE #4`` ``sizes_mm.append([size_mm_h, size_mm_w])`` ``return np.array(sizes_mm)`` ``sizes_mm = convert_to_mm(sizes, img_warped)``sizes_mm

图 11. 已转换为毫米的计算器高度和宽度。

到目前为止,我们已经成功获得了边缘的长度(以毫米为单位)。现在,我们将以文本的形式显示这些值,这些文本写在扭曲的图像上。为此完成的整个过程都放在函数内部write_size()。初始化函数后,我们可以直接调用它,如#1代码块 18 中所示。此代码的输出如图 12 所示。

看起来我们的代码如预期那样工作了。

# Codeblock 18``def write_size(rect_coords_list, sizes, img_warped):`` img_result = img_warped.copy()`` ``for rect_coord, size in zip(rect_coords_list, sizes):`` top_left = rect_coord[0].astype(int) top_right = rect_coord[1].astype(int) bottom_left = rect_coord[2].astype(int)`` cv2.line(img_result, top_left, top_right, (255,100,50), 4) cv2.line(img_result, top_left, bottom_left, (100,50,255), 4)`` cv2.putText(img_result, f'{np.int32(size[0])} mm',`` (bottom_left[0]-20, bottom_left[1]+50), `` cv2.FONT_HERSHEY_DUPLEX, 1, (100,50,255), 1)`` cv2.putText(img_result, f'{np.int32(size[1])} mm',`` (top_right[0]+20, top_right[1]+20), `` cv2.FONT_HERSHEY_DUPLEX, 1, (255,100,50), 1)`` ``return img_result`` ``img_result = write_size(rect_coords_list, sizes_mm, img_warped) #1``show_image(img_result)``print(img_result.shape)

图 12. 在图像上添加文字。

7. 将所有内容放在一个函数中

由于我试图解释此项目中代码使用的每个部分,因此我们上述执行的所有步骤似乎很长。事实上,到目前为止您看到的所有代码都可以包装在一个函数中,我measure_size()在 Codeblock 19 中将其命名为该函数。使用此函数,我们可以简单地将要处理的图像与参数一起传递,以完成我们刚刚完成的所有工作。

# Codeblock 19``def measure_size(path, img_original_scale=0.7,``PAPER_W=210, PAPER_H=297, SCALE=3, paper_eps_param=0.04, objects_eps_param=0.05, canny_thresh_1=57, canny_thresh_2=232):`` ``PAPER_W = PAPER_W * SCALE``PAPER_H = PAPER_H * SCALE`` # Loading and preprocessing original image.``img_original = load_image(path=path, scale=img_original_scale)``img_preprocessed, img_each_step = preprocess_image(img_original, thresh_1=canny_thresh_1, thresh_2=canny_thresh_2)`` # Finding paper contours and corners.``polygons, img_contours = find_contours(img_preprocessed, img_original, epsilon_param=paper_eps_param)`` # Reordering paper corners.``rect_coords = np.float32(reorder_coords(polygons[0]))`` # Warping image according to paper contours.``paper_coords = np.float32([[0,0], [PAPER_W,0], [0,PAPER_H],``[PAPER_W,PAPER_H]])``img_warped = warp_image(rect_coords, paper_coords, img_original)`` # Preprocessing the warped image.``img_warped_preprocessed, _ = preprocess_image(img_warped)`` # Finding contour in the warped image.``polygons_warped, img_contours_warped = find_contours(img_warped_preprocessed, img_warped,``epsilon_param=objects_eps_param)`` # Edge langth calculation.``sizes, rect_coords_list = calculate_sizes(polygons_warped)``sizes_mm = convert_to_mm(sizes, img_warped)``img_result = write_size(rect_coords_list, sizes_mm, img_warped)`` ``return img_result

一旦measure_size()创建了函数,我们现在就可以在几个测试用例上测试它。图 13 所示的输出表明,我们估算物体大小的方法对于不同的矩形物体非常有效。

# Codeblock 20``show_image(measure_size('images/1.jpeg'))``show_image(measure_size('images/2.jpeg'))``show_image(measure_size('images/3.jpeg'))``show_image(measure_size('images/4.jpeg'))``show_image(measure_size('images/5.jpeg'))``show_image(measure_size('images/6.jpeg', objects_eps_param=0.1))

图 13.其他图像上的结果。

一些局限

尽管在许多情况下我们的方法都运行正常,但这并不一定意味着我们的工作是完美的,即使在受控环境中也是如此。如果你仔细观察,你可能会注意到大多数预测的角点实际上并不位于实际的角点位置,这可能会导致测量结果有点不准确。这主要是因为角点的确定高度依赖于二值图像(即闭运算后的图像)的质量。这意味着在这种情况下输入参数值的确定非常关键。

此外,如果我们查看图 13 中从右侧开始的第二张图像,您会发现对象尺寸没有打印出来。这可能是因为由于对象和纸张之间的细微颜色差异,轮廓检测不太好。此外,在我们的最后一张测试图像(最右边的一张)中,尽管对象的整体外观呈矩形,但我们的实现似乎难以准确测量具有弧形角的对象的尺寸。

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

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

相关文章

iZotope RX 10 音频修复和增强工具 mac/win

iZotope RX 10 for Mac是一款出色的音频修复和增强工具,凭借其卓越的音频处理技术,能够轻松应对各种音频问题。 无论是背景噪音、回声还是失真,RX 10都能精准去除,还原清晰纯净的音频。同时,它还提供了丰富的增强工具&…

【Linux系统编程】第六弹---权限的概念

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、什么是权限 2、权限的本质 3、Linux中的用户 4、Linux中文件的权限 4.1、文件访问者的分类(角色) 4.2、文件类型和访问权…

Python 全栈安全(三)

原文:annas-archive.org/md5/712ab41a4ed6036d0e8214d788514d6b 译者:飞龙 协议:CC BY-NC-SA 4.0 第十一章:OAuth 2 本章内容 注册 OAuth 客户端 请求对受保护资源的授权 授权而不暴露身份验证凭据 访问受保护的资源 OAuth …

人脑是怎么防止梯度消失和梯度爆炸的

人脑是怎么防止梯度消失和梯度爆炸的? 神经网络,也称为人工神经网络 (ANN) 或模拟神经网络 (SNN),是机器学习的子集,并且是深度学习算法的核心。其名称和结构是受人类大脑的启发,模仿了生物神经元信号相互传递的方式。 感觉这个问…

亚马逊---设计安全架构

会从以下三个方面展开: 1、AWS资源访问安全 2、应用程序负载的网络安全 3、云中数据的安全 责任共担模式 就像租房子(房东和你的责任) AWS资源访问安全 需要掌握以下几点: 1、跨多个账户的访问控制和管理 2、AWS联合访问和身份服…

SpringMVC--RESTful

1. RESTful 1.1. RESTful简介 REST:Representational State Transfer,表现层资源状态转移。 RESTful是一种网络架构风格,它定义了如何通过网络进行数据的交互。这种风格基于HTTP协议,使得网络应用之间的通信变得更加简洁和高效。…

力扣283. 移动零

Problem: 283. 移动零 文章目录 题目描述思路复杂度Code 题目描述 思路 1.定义一个int类型变量index初始化为0; 2.遍历nums当当前的元素nums[i]不为0时使nums[i]赋值给nums[index]; 3.从index开始将nums中置对应位置的元素设为0; 复杂度 时间…

Java 异常处理详解

Java异常是Java编程语言中用于表示程序运行时错误的一种机制。Java异常体系通过异常类和异常处理来实现,允许程序在遇到预期或意外情况时,优雅地处理问题,而不是立即终止程序运行。 异常类层次结构 Java异常类都继承自java.lang.Throwable类…

python爬虫--------requests案列(二十七天)

兄弟姐们,大家好哇!我是喔的嘛呀。今天我们一起来学习requests案列。 一、requests____cookie登录古诗文网 1、首先想要模拟登录,就必须要获取登录表单数据 登录完之后点f12,然后点击network,最上面那个就是登录接口…

Spring AI Summary

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl Spring AI is a project that aims to streamline the development of AI applications by providing abstractions and reusable components that can be easily integrate…

【软考---系统架构设计师】软件架构

目录 1 一、软件架构的概念 二、软件架构风格 (1)数据流风格​​​​​​​ (2)调用/返回风格 (3)独立构件风格 (4)虚拟机风格 (5)仓库风格 三、架构…

labview中循环停止事件的深入研究

1.错误用法 第一次值事件运行的时候空白按钮给的F值,第二次值事件运行的时候空白按钮给的T值,这时循环才真正结束。 2.正确用法之一 赋值和值改变事件从同时进行变成按顺序执行。 3.正确用法之二 值事件发生以后超时事件将T值赋值给结束条件&#xff…

Linux环境变量深度解析

文章目录 一、引言二、环境变量的基本概念1、环境变量的定义2、环境变量的作用与意义 三、环境变量的导入1、导入所需文件2、登陆时的导入 四、环境变量的设置方法1、查看环境变量的方式2、使用export命令临时设置环境变量3、修改配置文件以永久设置环境变量 五、命令行参数与环…

免费听音乐,下载音乐mp3,mp4,歌词的网站分享(2024-04-22)

亲测!!! 1、音乐客 免费听和免费下载 经典老歌 - 音乐客音乐客,yinyueke.net,免费音乐,免费在线音乐播放器,免费下载音乐,音乐,播放器,下载,播放,DJ,免费,mp3,高音质,…

LinkedList和链表

1.ArrayList的缺陷 ArraryList由于底层是一段连续的空间,所以在ArrayList任意位置插入或者删除元素时,就 需要将后续元素往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较…

pytest教程-27-分布式执行用例插件-pytest-xdist

上一小节我们学习了pytest随机执行用例插件-pytest-random-order,本小节我们讲解一下pytest分布式执行用例插件pytest-xdist。 前言 平常我们手工测试用例非常多时,比如有1千条用例,假设每个用例执行需要1分钟。如果一个测试人员执行需要10…

C++的初步知识——命名空间,缺省参数,重载函数

C 首先写一段代码&#xff1a; #include <stdio.h>int main() {printf("Hello world\n");return 0; }这段C语言代码在cpp文件中仍可运行。我们了解C是兼容C语言的&#xff0c;C的关键字中就包含了C语言的关键字和自身的关键字。关于关键字&#xff0c;我们简…

PTA L2-052 吉利矩阵

题目 解析 这题考的是搜索剪枝 可行性剪枝&#xff1a; 即判断当前行&#xff08;列&#xff09;是否已经超过L和剩下的格子都填最大值是否小于L&#xff0c;若是则剪枝。 当前行数大于1时&#xff0c;判断上一个填完的行是否等于L&#xff0c;若否&#xff0c;则剪枝。 当前行…

浏览器数据找回

网站上分享的文章应该都是个人的心血&#xff0c;对于一些操作问题导致心血丢失真的很奔溃&#xff0c;终于找到一个弥补的办法&#xff0c;csdn的文章谷歌浏览器亲测有效&#xff0c;理论上其他浏览器的其他网站应该也可以&#xff0c;适用以下场景 把博客编辑当成了编写新博…

MATLAB中gurobi 运行报错与调试

问题背景如下&#xff1a;刚拿到一份MATLAB的代码&#xff0c;但是电脑第一次安装gurobi&#xff0c;在运行过程中发生了报错&#xff0c;使用断点进行调试和步进调试方法&#xff0c;最终发现&#xff0c;这个问题出在了哪一步&#xff0c;然后向了人工智能和CSDN、百度寻求答…