【计算机视觉】数字图像处理基础:以像素为单位的图像基本运算(点运算、代数运算、逻辑运算、几何运算、插值)
0、前言
在上篇文章中,我们对什么是数字图像、以及数字图像的组成(离散的像素点)进行了讲解🔗【计算机视觉】数字图像处理基础知识:模拟和数字图像、采样量化、像素的基本关系、灰度直方图、图像的分类。
我们知道,数字图像其实就是像素点组成的二维矩阵。本节我们要讲的就是基于这个二维矩阵进行一些数学上的基本运算(本质就是就是矩阵的计算——线性代数),对图像进行处理,这些基本运算也是数字图像处理的基础和基本算法,本节我们将介绍这些基本算法。
分别有以下几类
- 点运算:以一副图像(一个二维矩阵)为处理对象,图像中的像素点(矩阵中的值)为处理单位,对图像中的每个像素点进行按一定函数关系的运算,得到一副新图像的处理过程。
- 代数运算:以两幅图像(两个二维矩阵)为处理对象,对两幅图像的对应像素之间进行加减乘除,得到一副输出图像。
- 逻辑运算:也是以两幅图像(两个二维矩阵)为处理对象,对两幅图像的对应像素之间进行逻辑运算,得到输出图像。
- 几何运算:是以一副图像为处理单位,对图像中的不同像素进行变换(改变像素之间的空间关系),从而实现处理后得到的图像进行了几何变换(翻转、镜像、平移等)。
Tips: 我们在进行下面的基本运算时,都是将像素值进行归一化到
[0, 1]
的取值范围的,方便变换和计算。在对应变换的时候也需要注意这点,避免产生误解。
一、点运算
点运算是基于图像中每个像素点的像素值进行运算的图像处理方法。通过对输入图像的像素值和输入图像的像素值之间建立函数映射关系,实现“从像素到像素”的复制操作,输出图像中的每个像素值仅仅和输入图像中对应位置的像素值以及输入到输出像素之间的函数映射关系有关。
💐根据这个函数关系是否线性,我们可以把点运算分为两类:
- 线性点运算
- 非线性点运算
1.1:线性点运算
线性点运算是指输入图像的像素值和输出图像的像素值之间为线性函数关系:
O ( x , y ) = a × I ( x , y ) + b O(x,y) = a\times I(x,y)+b O(x,y)=a×I(x,y)+b
- a = 1 , b = 0 a=1, b=0 a=1,b=0,则 O ( x , y ) = I ( x , y ) O(x,y) = I(x,y) O(x,y)=I(x,y),输入图像和输出图像相同; a = 1 , b ≠ 0 a=1, b\ne 0 a=1,b=0,则输出图像整体的灰度值增加或减少,图像整体变得更两或更暗
- a > 1 a > 1 a>1,则输出图像对比度增大(因为像素之间的灰度差也增大了
a
倍),输出图像整体显示效果较输入图像会更亮。 - 0 ≤ a < 1 0 \le a< 1 0≤a<1,则输出图像对比度减小(因为像素之间的灰度差也减小了
a
倍),输出图像整体显示效果较输入图像更暗。 - a < 0 a<0 a<0, 则灰度值翻转:原来暗的地方变亮,原来亮的地方变暗
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取你的照片
image_path = 'kitten.jpg' # 替换成你的照片路径
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
image = image / 255.0 # 将图像归一化到 [0, 1] 范围# 定义线性点运算函数
def linear_point_operation(image, a, b):return np.clip(a * image + b, 0, 1)# 定义不同的参数
params = [(1, 0), # 原图(1, 0.2), # 增加亮度(2, 0), # 增加对比度(0.5, 0), # 减少对比度(-1, 1) # 灰度翻转
]# 创建子图
fig, axes = plt.subplots(1, 5, figsize=(15, 5))
axes = axes.ravel()# 显示原图和处理后的图像
for ax, (a, b) in zip(axes, params):processed_image = linear_point_operation(image, a, b)ax.imshow(processed_image, cmap='gray')ax.set_title(f'a={a}, b={b}')ax.axis('off')plt.tight_layout()
plt.show()
1.2:非线性点运算
非线性变换能更好模拟人眼对光强的感知特性,因为人眼对亮度的变化感知就是非线性的对数关系,它使图像看起来更加自然和舒适。
非线性点运算根据非线性函数的形式不同可以分为两类:
- 对数变换
- 指数变换
1.2.1:对数变换
表达式为:
O ( x , y ) = c log ( 1 + I ( x , y ) ) O(x,y) = c\log_{}{(1+I(x,y))} O(x,y)=clog(1+I(x,y))
其中,c
为尺度比例常数(变换范围/尺度大or小)
💁🏻♀️由对数变换曲线可以看到,在自变量小(原图像的灰度值底、暗)时,曲线斜率很高,因此因变量(输出图像)会增加的很大;在自变量大(原图像像素已经很亮)时,曲线斜率小,因变量(输出图像)增加的很小。
🪧对数变换的作用:
- 增强一副图像中较暗部分的细节,还原像素值动态范围高图像中被丢失的较暗的像素信息。(因为当像素的灰度值的动态扩展范围很高,但是设备的动态存储范围不满足,且图像像素值主要居于高像素值,则那些低像素值的信息不得不被丢弃,从而损失了大量暗部细节)。
- 使用对数变换,图像的动态范围被合理压缩(而不是由于设备原有限制而直接丢弃,且高值像素也不会过曝),从而可以清晰显示暗部信息。(eg: 前面博客里讲到的傅里叶变换得到的频谱,其动态范围可能宽达 0 ∼ 1 0 6 0 \sim 10^6 0∼106,直接显示频谱时,图像显示设备的动态范围往往不能满足要求,从而丢失大量暗部细节)。
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取你的照片
image_path = 'me.jpg' # 替换成你的照片路径
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
image = image / 255.0 # 将图像归一化到 [0, 1] 范围# 定义对数变换函数
def log_transform(image, c=1):return c * np.log(1 + image)# 进行对数变换
c = 1
log_image = log_transform(image, c)# 归一化到 [0, 1] 范围
log_image = cv2.normalize(log_image, None, 0, 1, cv2.NORM_MINMAX)# 创建子图
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes = axes.ravel()# 显示原图和对数变换后的图像
axes[0].imshow(image, cmap='gray')
axes[0].set_title('Original Image')
axes[0].axis('off')axes[1].imshow(log_image, cmap='gray')
axes[1].set_title('Log Transformed Image')
axes[1].axis('off')plt.tight_layout()
plt.show()
个人觉得对数变换好在:
- 对于低光暗图像,它能极大的提升这些细节的可见性
- 而对于高像素值的高光照图像,它能在保证高光地方不会因为对数变换而变得过于曝光的前提下,还能增强暗部的细节。
1.2.2:指数变换(幂次变换)
指数变换也叫幂次变换、伽马变换,其表达式为:
O ( x , y ) = c I ( x , y ) γ O(x,y) = cI(x,y)^\gamma O(x,y)=cI(x,y)γ
其中 c c c和 γ \gamma γ为正常数
指数变换比对数变换更加灵活,它能根据参数 c c c和 γ \gamma γ的不同取值,根据需求,选择是增强低灰度区域的对比度还是高灰度区域的对比度。
- 0 < γ < 1 0 < \gamma < 1 0<γ<1:效果和对数变换相似, 放大暗处细节;越大,效果越强
- γ > 1 \gamma > 1 γ>1:放大亮处细节,压缩暗处细节;越大效果越强
- γ = 1 \gamma = 1 γ=1: O ( x , y ) = c I ( x , y ) O(x,y) = cI(x,y) O(x,y)=cI(x,y),相当于线性变换
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取你的照片
image_path = 'dim.png' # 替换成你的照片路径
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
image = image / 255.0 # 将图像归一化到 [0, 1] 范围# 定义幂次变换(伽马变换)函数
def gamma_transform(image, c, gamma):return c * np.power(image, gamma)# 定义不同的参数
params = [(1, 0.5), # 增强暗部细节(1, 1.0), # 线性变换(1, 2.0), # 增强亮部细节(0.5, 0.5), # 增强暗部细节,降低亮度(2.0, 2.0) # 增强亮部细节,提高亮度
]# 创建子图
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
axes = axes.ravel()# 显示原图
axes[0].imshow(image, cmap='gray')
axes[0].set_title('Original Image')
axes[0].axis('off')# 显示不同参数下的幂次变换后的图像
for ax, (c, gamma) in zip(axes[1:], params):transformed_image = gamma_transform(image, c, gamma)transformed_image = np.clip(transformed_image, 0, 1) # 确保像素值在 [0, 1] 范围内ax.imshow(transformed_image, cmap='gray')ax.set_title(f'c={c}, γ={gamma}')ax.axis('off')# 隐藏多余的子图框
for ax in axes[len(params) + 1:]:ax.axis('off')plt.tight_layout()
plt.show()
二、代数运算
图像的代数运算是指将两幅或多幅图像通过对应像素之间加、减、乘、除的操作,得到输出图像的方法。
其实也就是矩阵运算:
它们的表达式如下
- 加 S ( x , y ) = A ( x , y ) + B ( x , y ) S(x,y) = A(x,y) + B(x,y) S(x,y)=A(x,y)+B(x,y)
- 减 S ( x , y ) = A ( x , y ) − B ( x , y ) S(x,y) = A(x,y) - B(x,y) S(x,y)=A(x,y)−B(x,y)
- 乘 S ( x , y ) = A ( x , y ) × B ( x , y ) S(x,y) = A(x,y) \times B(x,y) S(x,y)=A(x,y)×B(x,y)
- 除 S ( x , y ) = A ( x , y ) ÷ B ( x , y ) S(x,y) = A(x,y) ÷ B(x,y) S(x,y)=A(x,y)÷B(x,y)
2.1:加法运算
💐应用1:图像叠加
将两幅图像进行加权混合的具体运算表达式如下:
O v e r l a y I m a g e ( x , y ) = α ⋅ I m a g e 1 ( x , y ) + β ⋅ I m a g e 2 ( x , y ) + γ OverlayImage(x,y) = \alpha \cdot Image1(x,y) + \beta \cdot Image2(x,y) + \gamma OverlayImage(x,y)=α⋅Image1(x,y)+β⋅Image2(x,y)+γ
其中:
- O v e r l a y I m a g e ( x , y ) OverlayImage(x,y) OverlayImage(x,y) 是混合后的图像在位置 ( x , y ) (x,y) (x,y)的像素值。
- I m a g e 1 ( x , y ) Image1(x,y) Image1(x,y) 是第一幅图像在位置 ( x , y ) (x,y) (x,y) 的像素值。
- I m a g e 2 ( x , y ) Image2(x,y) Image2(x,y)是第二幅图像在位置 ( x , y ) (x,y) (x,y) 的像素值。
- α \alpha α 是第一幅图像的权重(透明度)。
- b e t a beta beta 是第二幅图像的权重(透明度),通常 (\beta = 1 - \alpha)。
- γ \gamma γ 是一个可选的常数项,用于调整图像的亮度(通常为0)。
这个运算表达式通过 cv2.addWeighted
函数实现
- 去噪:多幅图像相加求平均去除叠加性噪声。若图像中存在的各点噪声为互不相关的加性噪声,且均值为0,对同一景物连续摄取多幅图像,再对多幅图像相加取平均,消除噪声;
import cv2
import numpy as np
import matplotlib.pyplot as pltdef add_noise(image, mean=0, sigma=25):"""添加高斯噪声到图像"""gauss = np.random.normal(mean, sigma, image.shape).astype('uint8')noisy_image = cv2.add(image, gauss)return noisy_imagedef average_images(images):"""对多幅图像进行叠加并求平均"""avg_image = np.mean(images, axis=0).astype('uint8')return avg_image# 读取原始图像
image = cv2.imread('dog.jpg', cv2.IMREAD_GRAYSCALE)# 添加噪声并生成多幅图像
num_images = 10
noisy_images = [add_noise(image) for _ in range(num_images)]# 对多幅图像进行叠加并求平均
denoised_image = average_images(noisy_images)# 显示原始图像、噪声图像和去噪后的图像
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(image, cmap='gray')
axes[0].set_title('Original Image')
axes[0].axis('off')axes[1].imshow(noisy_images[0], cmap='gray')
axes[1].set_title('Noisy Image')
axes[1].axis('off')axes[2].imshow(denoised_image, cmap='gray')
axes[2].set_title('Denoised Image')
axes[2].axis('off')plt.tight_layout()
plt.show()
- 将一幅图像覆盖在另一幅图像上,通常使用透明度(alpha)值来控制叠加的效果。这种方法常用于创建视觉效果,如水印、徽标、特效等。
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取两幅图像
image1 = cv2.imread('dim.png')
image2 = cv2.imread('kitten.jpg')# 调整图像大小相同
image2 = cv2.resize(image2, (image1.shape[1], image1.shape[0]))# 设定权重
alpha = 0.3 # image1的权重
beta = 1 - alpha # image2的权重
gamma = 0 # 常数项,通常为0# 进行加权混合
overlay_image = cv2.addWeighted(image1, alpha, image2, beta, gamma)# 显示混合后的图像
plt.imshow(cv2.cvtColor(overlay_image, cv2.COLOR_BGR2RGB))
plt.title('Overlay Image')
plt.axis('off')
plt.show()
代码解释
alpha = 0.7
:第一幅图像的权重(透明度)为 0.3。beta = 1 - alpha
:第二幅图像的权重(透明度)为 0.7,确保两个权重值的总和为1。gamma = 0
:常数项为0,不对图像亮度进行调整。cv2.addWeighted(image1, alpha, image2, beta, gamma)
:将image1
和image2
按照指定的权重进行加权混合,生成叠加后的图像。
💐应用2:图像信息融合
图像融合的具体运算表达式与图像叠加的表达式类似,但通常图像融合的目的是为了结合多幅图像的信息,以提高图像的质量或增加图像的信息量。图像融合的运算表达式和图像叠加一样(只不过 α \alpha α和 β \beta β一般只会设置为0.5
),表示为:
F u s e d I m a g e ( x , y ) = α ⋅ I m a g e 1 ( x , y ) + β ⋅ I m a g e 2 ( x , y ) + γ FusedImage(x,y) = \alpha \cdot Image1(x,y) + \beta \cdot Image2(x,y) + \gamma FusedImage(x,y)=α⋅Image1(x,y)+β⋅Image2(x,y)+γ
当然,图像融合在多个领域中有着广泛的应用,尤其是在提高图像质量和增加信息量方面。以下是几个具体的应用场景:
- 医学图像处理
在医学图像处理中,图像融合技术用于结合不同成像模式(如CT、MRI、PET等)的图像,以提供更全面的诊断信息。例如:
- CT和MRI图像融合:CT图像提供骨骼结构的详细信息,而MRI图像提供软组织的详细信息。通过融合这两种图像,可以获得更全面的解剖结构信息,有助于医生进行更准确的诊断和治疗计划。
- PET和MRI图像融合:PET图像显示代谢活动,而MRI图像显示解剖结构。融合这些图像可以帮助医生更好地理解肿瘤的代谢活动和其在解剖结构中的位置。
- 遥感图像处理
在遥感图像处理中,图像融合技术用于结合不同传感器获取的图像,以提高图像的空间分辨率和光谱信息。例如:
- 全色图像和多光谱图像融合:全色图像具有高空间分辨率,但缺乏光谱信息;多光谱图像具有丰富的光谱信息,但空间分辨率较低。通过融合这两种图像,可以获得既具有高空间分辨率又具有丰富光谱信息的图像,有助于地物分类、植被监测和环境监测等任务。
- 雷达图像和光学图像融合:雷达图像具有全天候、全天时的成像能力,而光学图像提供丰富的光谱信息。融合这些图像可以提高目标检测和识别的准确性。
- 多光谱图像处理
在多光谱图像处理中,图像融合技术用于结合不同波段的图像,以提高图像的光谱分辨率和信息量。例如:
- 红外图像和可见光图像融合:红外图像在夜间或恶劣天气条件下具有较好的成像能力,而可见光图像在良好的光照条件下提供丰富的细节信息。融合这些图像可以在各种环境条件下提供更清晰和详细的图像。
- 高光谱图像融合:高光谱图像包含大量波段的信息,通过融合不同波段的图像,可以提取更多的光谱特征,有助于精细的地物分类和目标识别。
- 安防监控
在安防监控领域,图像融合技术用于结合不同摄像头的图像,以提高监控系统的性能。例如:
- 红外摄像头和可见光摄像头融合:红外摄像头在夜间或低光照条件下具有较好的成像能力,而可见光摄像头在白天提供丰富的细节信息。通过融合这两种摄像头的图像,可以在全天候条件下提供高质量的监控图像,提高目标检测和识别的准确性。
- 工业检测
在工业检测领域,图像融合技术用于结合不同成像模式的图像,以提高检测精度和效率。例如:
- X射线图像和可见光图像融合:X射线图像可以穿透物体,显示其内部结构,而可见光图像提供表面细节信息。通过融合这些图像,可以更全面地检测产品的内部和外部缺陷,提高质量控制的准确性。
具体的图像融合方法
图像融合的方法有很多,常见的包括:
- 加权平均法:如前面提到的
cv2.addWeighted
方法,通过对图像像素值进行加权平均来实现融合。 - 小波变换法:利用小波变换将图像分解为不同频率的子带,对子带进行融合后再重建图像。
- 金字塔变换法:利用金字塔变换将图像分解为不同尺度的子图,对子图进行融合后再重建图像。
- 深度学习法:利用卷积神经网络等深度学习方法对图像进行特征提取和融合。
图像融合技术在各个领域中的应用极大地提高了图像处理的效果和效率,为科学研究、工业生产和日常生活带来了诸多便利。
💐应用3:图像增强
🚨:"图像增强"≠“数据增强”。前者是增强一副图像的特征,提高图像的可见性和识别效果;后者是因为训练模型时数据不够或需要鲁棒性而增加数据的数量。
通过将原始图像与增强图像(如边缘检测结果)相加,可以增强图像的某些特征,从而提高图像的可见性或识别效果。
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像
image = cv2.imread('kitten.jpg', cv2.IMREAD_GRAYSCALE)# 进行边缘检测
edges = cv2.Canny(image, 100, 200)# 将边缘图像与原始图像相加
enhanced_image = cv2.add(image, edges)# 显示图像
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(image, cmap='gray')
axes[0].set_title('Original Image')
axes[0].axis('off')axes[1].imshow(edges, cmap='gray')
axes[1].set_title('Edges')
axes[1].axis('off')axes[2].imshow(enhanced_image, cmap='gray')
axes[2].set_title('Enhanced Image')
axes[2].axis('off')plt.tight_layout()
plt.show()
2.2:减法运算
图像的减法运算也称为差分方法,将两幅图像进行对应像素值相减可以用来检测图像的变化(缺陷检测)、物体的运动等。
💐应用1:运动检测
运动检测是通过对一个视频的连续帧相减,来判断是否有运动的物体(or外来人员检测)。
import cv2
import numpy as npdef motion_detection(video_path, window_size=(640, 480)):cap = cv2.VideoCapture(video_path)ret, frame1 = cap.read()if not ret:print("Failed to read video")returnprvs = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)while cap.isOpened():ret, frame2 = cap.read()if not ret:breaknext = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)diff = cv2.absdiff(prvs, next)_, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)# 调整窗口大小cv2.namedWindow('Original', cv2.WINDOW_NORMAL)cv2.resizeWindow('Original', window_size[0], window_size[1])cv2.imshow('Original', frame2)cv2.namedWindow('Motion Detection', cv2.WINDOW_NORMAL)cv2.resizeWindow('Motion Detection', window_size[0], window_size[1])cv2.imshow('Motion Detection', thresh)prvs = nextif cv2.waitKey(30) & 0xFF == 27:breakcap.release()cv2.destroyAllWindows()if __name__ == "__main__":video_path = 'D:/CollegeStudy/AI/windElectProject/VS2019_cpp/data/video_good/test_video.mp4'window_size = (800, 600) # 设置窗口大小motion_detection(video_path, window_size)
前一帧减去后一帧,再二值化,没有运动的地方结果为0,显示为黑色,运动的地方结果为1
💐应用2:背景减除:
通过从当前图像中减去背景图像,可以去除背景,仅保留前景,这对对象检测和跟踪中是否有用。
import cv2
import matplotlib.pyplot as pltdef background_subtraction(image_path, background_path):image = cv2.imread(image_path)background = cv2.imread(background_path)gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)gray_background = cv2.cvtColor(background, cv2.COLOR_BGR2GRAY)diff = cv2.absdiff(gray_background, gray_image)_, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)# 将BGR图像转换为RGB图像以便使用matplotlib显示image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)background_rgb = cv2.cvtColor(background, cv2.COLOR_BGR2RGB)# 使用matplotlib显示图像plt.figure(figsize=(15, 5))plt.subplot(1, 3, 1)plt.title("Original Image")plt.imshow(image_rgb)plt.axis('off')plt.subplot(1, 3, 2)plt.title("Background Image")plt.imshow(background_rgb)plt.axis('off')plt.subplot(1, 3, 3)plt.title("Foreground (Processed Image)")plt.imshow(thresh, cmap='gray')plt.axis('off')plt.show()if __name__ == "__main__":image_path = 'people.png'background_path = 'background.png'background_subtraction(image_path, background_path)
但是实际过程中我们可能一开始没有背景图像(已经有人在里面了),这个时候我们要用其他技术,取根据图片信息获取到背景:背景建模:在动态场景中,可以通过背景建模的方法来获取背景图像。背景建模通常利用多帧图像来估计背景,常见的方法包括高斯混合模型(GMM)等。
当然背景也不是一成不变,在某些应用中,可以通过实时更新背景的方法来获取背景图像。例如,在视频监控中,可以逐帧更新背景图像,以适应背景的缓慢变化。
以下是一个通过高斯混合模型(GMM)进行背景估计和实时更新背景的示例代码:
import cv2
import numpy as np
import matplotlib.pyplot as pltdef background_subtraction(video_path):cap = cv2.VideoCapture(video_path)# 创建背景减除对象fgbg = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=True)while cap.isOpened():ret, frame = cap.read()if not ret:break# 应用背景减除fgmask = fgbg.apply(frame)# 获取当前背景图像background = fgbg.getBackgroundImage()# 将BGR图像转换为RGB图像以便使用matplotlib显示frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)background_rgb = cv2.cvtColor(background, cv2.COLOR_BGR2RGB)# 使用matplotlib显示图像plt.figure(figsize=(15, 5))plt.subplot(1, 3, 1)plt.title("Original Frame")plt.imshow(frame_rgb)plt.axis('off')plt.subplot(1, 3, 2)plt.title("Background Image")plt.imshow(background_rgb)plt.axis('off')plt.subplot(1, 3, 3)plt.title("Foreground Mask")plt.imshow(fgmask, cmap='gray')plt.axis('off')plt.show()if cv2.waitKey(30) & 0xFF == 27:breakcap.release()cv2.destroyAllWindows()if __name__ == "__main__":video_path = 'test.mp4'background_subtraction(video_path)
💐应用3:缺陷(病变)检测
医学图像分析通过对比不同时间点的医学图像来检测病变或异常变化、目前使用图像减法检测PCB上的缺陷零件的方法被证明速度非常快。以下是一个简单的示例代码:
import cv2def medical_image_analysis(image_path1, image_path2):image1 = cv2.imread(image_path1, cv2.IMREAD_GRAYSCALE)image2 = cv2.imread(image_path2, cv2.IMREAD_GRAYSCALE)diff = cv2.absdiff(image1, image2)_, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)cv2.imshow('Medical Image 1', image1)cv2.imshow('Medical Image 2', image2)cv2.imshow('Change Detection', thresh)cv2.waitKey(0)cv2.destroyAllWindows()if __name__ == "__main__":image_path1 = 'path/to/your/medical_image1.png'image_path2 = 'path/to/your/medical_image2.png'medical_image_analysis(image_path1, image_path2)
2.3:乘法运算:
将两副图像进行乘法运算,其实一般都是 掩膜(mask) 的作用:将一个图像作为遮罩/掩膜,与待处理图像对应像素位置相乘,从而只显示被遮罩or没有被遮罩部分内容,通过改变掩膜的形状可以对待处理图像中的信息进行处理。
将图像和某些滤波器逐像素相乘,也可以实现滤波作用。
比如在前面的傅里叶频谱处理过程中,高通or低通滤波就用到了和频谱大小一样的掩膜(mask)与原图像相乘,从而去掉高频or低频信息。
2.4:除法运算
将两个图像的像素点进行逐像素相除,它也能描述像素之间的差异,但不是像相减那样的绝对差异,它描述的是两幅图像对应像素值的变化比率。
通过像素值的变化比率,可以用于矫正成像设备的非线性影响:
原理解释
成像设备(如相机、扫描仪等)在捕捉图像时,可能会因为传感器的非均匀性、光学系统的不完美等原因,导致图像的亮度或颜色在空间上出现非线性变化。这种非线性影响会使得图像在不同位置的亮度或颜色表现不一致,即使实际场景是均匀的。
通过图像除法运算,可以将一幅受影响的图像与一幅理想情况下的参考图像进行相除,得到一个比率图像。这个比率图像反映了原始图像中每个像素相对于参考图像的变化比率,得到这个比率图像后对其进行合理处理,再乘以原图像相乘,从而消除了非线性影响。
校正过程的详细解释
- 原始图像(Image1):这是受成像设备非线性影响的图像。
- 参考图像(Image2):这幅图像应该反映理想情况下的图像,通常是在标准条件下拍摄的图像,或者经过某种处理得到的图像。
校正步骤
-
获取参考图像:理想情况下,参考图像应该是没有非线性影响的图像。如果没有,可以通过多次拍摄取平均值,或者使用某种图像处理技术得到。
-
图像相除:将原始图像逐像素除以参考图像,得到比率图像。
-
比率图像处理: 使用 cv2.GaussianBlur 对比率图像进行高斯滤波,以减少噪声和不稳定因素。
-
校正后的图像生成: 将比率图像与参考图像逐像素相乘,得到校正后的图像。
注意事项
- 参考图像的获取:参考图像应尽可能准确地反映成像设备在标准条件下的输出,可以通过多次扫描取平均值或使用标准化模具来获得。
- 图像配准:在进行图像相除之前,确保两幅图像在空间上严格对齐。可以使用图像配准算法(如基于特征点的配准或基于互信息的配准)来实现。
- 噪声处理:在实际应用中,图像可能包含噪声,除法运算可能会放大噪声,因此需要进行适当的噪声处理,如使用滤波器。
三、逻辑运算
逻辑运算主要针对的是二值图像,以像素为基础进行两幅或多幅图的操作。常见的逻辑运算:与、或、非、或非、与非、异或等。
import numpy as np
import cv2
import matplotlib.pyplot as plt# 模拟两幅黑白图像的矩阵数据(0表示黑色,255表示白色)
image1 = np.array([[0, 255, 0],[0, 255, 0],[0, 255, 0]], dtype=np.uint8)image2 = np.array([[0, 0, 0],[255, 255, 255],[0, 0, 0]], dtype=np.uint8)# 逻辑与运算
and_image = cv2.bitwise_and(image1, image2)# 逻辑或运算
or_image = cv2.bitwise_or(image1, image2)# 逻辑非运算
not_image1 = cv2.bitwise_not(image1)
not_image2 = cv2.bitwise_not(image2)# 逻辑异或运算
xor_image = cv2.bitwise_xor(image1, image2)# 使用matplotlib显示图像
plt.figure(figsize=(15, 10))plt.subplot(2, 3, 1)
plt.title("Original Image 1")
plt.imshow(image1, cmap='gray')
plt.axis('off')plt.subplot(2, 3, 2)
plt.title("Original Image 2")
plt.imshow(image2, cmap='gray')
plt.axis('off')plt.subplot(2, 3, 3)
plt.title("AND Operation")
plt.imshow(and_image, cmap='gray')
plt.axis('off')plt.subplot(2, 3, 4)
plt.title("OR Operation")
plt.imshow(or_image, cmap='gray')
plt.axis('off')plt.subplot(2, 3, 5)
plt.title("NOT Operation on Image 1")
plt.imshow(not_image1, cmap='gray')
plt.axis('off')plt.subplot(2, 3, 6)
plt.title("XOR Operation")
plt.imshow(xor_image, cmap='gray')
plt.axis('off')plt.show()
四、几何运算
又称之为几何变换,不同于上面几种只是改变相同位置处的灰度值,几何变换不改变灰度值,只改变该像素在输出图像中的位置。
基本的结合变换本质也是对体现这个二维矩阵进行变换,根据变换方法不同(即像素坐标映射方法的不同),可以分为以下几种:
4.1:平移
x 1 = x 0 + △ x , y 1 = y 0 + △ y x_1 = x_0 +\bigtriangleup x,y_1 = y_0 +\bigtriangleup y x1=x0+△x,y1=y0+△y
其中x0,y0
是该像素点在原始图像中的位置,x1,y1
是该像素点映射到输出图像上的位置。
用矩阵的形式表示为:
( x 1 y 1 1 ) = ( 1 0 Δ x 0 1 Δ y 0 0 1 ) ( x 0 x 1 1 ) \begin{pmatrix}x_1 \\y_1 \\1 \end{pmatrix} = \begin{pmatrix} 1 & 0 & \Delta x\\ 0 & 1 & \Delta y \\ 0 &0 &1 \end{pmatrix}\begin{pmatrix} x_0\\ x_1 \\ 1 \end{pmatrix} x1y11 = 100010ΔxΔy1 x0x11
import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像
image = cv2.imread('dog.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 转换颜色空间到RGB# 平移距离
tx, ty = 100, 50# 创建平移矩阵
translation_matrix = np.float32([[1, 0, tx], [0, 1, ty]])# 应用平移
translated_image = cv2.warpAffine(image, translation_matrix, (image.shape[1], image.shape[0]))# 可视化
plt.subplot(1, 2, 1)
plt.imshow(image)
plt.title('Original Image')
plt.subplot(1, 2, 2)
plt.imshow(translated_image)
plt.title('Translated Image')
plt.show()
4.2:镜像
镜像变换又称之为对称变换,分为水平对称(水平镜像)、垂直对称(垂直镜像)等多种变换。
设原始图像的宽为w
,高为h
:
- 水平镜像:以图像竖直中轴线为中心轴,将左右两部分像素点进行对换。
( x 1 y 1 1 ) = ( − 1 0 w 0 1 0 0 0 1 ) ( x 0 x 1 1 ) \begin{pmatrix}x_1 \\y_1 \\1 \end{pmatrix} = \begin{pmatrix} -1 & 0 & w\\ 0 & 1 & 0 \\ 0 &0 &1 \end{pmatrix}\begin{pmatrix} x_0\\ x_1 \\ 1 \end{pmatrix} x1y11 = −100010w01 x0x11
- 竖直镜像:以图像水平中轴线为中心轴,将上下两部分像素点进行兑换。
( x 1 y 1 1 ) = ( 1 0 0 0 − 1 h 0 0 1 ) ( x 0 x 1 1 ) \begin{pmatrix}x_1 \\y_1 \\1 \end{pmatrix} = \begin{pmatrix} 1 & 0 &0\\ 0 & -1 & h \\ 0 &0 &1 \end{pmatrix}\begin{pmatrix} x_0\\ x_1 \\ 1 \end{pmatrix} x1y11 = 1000−100h1 x0x11
import cv2
import matplotlib.pyplot as plt# 加载图像并转换颜色通道顺序,以便正确显示
image = cv2.imread('me.jpg') # 替换为你的图像路径
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 水平镜像
flipped_horizontally = cv2.flip(image_rgb, 1)# 垂直镜像
flipped_vertically = cv2.flip(image_rgb, 0)# 设置绘图
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(image_rgb)
axes[0].set_title("Original Image")
axes[0].axis('off')axes[1].imshow(flipped_horizontally)
axes[1].set_title("Horizontally Flipped")
axes[1].axis('off')axes[2].imshow(flipped_vertically)
axes[2].set_title("Vertically Flipped")
axes[2].axis('off')# 显示结果
plt.tight_layout()
plt.show()
4.3:旋转
旋转变换通常是以图像的中心为圆心,将像素点绕着这个圆心进行旋转一定角度。
为了描述这种旋转关系,我们先把在笛卡尔系下的坐标转换成极坐标表示:
{ x 0 = r cos α y 0 = r sin α \left\{\begin{matrix} x_0=r\cos \alpha \\ y_0 = r\sin \alpha \end{matrix}\right. {x0=rcosαy0=rsinα
当我们逆时针旋转β
角度后,得到新坐标为:
{ x = r cos ( α − β ) = r cos α cos β + r sin α sin β y = r sin ( α − β ) = r sin α cos β − r cos α sin β \left\{\begin{matrix} x=r\cos (\alpha -\beta ) = r\cos \alpha \cos \beta + r\sin \alpha \sin\beta \\ y = r\sin (\alpha -\beta )= r\sin \alpha \cos \beta - r\cos \alpha \sin\beta \end{matrix}\right. {x=rcos(α−β)=rcosαcosβ+rsinαsinβy=rsin(α−β)=rsinαcosβ−rcosαsinβ
将x0,y0
带入:
{ x = x 0 cos β + y 0 sin β y = − x 0 sin β + y 0 cos β \left\{\begin{matrix} x= x_0 \cos \beta + y_0\sin\beta \\ y = -x_0 \sin\beta +y_0 \cos \beta \end{matrix}\right. {x=x0cosβ+y0sinβy=−x0sinβ+y0cosβ
用矩阵表示为:
( x 1 y 1 1 ) = ( cos β sin β 0 − sin β cos β 0 0 0 1 ) ( x 0 x 1 1 ) \begin{pmatrix}x_1 \\y_1 \\1 \end{pmatrix} = \begin{pmatrix} \cos\beta & \sin \beta &0\\ -\sin\beta & \cos \beta & 0 \\ 0 &0 &1 \end{pmatrix}\begin{pmatrix} x_0\\ x_1 \\ 1 \end{pmatrix} x1y11 = cosβ−sinβ0sinβcosβ0001 x0x11
import cv2
import matplotlib.pyplot as plt
import numpy as np# 读取图像
image = cv2.imread('me.jpg') # 替换为你的图像路径
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 将BGR转换为RGB# 获取图像的尺寸及中心点坐标
(h, w) = image.shape[:2]
(cx, cy) = (w // 2, h // 2)# 设置旋转角度
angle = 45 # 旋转45度# 获取旋转矩阵
M = cv2.getRotationMatrix2D((cx, cy), angle, 1.0) # 中心点,旋转角度,缩放因数# 执行旋转
rotated_image = cv2.warpAffine(image_rgb, M, (w, h))# 可视化原始图像与旋转后的图像
plt.subplot(1, 2, 1)
plt.imshow(image_rgb)
plt.title('Original Image')
plt.axis('off')plt.subplot(1, 2, 2)
plt.imshow(rotated_image)
plt.title('Rotated Image')
plt.axis('off')plt.tight_layout()
plt.show()
在OpenCV的
cv2.getRotationMatrix2D
函数中,如果你提供一个正角度,图像将会沿着原点顺时针旋转。
4.4:缩放
图像缩放指的是改变图像的大小尺寸,这包括:放大、缩小。无论是在放大还是缩小的过程中。
缩放和前面三种几何运算不太一样,在于图像所方法不仅仅是图像位置的简单重新映射,因为图像尺寸的改变,必然带来新像素的产生和原有像素的合并,所以新图像中像素值需要通过插值算法来确定,这是一种由后往前的过程。
插值算法决定了新像素值的生成(不只是多出来的那些,而是整个输出图像的像素值),不同插值方法对图像的质量和细节产生不同的影响。
放大图像
当你放大一幅图像时,就会有新的像素点添加到图像中。这些新的像素点并没有直接对应于原始图像中的实际像素值。因此,需要通过插值的方式来估计这些新像素点的颜色值。常见的插值方法包括:
-
最近邻插值:选择最近的像素值作为新像素点的值,这种方式是最简单但可能导致锯齿状的效果。
最近邻插值方法中的“最近”,是指在原始图像中距离新像素位置欧几里得距离最近的原始像素点的值被直接用作新像素点的值。换句话说,对于缩放后图像中的每一个新像素位置,算法都会在原始图像中寻找最接近这个新位置(在原始尺度上)的原始像素点(哪个像素点经过放缩变换后离它最近),并将该原始像素点的值直接赋给新像素点。
假设有一个原始图像需要放大,当你需要计算新图像中某个像素点的值时:
首先,你将这个新像素点的位置映射回原始图像的尺度。
然后,寻找与映射位置最接近的原始像素点。
最后,直接使用这个最近原始像素点的值作为新像素点的值。 -
双线性插值:基于周围4个像素点的线性插值,能够得到更加平滑的效果。
step1:确定周围的四个像素:在缩放过程中,新像素位置会落在原始像素构成的网格中的某些位置。为了计算新像素的值,我们需要找到原始图像中最接近新像素位置的四个像素。
step2:计算距离权重:基于新像素位置与这四个像素位置在x和y轴方向上的距离,计算每个像素对新像素值的贡献权重。距离越近,权重越大。
step3:加权平均:用找到的四个像素值和对应的权重,计算加权平均值,这个值即为新像素的值。 -
双三次插值(立方插值):基于周围16个像素点的更复杂的插值,能够得到更平滑的边缘。
缩小图像
当缩小图像时,原始图像中的多个像素值会映射到新图像中的单个像素点上。因此必须采取某种策略来决定新像素点的值。常见的方法包括:
- 区域插值:通过结合原图像中缩放区域内所有像素值的平均值来确定新像素点的值。
- 毛玻璃插值:基于统计学方法(如平均值或中值),结合原图像中像素的随机样本来决定新像素点的值。
总之,无论是放大还是缩小图像,都需要通过某种方式来填充新的像素点或合并多个像素点,而这正是插值发挥作用的地方。插值方法的选择会直接影响图像缩放后的质量,因此在缩放图像时应当考虑所使用的插值方法。在OpenCV中,cv2.resize()
函数让你可以指定使用的插值方法,以达到最佳的缩放效果。
import cv2
import matplotlib.pyplot as plt
import math# 加载图像,并转换颜色为RGB
image = cv2.imread('me.jpg') # 替换为你的图像路径
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)# 设置不同的缩放因子
scales = [0.25, 0.5, 0.75, 1.25, 1.5] # 将分别缩放25%, 50%, 75%, 125%, 和 150%# 确定subplot的行数和列数
rows = 2
cols = 3# 创建subplot
fig, axes = plt.subplots(rows, cols, figsize=(20, 10))# 将原始图像和缩放后的图像放入list,双线性插值方法
images = [image_rgb] + [cv2.resize(image_rgb, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR) for scale in scales]
titles = ['Original Image'] + [f'Scale {scale*100}%' for scale in scales]# 填充每个subplot
for i, ax in enumerate(axes.flat):if i < len(images):ax.imshow(images[i])ax.set_title(titles[i])ax.axis('off')plt.tight_layout()
plt.show()
相关文章:

【计算机视觉】数字图像处理基础:以像素为单位的图像基本运算(点运算、代数运算、逻辑运算、几何运算、插值)
0、前言 在上篇文章中,我们对什么是数字图像、以及数字图像的组成(离散的像素点)进行了讲解🔗【计算机视觉】数字图像处理基础知识:模拟和数字图像、采样量化、像素的基本关系、灰度直方图、图像的分类。 我们知道&a…...

Spring Boot整合WebSocket和Redis实现直播间在线人数统计功能
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…...

uniapp自定义的下面导航
uniapp自定义的下面导航 看看效果图片吧 文章目录 uniapp自定义的下面导航 看看效果图片吧  前言一、写组件、我这里就没有写组件了直接写了一个页面?总结 前言 在…...
【Python】selenium使用find_element时解决【StaleElementReferenceException】问题的方法
StaleElementReferenceException 是 Selenium WebDriver 中的一种异常,通常在元素与当前页面的状态不同步时抛出,比如页面已经刷新或导航到另一个页面,但是尝试操作的元素引用仍然是旧页面上的元素。 以下是一些解决 StaleElementReferenceE…...

Apache IoTDB 分布式架构三部曲(三)副本与共识算法
IoTDB 首创并应用的共识协议统一框架,为用户提供了灵活选择不同共识算法的可能性。 对于一个分布式集群而言,为了使得海量数据场景下集群能够横向扩展,集群需要按照一定的规则将全部数据分成多个子集存储在不同的节点上,从而能够更…...

数据挖掘--聚类分析:基本概念和方法
数据挖掘--引论 数据挖掘--认识数据 数据挖掘--数据预处理 数据挖掘--数据仓库与联机分析处理 数据挖掘--挖掘频繁模式、关联和相关性:基本概念和方法 数据挖掘--分类 数据挖掘--聚类分析:基本概念和方法 聚类分析 聚类分析是把一个数据对象&…...

APP单页分发源码下载安卓苹果自动识别apk描述文件免签自动安装
下载地址:APP单页分发源码下载安卓苹果自动识别apk描述文件免签自动安装...
golang定时器使用示例
1.定时器创建与停止 //定时器使用t1 : time.NewTimer(2 * time.Second)<-t1.Cfmt.Println("timer1 fired")t2 : time.NewTimer(5 * time.Second)go func() {fmt.Println("go协程处理中,等待5秒后输出...")<-t2.Cfmt.Println("timer2 fired&quo…...

[FSCTF 2023]Tea_apk
得到密文和密钥 import base64 from ctypes import c_uint32import libnumDELTA 0x9E3779B9def decrypt(v, n, k):rounds 6 int(52 / n)sum c_uint32(rounds * DELTA)y v[0].valuewhile rounds > 0:e (sum.value >> 2) & 3p n - 1while p > 0:z v[p …...

分享一个用python写的本地WIFI密码查看器
本章教程,主要分享一个本地wifi密码查看器,用python实现的,感兴趣的可以试一试。 具体代码 import subprocess # 导入 subprocess 模块,用于执行系统命令 import tkinter as tk # 导入 tkinter 模块,用于创建图形用…...

【SkyWalking】启用apm-trace-ignore-plugin追踪忽略插件
背景 使用Agent采集追踪数据的时候,想排除某些路径,比如健康检查等,这样可以减少上报的数据,也可以去除一些不必要的干扰数据。 加载插件 在agent/optional-plugins目录中有个apm-trace-ignore-plugin-${version}.jar插件&…...

独立游戏之路 -- 获取OAID提升广告收益
Unity 之 获取手机:OAID、IMEI、ClientId、GUID 前言一、Oaid 介绍1.1 Oaid 说明1.2 移动安全联盟(MSA) 二、站在巨人的肩膀上2.1 本文实现参考2.2 本文实现效果2.3 本文相关插件 三、Unity 中获取Oaid3.1 查看实现源码3.2 工程配置3.3 代码实现3.4 场景搭建 四、总…...

反转链表 (oj题)
一、题目链接 https://leetcode.cn/problems/reverse-linked-list/submissions/538124207 二、题目思路 1.定义三个指针,p1先指向NULL p2指向头结点 p3指向第二个结点 2.p2的next指向p1。然后移动指针,p1来到p2的位置,p2来到p3的位置&…...

Mysql使用中的性能优化——批量插入的规模对比
在《Mysql使用中的性能优化——单次插入和批量插入的性能差异》中,我们观察到单次批量插入的数量和耗时呈指数型关系。 这个说明,不是单次批量插入的数量越多越好。本文我们将通过实验测试出本测试案例中最佳的单次批量插入数量。 结论 本案例中约每次…...
TCP为什么握手是三次,而挥手是四次
TCP(传输控制协议)使用三次握手(3WHS)来建立一个可靠的连接,并使用四次挥手(4WHS)来终止连接。以下是每个步骤的详细解释: 三次握手(3WHS)建立连接ÿ…...
前端面试题大合集9----TypeScript
目录 一、TypeScript 中静态类型的概念及其好处 二、如何在 TypeScript 的接口中定义可选属性? 三、解释 TypeScript 中联合类型的概念并提供示例 四、TypeScript 中的类型断言是什么? 五、TS中泛型是什么? 六、解释 TypeScript 中的“…...

Linux:动态库和静态库的编译与使用
目录 1.前言 2.静态链接库 3.静态链接库生成步骤 4.静态链接库的使用 5.动态链接库 6.动态链接库生成步骤 7.动态链接库的使用 8.动态链接库无法加载 9.解决动态链接库无法加载问题 前言 在《MinGW:从入门到链接库》博客中简单介绍了如何编译动态链接库和静态链接库…...

【Pyqt6 学习笔记】DIY一个二维码解析生成小工具
文章目录 Pycharm 配置QtDesignerPyUIC基本模板 代码示例依赖包main.pyscreen_shot_module.pyuntitled.pyuntitled.ui Pycharm 配置 摘自PyQT6的从零开始在Pycharm中配置与使用——蹦跑的蜗牛 pip install PyQt6 PyQt6-toolsQtDesigner File -> Settings -> External …...
关于xilinx srio ip复位问题
关于xilinx srio ip复位问题 语言 :Verilg HDL 、VHDL EDA工具: Vivado 关于xilinx srio ip复位问题一、引言二、FPGA 之间 srio通信复位处理复位时序不同步:SRIO 模块未正确初始化:等待复位完成的时间不足:SRIO 配置…...

04 uboot 编译与调试
新手不需要详细掌握 uboot,只需要知道它是一个什么东西即可,工作中也只是改一些参数而已。 1、uboot 是什么 Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段 bootloader 程序。这段 bootloader 程序会先初始化 DDR 等外设,然后将 Linux 内…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...