当前位置: 首页 > news >正文

【学习笔记】车道线识别——图像处理方法

一、图像基本知识

1. HLS:色相,亮度,饱和度

色相通道:确定颜色

亮度通道:亮度信息

饱和度通道:饱和度信息对于颜色区分鲜艳程度很关键。

二、视频读取示例

import cv2 if __name__ == '__main__': video = cv2.VideoCapture("./img/img/up.mp4") # 读取帧率 fps = video.get(cv2.CAP_PROP_FPS) # success代表是否成功读取,frame是视频的第一帧, success, frame = video.read() # 如果第一帧读取成功 while success: cv2.imshow('frame', frame) success, frame = video.read() # 视频第一帧之后的每一帧,每一次success, frame = video.read()只读一帧,这里循环调用成了每一帧 # 按键退出 # 按下空格关闭视频 if cv2.waitKey(1) == ord( ' '): # cv2.waitKey(1) 函数会等待1毫秒,检查是否有按键被按下。ord(' ') 是一个Python内置函数,它返回字符的ASCII码值。在这个例子中,ord(' ') 返回空格键的ASCII码值,即32 break # 1000ms = 1s 1000/fps 计算每一帧的时间 cv2.waitKey(int(1000 / int(fps))) # 防止出现非整数 print(int(1000 / int(fps))) "按下空格退出视频很慢,增加释放资源功能提高程序运行效率" # 释放资源 video.release() # 关闭所有窗口 cv2.destroyAllWindows()

2、RGB:红、绿、蓝

三、图像边缘提取:

1. sobel算子

本质上是卷积,在图像处理领域梯度不是传统意义上的梯度,只是用卷积算出了变化幅度,本质上类似于梯度

1.1 卷积核

Gx: [-1 0 1,-2 0 2,-1 0 1]Gy: [-1 -2 -1,0 0 0,1 2 1]


之所以选择这个卷积核,是因为当出现边缘时,左右或者上下的差值大,-的或者+的一边会大于另一边,绝对值就会大,如果与卷积核进行卷积的地方数值差不多最后结果则接近于0。这样便能区分出非边缘区域和边缘区域
有2的原因时因为sobel认为离得近的像素点更重要,即类似于加权

1.2 Sobel算子的工作原理:

它使用两个卷积内核,一个用于计算x方向上的梯度,另一个用于计算y方向上的梯度。
每个内核都考虑了像素及其邻域的值,并根据这些值计算梯度。
通过将这两个梯度分量结合起来(通常使用平方和的平方根),我们可以得到梯度的幅值,它表示了图像中每个像素点处的强度变化速率。

1.3 opencv的sobel函数参数

由于sobel计算得到的边缘信息可能存在负数(通过导数计算得到的梯度信息)
超出图像像素范围 0-255 的无符号数的8位整数类型
需要用
绝对值的形式来取值,以便后续操作

sobel_x参数:
1.src:类型numpy.ndarray,图像的输入, 必须是灰度图、单通道


2.ddepth:类型int,输出图像的深度(数据类型)指定了输出图像的位深度。
常用的值:-1,输出图像与输入图像深度相同。
其他值:cv2.cv_8U8位无符号整数、cv2.cv_16S16位有符号数、cv2.cv_32F32位浮点数cv2.cv_64F64位浮点数。选择合适的输出图像深度对于计算结果的精度和表示的范围有影响。


3.dx、dy:类型int,某个方向的阶数, 表示图像在x、y轴的求导次数
1表示计算y、x方向的一阶导数0不计算x, y的导数。大于1表示更高阶的导数,极少用


4.ksize:类型int,sobel算子的大小, 通常取奇数, 表示计算梯度时所使用的卷积核大小, 常见的大小有3 * 35 * 57 * 7ksize越大, 算子对图像的平滑效果越强, 但是可能会丢失细节。


5.scale:类型float,可选的缩放因子, 默认值1,计算梯度结果比例缩放


6.delta:类型folat,可选的偏移量, m默认值0, 在计算梯度时,.通常用于调整最终图像中的亮度或者对比度


7.borderType:类型int,边缘像素处理方式, 邻域参数决定了怎么处理图像的边界
cv2.BORDER_CONSTANT使用常数值来填充边界外的像素
cv2.BORDER_REFLECT边界外的像素值的镜像反射。
cv2.BORDER_FEPLTCATE边界外的像素值的镜像复制。
CV2.BORDER_DEFALUT默认方式填充边界:

使用实例:

sobel_x = cv2.Sobel(channels_l, -1, 1, 0) sobel_x_2 = cv2.Sobel(channels_l, -1, 1, 0, borderType=cv2.BORDER_REPLICATE) # sobel_x 变量将存储所有检测的边缘信息, # -1表示输出图像的数据类型与输入图像的数据类型保持一致 # 1 表示 在x轴的方向求一阶导数(检测横向边缘) # 0 在y轴的方向不求导数,(不用纵向检测) """ 由于sobel计算得到的边缘信息可能存在负数(通过导数计算得到的梯度信息) 而图像像素范围 0-255 的无符号数的8位整数类型 一定要用绝对值的形式来取值,以便后续操作 """ abs_sobel_x = np.absolute(sobel_x) abs_sobel_x_2 = np.absolute(sobel_x_2) print(abs_sobel_x.shape) print(abs_sobel_x_2.shape)

2. 归一化和二值化

2.1 归一化:

2.2 二值化:

忽略不重要的地方,单独取出车道线,事实上就是取出轮廓最明显(梯度最大)的

使用实例:

""" # 使用比较高效的布尔索引进行二值化 # 将170作为下限阈值,255为上限阈值,基于车道线在图像中的亮度特征的分析以及多次实验 # 同时满足"<170"和">255"条件的为True """ # 归一化结果进一步二值化 sx_binary = np.zeros_like(scaled_sobel) sx_binary[(170 <= scaled_sobel) & (scaled_sobel <= 255)] = 255 # 先是取出<170和>255的元素,取出的均为布尔值,再根据布尔值为True的填充为255(在灰度图中即为白色) s_binary = np.zeros_like(channels_s) s_binary[(100 <= channels_s) & (channels_s <= 255)] = 255 # 饱和度二值化 color_binary = (sx_binary | s_binary) cv2.imshow("color", color_binary) cv2.waitKey(0) cv2.imwrite("panel12.png", color_binary)

2.3 图片保存:

cv2.imwrite("panel12.png", color_binary)

四、仿射变换

import cv2 import numpy as np # 原图片 img = cv2.imread("./img/img/up01.png") # 处理后的图片 color_binary = cv2.imread("./panel11.png") # 灰度化 color_binary = cv2.cvtColor(color_binary, cv2.COLOR_BGR2GRAY) img_shape = color_binary.shape print(img_shape) """ ----------------------------------透视变换--------------------------------------------------- 汽车进入车道的角度会变化,为了统一使用透视变换使视角统一 仿射变换是一种几何变换,它保持了图像中的直线性和平行性。仿射变换包括平移、缩放、旋转和剪切(shear)等操作。 本质上是一种矩阵计算,矩阵本身就是一种空间变换 这些变换可以组合使用,以实现复杂的图像变换。 """ """ 1、偏移量设置: offset_x = 160 和 offset_y = 0:这些偏移量用于调整透视变换后目标图像的位置。 偏移量的选择通常基于实验和图像的具体需求,以确保变换后的图像能够正确地表示车道线. """ offset_x = 160 offset_y = 0 """ 2、定义原始图像上的透视变换点: pts1:这些点定义了原始图像上进行透视变换的四个关键坐标点。定义了需要进行变换的区域 坐标是以图像宽度和高度的比例来表示的,数据类型是np.float32类型的数组。这些点的选择基于图像内容和所需的变换效果。 """ pts1 = np.float32([ [img_shape[1] * 0.4, img_shape[0] * 0.7], # 第一个坐标点,横坐标的图像宽度的0.4倍,纵坐标的图像0.7倍,该例结果为512.0和503.99999999999994 [img_shape[1] * 0.6, img_shape[0] * 0.7], # 第二个坐标点,横坐标的图像宽度的0.4倍,纵坐标的图像0.7倍 [img_shape[1] * 1 / 8, img_shape[0]], # 第三个坐标点,横坐标的图像宽度的1/8倍,纵坐标就是图像高度 [img_shape[1] * 7 / 8, img_shape[0]], # 第四个坐标点,横坐标的图像宽度的7/8倍,纵坐标就是图像高度 ]) print(f"放射变换的参数:{img_shape[1] * 0.4, img_shape[0] * 0.7}") """ 3、定义目标图像上的透视变换点: pts2:这些点定义了透视变换后目标图像上的四个点。这些点的坐标根据图像的宽度、高度和偏移量进行计算。 目标点的选择是为了将原始图像的特定区域映射到目标图像的特定位置,以便于后续处理。 """ pts2 = np.float32([ [offset_x, offset_y], # 第一个点变换后的坐标,按照偏移量来定位 [img_shape[1] - offset_x, offset_y], # 横坐标根据图片的宽度和偏移量进行计算,纵坐标按照偏移量设置0 [offset_x, img_shape[0] - offset_y], # 横坐标按照偏移量,纵坐标根据图像高度和偏移量确定 [img_shape[1] - offset_x, img_shape[0] - offset_y], ]) """ 4、计算透视变换矩阵: 使用cv2.getPerspectiveTransform(pts1, pts2)函数根据给定的源点集pts1和目标点集pts2计算透视变换矩阵pts。 这个矩阵用于将原始图像透视变换为目标图像。 这里的矩阵就是进行仿射变换的关键,仿射变换是一种矩阵运算。 """ pts = cv2.getPerspectiveTransform(pts1, pts2) """ 5、应用透视变换: 使用cv2.warpPerspective(color_binary, pts, (img_shape[1], img_shape[0]))函数对原始图像color_binary进行透视变换, 得到校正后的图像correct_image。变换后的图像与原始图像的宽高保持一致。 """ correct_image = cv2.warpPerspective(color_binary, pts, (img_shape[1], img_shape[0])) # img_shape[0]和img_shape[1]是元组 """ 6、绘制填充矩形: 在correct_image上绘制一个填充矩形,用于标记或处理图像的特定区域。矩形的坐标根据图像的宽度和高度比例计算得出。 """ cv2.rectangle(correct_image, [int(img_shape[1] * 0.4 + 20), int(img_shape[0] * 0.7)], [int(img_shape[1] * 0.6 + 20), int(img_shape[0])], color=(0, 0, 0), thickness=cv2.FILLED ) cv2.imshow("correct_image", correct_image) cv2.waitKey(0) # correct_image是一个图片类型,保存的图片命名要跟上后缀用于确定保存格式 # cv2.imwrite("panel2.png", correct_image) # cv2.waitKey(0)

五、开运算与闭运算

<形态学的腐蚀与膨胀>

1. 开运算:

先腐蚀后膨胀,用于去除图形中的小噪点、孤立的小点,腐蚀多余的像素点

原理:
腐蚀操作阶段:使用一个结构元素(矩形、圆形、其他形状)逐个滑动。
当遇到不符合物体(通常是白色)状态时,就会腐蚀掉(背景像素、通常时黑色的)
就可以去除小的噪点、和微小的物体。

膨胀操作阶段:
使用一个结构元素(矩形、圆形、其他形状)逐个滑动。
当遇到有一个像素是目标像素时,就会膨胀其结构元素中心点的像素
不会回复之前已经腐蚀的像素点

应用:
图像去噪、物体分离


2. 闭运算:

先膨胀后腐蚀。填充图像中的小孔、小裂缝。膨胀特点的元素.

原理:
膨胀阶段:
使用一个结构元素(矩形、圆形、其他形状)逐个滑动。
当遇到有一个像素是目标像素时,就会膨胀其结构元素中心点的像素
不会回复之前已经腐蚀的像素点

腐蚀阶段:
使用一个结构元素(矩形、圆形、其他形状)逐个滑动。
当遇到不符合物体(通常是白色)状态时,就会腐蚀掉中心像素点(背景像素、通常时黑色的)
就可以去除小的噪点、和微小的物体。避免过度膨胀。

应用:
图像修复
物体轮廓修复

3. 腐蚀与膨胀原理

腐蚀:
定义:形态学操作,可以是图像中目标物体(白的或者比较亮的物体)经过一定的收缩,
在二进制图像中,(只有 0 黑色 1 白色)
会将目标物体(白色区域)的边界像素根据一定的规则来变为背景颜色(黑色)

原理:
结构元素的小矩阵,进行滑动,,对于每个像素位置,,当结构元素所覆盖的像素与图像中心点的元素的不完全匹配时,就要进行覆盖

开运算效果图

闭运算效果图

图片转载自形态学应用——图像开运算与闭运算_图像开运算和闭运算-CSDN博客

开运算与闭运算示例

# MORPH_RECT 表示创建矩形结构元素 # MORPH_ELLIPSE 表示创建圆形结构元素 # MORPH_CROSS 表示创建十字结构元素 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) print(f"kernel is:{kernel}") """ 2、闭运算 """ # 先进行膨胀操作 # dilate 使用定义好的元素结构对图像correct_image进行膨胀 # 膨胀操作会让图像中白色(较亮)区域扩大 dilate_image = cv2.dilate(correct_image, kernel) # 再进行腐蚀操作 # erode 使用定义好的元素结构kernel对图像correct_image进行腐蚀 erode_image = cv2.erode(dilate_image, kernel) # 显示经过一次闭运算之后的腐蚀图像 # cv2.imshow("erode_image", erode_image) # cv2.waitKey(0) """ 3、开运算 """ # 先进行腐蚀操作 # erode 使用定义好的元素结构kernel对图像correct_image进行腐蚀 erode_image = cv2.erode(erode_image, kernel) # 显示经过一次闭运算之后的腐蚀图像 # 再进行膨胀操作 # dilate 使用定义好的元素结构对图像correct_image进行膨胀 # 膨胀操作会让图像中白色(较亮)区域扩大 dilate_image = cv2.dilate(erode_image, kernel) # cv2.imshow("erode_image", erode_image) # cv2.waitKey(0) """ 3、直方图 """ # np.sum(...,axis=0) 表示沿着列方向,将图像中矩阵的数值加到一个新的矩阵中 histogram = np.sum(dilate_image[:, :], axis=0) # 类似于横向压缩为一个矩阵? print(pd.DataFrame(histogram)) # 使用matplotlib绘制直方图 # 横坐标: # 是图像的列索引(范围从0开始 到图像的长度,通过np.arrange(0,len(histogram))获得,(例子结果是1280) # 纵坐标: # 对应的列像素总和,histogram # r red g green blue y yellow # - 虚线 .-点虚线 ................... plt.plot(np.arange(0, len(histogram)), histogram, 'r-') plt.show() # 这个直方图揭示了当纵坐标为多少时横坐标是非0的

六、车道线可视化(示例)

import cv2 import numpy as np import matplotlib.pyplot as plt import pandas as pd correct_image = cv2.imread('./panel2.png') # 灰度处理 correct_image = cv2.cvtColor(correct_image, cv2.COLOR_BGR2GRAY) # 创建矩形结构元素。 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) """ 1.膨胀,物体边界扩展,可以链接断开的部分 2.对膨胀的图像进行腐蚀消除噪点 3.再次腐蚀,进一步细化图像(消除不需要的部分) 4.再次膨胀,相互配合,调整图像形态 5.又一次膨胀 6.又一次腐蚀 具体以实际情况为主 """ # 膨胀 dilate_image = cv2.dilate(correct_image, kernel) # 腐蚀 erode_image = cv2.erode(dilate_image, kernel) # 腐蚀 erode_image = cv2.erode(erode_image, kernel) # 膨胀 dilate_image = cv2.dilate(erode_image, kernel) # 膨胀 dilate_image = cv2.dilate(dilate_image, kernel) # 腐蚀 erode_image = cv2.erode(dilate_image, kernel) # 膨胀 dilate_image = cv2.dilate(erode_image, kernel) # 腐蚀 erode_image = cv2.erode(dilate_image, kernel) # cv2.imshow("erode_image", erode_image) # cv2.waitKey(0) """ 一、获取车道线的初始位置 """ "1.1 找到两条车道线" # 直方图图像会有两个凸点,代表了车道线,因为数组的其他地方为0,有车道线的地方才有数字,每列相加为一维数组后只有有车道线的那一块会有数值。 histogram = np.sum(dilate_image[:, :], axis=0) # 类似于纵向压缩为一个矩阵 plt.plot(np.arange(0, len(histogram)), histogram, 'r-') plt.show() "1.2 计算两条车道线的中心线并以此划分左右车道的搜索范围" # 计算直方图(每列像素值相加数组)中的中点位置,用于分割划分左右车道的搜索范围 midpoint = np.array(histogram.shape[0] / 2, dtype=np.int32) # 在直方图的左半部分,寻找像素点累加的最大位置,这个位置作为左车道线初始搜索的大致横坐标的起点。 left_x_base = np.argmax(histogram[:midpoint]) # 在直方图的右半部分,寻找像素点累加的最大位置+中点位置的偏移量,这个位置作为右车道线初始搜索的大致横坐标的终点。 right_x_base = np.argmax(histogram[midpoint:]) + midpoint # histogram[midpoint:]只是中点到右车道线的距离 # 根据上面的计算初始化车道线 # 车道检测当前位置,初始化左车道线的当前横坐标 left_x_current = left_x_base right_x_current = right_x_base """ 二、创建滑动窗口用于检测车道线,并为正式滑动作准备 """ "2.1 设置滑动窗口的数量、高度、检测的水平范围、最小像素点阈值" # 设置滑动窗口的数量,数量可以决定在图像的垂直方向划分多个区域来搜索车道线 m_windows = 9 # 计算每个滑动窗口的高度通过图像的总高度/窗口数量 window_height = int(erode_image.shape[0] / m_windows) # 设置x的检测范围,这里是滑动窗口宽度的一半,手动指定一个值_____,可以确定每个滑动窗口内左右车道线可能出现的水平范围, margin = 100 # 6.设置最小像素点阈值,用于统计每个滑动区域的非0像素个数,当窗口内的非0像素个数小于阈值是,就说明可能不是车道线,对中心点位置进行更新 minpix = 50 "2.2 找到整个图像中不为零的像素点的坐标" # 获取图像中像素值不为0的坐标,nonzero()返回值是两个数组,非零点的纵坐标(行),非0点的纵坐标(行索引)和横坐标(列索引) non_zero = erode_image.nonzero() print(non_zero) # 将此数组的纵、坐标提取出来,并且进行类型转换为numpy non_zero_y = np.array(non_zero[0]) # 纵坐标 non_zero_x = np.array(non_zero[1]) # 横坐标 "2.3 初始化两个索引,分别用于记录那些在左、右车道线搜索中找到的非0数值" # 用于记录搜索窗口的左右车道线的非0数值在nonzero_y和x的索引。初始化为空。 left_lane_inds = [] right_lane_inds = [] """ 三、 开始用滑动窗口搜索车道线,遍历该图中的每一个窗口,从底部窗口开始向上遍历 """ # m_windows是滑动窗口个数 for window in range(m_windows): "3.1 窗口纵坐标范围" # 设置窗口的y的检测范围(纵坐标范围) win_y_low = erode_image.shape[0] - (window + 1) * window_height win_y_high = erode_image.shape[0] - window * window_height "3.2 窗口横坐标范围" # 左车道线x的范围,根据当前左车道的横坐标位置与设置margin(检测范围100)来确定当前车道线坑出现的水平范围 win_x_left_low = left_x_current - margin win_x_left_high = left_x_current + margin # 右车道线x的范围,根据当前右车道的横坐标位置与设置margin(检测范围100)来确定当前车道线坑出现的水平范围 win_x_right_low = right_x_current - margin win_x_right_high = right_x_current + margin "3.3 收集在当前滑动窗口中的非零像素点的索引" # good_left_inds和good_right_inds是一个数组,它包含了在当前滑动窗口中被识别为属于左右车道线的非零像素点的索引。这些索引指向non_zero_x和non_zero_y数组中的位置,即它们对应于图像中非零像素点的横坐标 good_left_inds = ((non_zero_y >= win_y_low) & (non_zero_y < win_y_high) & # 表示处于窗口纵坐标范围内的非0值的索引 (non_zero_x >= win_x_left_low) & ( non_zero_x < win_x_left_high)).nonzero() # 表示处于窗口左车道横坐标范围的非0值的索引 good_right_inds = ((non_zero_y >= win_y_low) & (non_zero_y < win_y_high) & # 表示处于窗口纵坐标范围内的非0值的索引 (non_zero_x >= win_x_right_low) & ( non_zero_x < win_x_right_high)).nonzero() # 表示处于窗口右车道横坐标范围的非0值的索引 "3.4 将收集到的索引添加到分为左右车道分别添加到列表中" # 将在车道线搜索窗口内的非0点的索引添加到记录在车道索引的列表中 left_lane_inds.append(good_left_inds) right_lane_inds.append(good_right_inds) "3.5 通过阈值检验车道是否在窗口内,如果不在就更新横坐标(初始横坐标是根据上面的一维纵坐标相加矩阵)" # 不直接用non_zero_x和non_zero_y原因就是good_left_inds和good_right_inds是经过窗口的阈值(minpix)检验的,更能说明是车道线,而non_zero_x和non_zero_y包含大量非车道线数值 # 如果获取的左车道线搜索窗口内的个数小于最小个数(minpix),则利用这些点的横坐标平均值来进行更新滑动窗口在x轴的车道线 if len(good_left_inds) > minpix: left_x_current = np.mean(non_zero_x[good_left_inds]).astype(dtype=np.int32) # 如果获取的右车道线搜索窗口内的个数小于最小个数(minpix),则利用这些点的横坐标平均值来进行更新滑动窗口在x轴的车道线 if len(good_right_inds) > minpix: right_x_current = np.mean(non_zero_x[good_right_inds]).astype(dtype=np.int32) """ 四、循环结束,提取获得到的车道线的索引 """ # 将检测处左右车道点的索引列表合成一个numpy数组,为了统一处理, axis=1————>按列方向 "4.1 转为numpy数组" left_lane_inds = np.concatenate(left_lane_inds, axis=1) right_lane_inds = np.concatenate(right_lane_inds, axis=1) "4.2 获取左右车道的横纵坐标" left_x = non_zero_x[left_lane_inds] left_y = non_zero_y[left_lane_inds] right_x = non_zero_x[right_lane_inds] right_y = non_zero_y[right_lane_inds] """------------------------------------------------程序执行至此已经获取了所有车道线的坐标-------------------------------------------------------------------""" """五、曲线拟合""" # 就是类似于回归拟合一样的东西,用二次项的方式去拟合车道线 # 3.用于曲线拟合检测出的点,二次多项式拟合,返回结果是二次项的系数(a,b,c),拟合车道线检测出的点,拟合x = ay left_fit = np.polyfit(left_y[0], left_x[0], 2) right_fit = np.polyfit(right_y[0], right_x[0], 2) """六、进行车道线可视化""" "6.1 获取图像行数" y_max = erode_image.shape[0] "6,2 创建一个处理后的图像,从灰度图重新转为彩色图" # np.dstack 是 NumPy 库中的一个函数,用于沿深度方向(第三维)堆叠数组。 # 在这里是从单通道灰度图转为三通道灰度图,三通道意味着虽然本身暂时还没有颜色,但是具备了显示彩色的能力 out_img = np.dstack( (erode_image, erode_image, erode_image)) * 255 # *255的原因是:灰度图是二值图像,只有0和1,回到彩色图要变成0-255的范围,白的就是255,黑的仍然是0 "6.3 获得根据车道线进行曲线拟合生成的坐标点" # 在拟合曲线中获取左、右车道线的像素点通过垂直方向的每一个坐标点y代入拟合的二次多项式公式,进行计算横坐标,从而生成一系列的坐标点 left_points = [[left_fit[0] * y ** 2 + left_fit[1] * y + left_fit[2], y] for y in range(y_max)] right_points = [[right_fit[0] * y ** 2 + right_fit[1] * y + right_fit[2], y] for y in range(y_max)] # 左右车道线的像素点进行合并。形成一个总的坐标点。 line_points = np.vstack((right_points, left_points)) "6.4 优化" # 对合并后的坐标点进行随机打乱,更加均匀地展示 np.random.shuffle(line_points) # 线条区分需要更加明显时使用。 "6.5 根据左右车道线的像素位置绘制多边形。效果是看起来像一整个车道?" # cv2.fillPoly(out_img, [np.array(line_points, dtype=np.int32)], (0, 255, 0)) "6.6 绘制拟合的车道线" # 遍历每个车道线像素点,在输出图像上以原型绘制 for point in line_points.astype(dtype=np.int32): cv2.circle(out_img, point, 10, (0, 255, 0), thickness=5) "6.7 显示" # 显示绘制好的车道线的图像 cv2.imshow('Output', out_img) cv2.waitKey(0)
http://www.jsqmd.com/news/694415/

相关文章:

  • Vue Design System:从零开始构建企业级UI设计系统的完整指南
  • 2025年黑苹果装机终极指南:gh_mirrors/ha/Hackintosh项目完全解析
  • paho.mqtt.c与主流MQTT代理集成:Mosquitto、EMQX、HiveMQ实战
  • x-flux IP-Adapter应用实战:实现图像提示生成的高效方法
  • 避坑指南:Win11下用VS2022配置PCL1.12.1,环境变量和VTK警告都帮你搞定了
  • 终极指南:如何用12-Factor Agents构建革命性教育科技个性化学习体验
  • 从CentOS迁移者视角:手把手在VMware上安装openEuler 22.03 LTS SP3并配置中文环境
  • 【收藏级】月薪6万招不到人!2026年AI时代红利,小白程序员必看
  • 【仅限政企开发者】:VSCode国产化调试证书链信任体系重构方案——基于国家密码管理局SM2根证书的100%自主可控调试通道搭建
  • Linux内核模块/CUDA驱动/RT-Thread组件开发必读:2026内存安全编码黄金11条(附LLVM Pass验证源码)
  • emailjs 与其他邮件库对比:为什么选择 emailjs 的6大理由
  • FluidSynth完全指南:从零开始掌握开源MIDI合成器
  • 终极指南:如何在Windows电脑上轻松安装APK文件?告别笨重模拟器!
  • 抖音视频批量下载终极指南:新手也能轻松掌握的开源工具
  • 告别CANoe新手村:从零搭建一个能跑起来的仿真工程(附DBC文件创建避坑指南)
  • 编译GoodbyeDPI时遇到windres缺失?三步解决Windows环境下的编译难题
  • 2026年小程序商城搭建成本分析:不同方案价格对比?
  • 【实战篇】Qt+VTK项目编译与常见问题排错
  • 实测分享:用Docker编译Android AOSP,比原生Ubuntu 20.04快在哪?踩了哪些坑?
  • 如何使用SVGo创建动态SVG图表和可视化
  • WebRTC for the Curious:深入理解实时通信协议的终极指南
  • 2026 年收藏|AI 大模型零基础自学完整路线,程序员转型落地必备指南
  • 告别二选一!在ESP-IDF项目里优雅调用Arduino库(保姆级配置指南)
  • 终极解决方案:彻底消除drawio桌面版控制台输出污染父进程终端的实战指南
  • 从几何到优化:手把手推导普吕克线与正交表示的转换(附Python验证脚本)
  • 系统安全审计方法
  • Steam成就管理终极指南:快速掌握SAM的完整教程
  • 别再只用PlaySound了!深入聊聊Windows老牌多媒体API:mciSendString的现代玩法
  • 终极解决方案:如何用GoodbyeDPI彻底解决4chan等网站访问难题
  • Handright实战案例:从古诗到现代文档的手写生成