Python计算机视觉 第3章-图像到图像的映射

Python计算机视觉 第3章-图像到图像的映射

3.1 单应性变换

单应性变换(Homography)是计算机视觉中非常重要的一种几何变换,它用于将一个平面内的点映射到另一个平面内。具体来说,单应性变换可以描述一个图像在摄像机视角变化、平面移动或旋转时,如何从一个视角变换到另一个视角。

这种变换在多个应用场景中非常有用,比如:

  1. 图像配准:将不同视角或不同时间拍摄的图像对齐,找到它们之间的对应关系。
  2. 图像校正:修正由于摄像机角度或透视导致的图像扭曲,使图像看起来更平整。
  3. 纹理扭曲:将一个平面的纹理准确地映射到另一个平面上。
  4. 全景图像创建:将多个图像拼接成一个大的全景图像。

单应性变换的频繁使用,尤其是在涉及多个视角或需要精确对齐图像的情况下,能够显著提升算法的鲁棒性和精度。在项目中,理解和正确应用单应性变换是处理图像和三维几何信息的关键技能。

单应性变换(Homography)将二维平面上的点映射到另一个平面上的点,在齐次坐标(homogeneous coordinates)下,这种映射可以通过以下方程来表示:

( x ′ y ′ w ′ ) H ⋅ ( x y w ) \begin{pmatrix} x' \\ y' \\ w' \ \end{pmatrix} \mathbf{H} \cdot \begin{pmatrix} x \\ y \\ w \end{pmatrix} xyw  H xyw

其中,单应性矩阵 H \mathbf{H} H 为:

H = ( h 11 h 12 h 13 h 21 h 22 h 23 h 31 h 32 h 33 ) \mathbf{H} = \begin{pmatrix} h_{11} & h_{12} & h_{13} \\ h_{21} & h_{22} & h_{23} \\ h_{31} & h_{32} & h_{33} \end{pmatrix} H= h11h21h31h12h22h32h13h23h33

经过单应性变换后的目标点的常规二维坐标 ( x ′ , y ′ ) (x', y') (x,y) 为:

x ′ = h 11 x + h 12 y + h 13 h 31 x + h 32 y + h 33 x' = \frac{h_{11}x + h_{12}y + h_{13}}{h_{31}x + h_{32}y + h_{33}} x=h31x+h32y+h33h11x+h12y+h13

y ′ = h 21 x + h 22 y + h 23 h 31 x + h 32 y + h 33 y' = \frac{h_{21}x + h_{22}y + h_{23}}{h_{31}x + h_{32}y + h_{33}} y=h31x+h32y+h33h21x+h22y+h23

通过这些公式,你可以描述平面间的各种变换,比如旋转、缩放、平移、透视变换等。

3.1.1 直接线性变换算法

单应性矩阵可以由两幅图像(或者平面)中对应点对计算出来。前面已经提到过,一个完全射影变换具有8个自由度。根据对应点约束,每个对应点对可以写出两个方程,分别对应于x和y坐标。因此,计算单应性矩阵H需要4个对应点对。

DLT(Direct Linear Transformation,直接线性变换)是给定4个或者更多对应点对矩阵,来计算单应性矩阵H的算法。将单应性矩阵H作用在对应点对上,重新写出该方程,我们可以得到下面的方程:

[ − x 1 − y 1 − 1 0 0 0 x 1 x 1 ′ y 1 x 1 ′ x 1 ′ 0 0 0 − x 1 − y 1 − 1 x 1 y 1 ′ y 1 y 1 ′ y 1 ′ − x 2 − y 2 − 1 0 0 0 x 2 x 2 ′ y 2 x 2 ′ x 2 ′ 0 0 0 − x 2 − y 2 − 1 x 2 y 2 ′ y 2 y 2 ′ y 2 ′ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ] [ h 1 h 2 h 3 h 4 h 5 h 6 h 7 h 8 h 9 ] = 0 \begin{bmatrix}-x_1&-y_1&-1&0&0&0&x_1x_1^{\prime}&y_1x_1^{\prime}&x_1^{\prime}\\0&0&0&-x_1&-y_1&-1&x_1y_1^{\prime}&y_1y_1^{\prime}&y_1^{\prime}\\-x_2&-y_2&-1&0&0&0&x_2x_2^{\prime}&y_2x_2^{\prime}&x_2^{\prime}\\0&0&0&-x_2&-y_2&-1&x_2y_2^{\prime}&y_2y_2^{\prime}&y_2^{\prime}\\&\vdots&&\vdots&\vdots&\vdots&\vdots&\vdots\end{bmatrix}\begin{bmatrix}h_1\\h_2\\h_3\\h_4\\h_5\\h_6\\h_7\\h_8\\h_9\end{bmatrix}=\mathbf{0} x10x20y10y2010100x10x20y10y20101x1x1x1y1x2x2x2y2y1x1y1y1y2x2y2y2x1y1x2y2 h1h2h3h4h5h6h7h8h9 =0

或者Ah=0,其中A是一个具有对应点对二倍数量行数的矩阵。将这些对应点对方程的系数堆叠到一个矩阵中,我们可以使用SVD(Singular Value Decomposition,奇异值分解)算法找到H的最小二乘解。

下面是该算法的代码:

def H_from_points(fp, tp):"""使用线性DLT方法,计算单应性矩阵H,使fp映射到tp。点自动进行归一化"""if fp.shape != tp.shape:raise RuntimeError('number of points do not match')# 对点进行归一化(对数值计算很重要)# ---映射起始点---m = mean(fp[:2], axis=1)maxstd = max(std(fp[:2], axis=1)) + 1e-9C1 = diag([1/maxstd, 1/maxstd, 1])C1[0][2] = -m[0]/maxstdC1[1][2] = -m[1]/maxstdfp = dot(C1, fp)# ---映射对应点---m = mean(tp[:2], axis=1)maxstd = max(std(tp[:2], axis=1)) + 1e-9C2 = diag([1/maxstd, 1/maxstd, 1])C2[0][2] = -m[0]/maxstdC2[1][2] = -m[1]/maxstdtp = dot(C2, tp)# 创建用于线性方法的矩阵,对于每个对应对,在矩阵中会出现两行数值nbr_correspondences = fp.shape[1]A = zeros((2*nbr_correspondences, 9))for i in range(nbr_correspondences):A[2*i] = [-fp[0][i], -fp[1][i], -1, 0, 0, 0,tp[0][i]*fp[0][i], tp[0][i]*fp[1][i], tp[0][i]]A[2*i+1] = [0, 0, 0, -fp[0][i], -fp[1][i], -1,tp[1][i]*fp[0][i], tp[1][i]*fp[1][i], tp[1][i]]U, S, V = linalg.svd(A)H = V[8].reshape((3, 3))# 反归一化H = dot(linalg.inv(C2), dot(H, C1))# 归一化,然后返回return H / H[2, 2]

上面函数的第一步操作是检查点对的两个数组中点的数目是否相同。如果不相同,函数将会抛出异常信息。这对于写出稳健的代码来说非常有用。但是,为了使得代码例子更简单、更容易理解,我们在本书中仅在很少的例子中使用异常处理技巧。

3.1.2 仿射变换

由于仿射变换具有6个自由度,因此我们需要三个对应点对来估计矩阵H。通过将最后两个元素设置为0,即 h 7 = h 8 = 0 h_7 =h_8=0 h7=h8=0,仿射变换可以用上面的DLT算法估计得出。
下面是算法的关键代码部分:

def Haffine_from_points(fp, tp):"""计算 H,仿射变换,使得 tp 是 fp 经过仿射变换 H 得到的"""if fp.shape != tp.shape:raise RuntimeError('number of points do not match')# 对点进行归一化# --- 映射起始点 ---m = mean(fp[:2], axis=1)maxstd = max(std(fp[:2], axis=1)) + 1e-9C1 = diag([1/maxstd, 1/maxstd, 1])C1[0][2] = -m[0]/maxstdC1[1][2] = -m[1]/maxstdfp_cond = dot(C1, fp)# --- 映射对应点 ---m = mean(tp[:2], axis=1)C2 = C1.copy()  # 两个点集,必须都进行相同的缩放C2[0][2] = -m[0]/maxstdC2[1][2] = -m[1]/maxstdtp_cond = dot(C2, tp)# 因为归一化后点的均值为0,所以平移量为0A = concatenate((fp_cond[:2], tp_cond[:2]), axis=0)U, S, V = linalg.svd(A.T)# 如 Hartley 和 Zisserman 著的 Multiple View Geometry in Computer, Second Edition 所示,# 创建矩阵 B 和 Ctmp = V[:2].TB = tmp[:2]C = tmp[2:4]# 反归一化tmp2 = concatenate((dot(C, linalg.pinv(B)), zeros((2, 1))), axis=1)H = vstack((tmp2, [0, 0, 1]))H = dot(linalg.inv(C2), dot(H, C1))return H / H[2, 2]

同样地,类似于DLT算法,这些点需要经过预处理和去处理化操作。

3.2 图像扭曲

对图像块应用仿射变换,我们将其称为图像扭曲(或者仿射扭曲)。该操作不仅经常应用在计算机图形学中,而且经常出现在计算机视觉算法中。扭曲操作可以使用SciPy工具包中的ndimage包来简单完成。

以下为实验代码:

import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
from skimage import data, color# 读取示例图像
image = color.rgb2gray(data.astronaut())# 定义仿射变换矩阵
# 例如,这里是一个旋转矩阵和一个平移矩阵的组合
affine_matrix = np.array([[1.2, 0.2, -30],  # x轴的缩放和旋转,以及平移[0.1, 1.2, 20],   # y轴的缩放和旋转,以及平移[0, 0, 1]         # 齐次坐标的归一化因子
])# 对图像应用仿射变换
transformed_image = ndimage.affine_transform(image,affine_matrix[:2, :2],  # 2x2 仿射矩阵offset=affine_matrix[:2, 2],  # 平移偏移mode='reflect'  # 边界处理模式
)# 显示原始和变换后的图像
plt.figure(figsize=(10, 5))plt.subplot(1, 2, 1)
plt.title('Original Image')
plt.imshow(image, cmap='gray')
plt.axis('off')plt.subplot(1, 2, 2)
plt.title('Transformed Image')
plt.imshow(transformed_image, cmap='gray')
plt.axis('off')plt.show()

在这里插入图片描述

实验图1 图像扭曲处理结果

3.2.1 图像中的图像

仿射扭曲的一个简单例子是,将图像或者图像的一部分放置在另一幅图像中,使得它们能够和指定的区域或者标记物对齐。

import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
from skimage import io, colordef image_in_image(background, overlay, position):"""将图像 overlay 放置到图像 background 中的指定位置。:param background: 背景图像:param overlay: 要放置的图像:param position: 放置 overlay 的坐标 (x, y) 元组:return: 带有 overlay 的背景图像"""# 确保 overlay 图像的尺寸h, w = overlay.shape[:2]# 生成仿射变换矩阵,将 overlay 图像的四个角点对齐到背景图像中的指定区域src_points = np.array([[0, 0],  # overlay 的左上角[w, 0],  # overlay 的右上角[w, h],  # overlay 的右下角[0, h]  # overlay 的左下角])dst_points = np.array([[position[0], position[1]],  # 背景图像中放置位置的左上角[position[0] + w, position[1]],  # 背景图像中放置位置的右上角[position[0] + w, position[1] + h],  # 背景图像中放置位置的右下角[position[0], position[1] + h]  # 背景图像中放置位置的左下角])# 构建矩阵 A 和向量 b 以求解仿射变换矩阵A = []b = []for i in range(4):A.append([src_points[i][0], src_points[i][1], 1, 0, 0, 0, -dst_points[i][0] * src_points[i][0],-dst_points[i][0] * src_points[i][1]])A.append([0, 0, 0, src_points[i][0], src_points[i][1], 1, -dst_points[i][1] * src_points[i][0],-dst_points[i][1] * src_points[i][1]])b.append(dst_points[i][0])b.append(dst_points[i][1])A = np.array(A)b = np.array(b)# 通过最小二乘法求解仿射变换矩阵h = np.linalg.lstsq(A, b, rcond=None)[0]H = np.append(h, [1]).reshape(3, 3)# 将 overlay 图像进行仿射变换transformed_overlay = ndimage.affine_transform(overlay,H[:2, :2],offset=H[:2, 2],output_shape=background.shape,mode='constant',cval=0)# 合并图像mask = (transformed_overlay > 0).astype(float)result = background.copy()result = result * (1 - mask) + transformed_overlay * maskreturn result# 示例使用
if __name__ == "__main__":# 读取内置示例图像background = color.rgb2gray(io.imread('img.png'))  # 背景图像overlay = color.rgb2gray(io.imread('python.png'))  # 要放置的图像position = (100, 100)  # 放置位置(x, y)# 应用函数result_image = image_in_image(background, overlay, position)# 显示结果plt.figure(figsize=(10, 5))plt.subplot(1, 3, 1)plt.title('Background Image')plt.imshow(background, cmap='gray')plt.axis('off')plt.subplot(1, 3, 2)plt.title('Overlay Image')plt.imshow(overlay, cmap='gray')plt.axis('off')plt.subplot(1, 3, 3)plt.title('Result Image')plt.imshow(result_image, cmap='gray')plt.axis('off')plt.show()

在这里插入图片描述

实验图2 处理结果

3.2.2 分段仿射扭曲

对应点对集合之间最常用的扭曲方式:分段仿射扭曲。给定任意图像的标记点,通过将这些点进行三角剖分,然后使用仿射扭曲来扭曲每个三角形,我们可以将图像和另一幅图像的对应标记点扭曲对应。对于任何图形和图像处理库来说,这些都是最基本的操作。
为了三角化这些点,我们经常使用狄洛克三角剖分方法。在Matplotlib(但是不在PyLab 库中)中有狄洛克三角剖分,我们可以用下面的方式使用它:

以下是实验代码:

import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage
from scipy.spatial import Delaunayx,y = np.array(np.random.standard_normal((2,100)))
tri = Delaunay(np.c_[x, y]).simplices
plt.figure() 
for t in tri:t_ext = [t[0], t[1], t[2], t[0]] # 将第一个点加入到最后plt.plot(x[t_ext],y[t_ext],'r')
plt.plot(x,y,'*')
plt.axis('off')
plt.show()

在这里插入图片描述

实验图3 处理结果

3.2.3 图像配准

图像配准是对图像进行变换,使变换后的图像能够在常见的坐标系中对齐。配准可以是严格配准,也可以是非严格配准。为了能够进行图像对比和更精细的图像分析,图像配准是一步非常重要的操作。

3.3 创建全景图

在同一位置(即图像的照相机位置相同)拍摄的两幅或者多幅图像是单应性相关的(如图3-9所示)。我们经常使用该约束将很多图像缝补起来,拼成一个大的图像来创建全景图像。
在这里插入图片描述

图3-9 瑞典隆德主要大学建筑的5幅图像。这些图像都是从同一个视点拍摄的

3.3.1 RANSAC

RANSAC是“RANdom SAmple Consensus”(随机一致性采样)的缩写。该方法是用来找到正确模型来拟合带有噪声数据的迭代方法。给定一个模型,例如点集之间的单应性矩阵,RANSAC基本的思想是,数据中包含正确的点和噪声点,合理的模型应该能够在描述正确数据点的同时摒弃噪声点。

RANSAC的标准例子:用一条直线拟合带有噪声数据的点集。简单的最小二乘在该例子中可能会失效,但是RANSAC能够挑选出正确的点,然后获取能够正确拟合的直线。

在这里插入图片描述

图3-10 使用RANSAC算法用一条直线来拟合包含噪声数据点集

3.3.2 拼接图像

估计出图像间的单应性矩阵(使用RANSAC算法),现在我们需要将所有的图像扭曲到一个公共的图像平面上。通常,这里的公共平面为中心图像平面(否则,需要进行大量变形)。一种方法是创建一个很大的图像,比如图像中全部填充0,使其和中心图像平行,然后将所有的图像扭曲到上面。由于我们所有的图像是由照相机水平旋转拍摄的,因此我们可以使用一个较简单的步骤:将中心图像左边或者右边的区域填充0,以便为扭曲的图像腾出空间。以下为示例代码:

def panorama(H, fromim, toim, padding=2400, delta=2400):""" 使用单应性矩阵 H(使用 RANSAC 健壮性估计得出),协调两幅图像,创建水平全景图像。结果为一幅和 toim 具有相同高度的图像。padding 指定填充像素的数目,delta 指定额外的平移量。 """# 检查图像是灰度图像,还是彩色图像is_color = len(fromim.shape) == 3# 用于 geometric_transform() 的单应性变换def transf(p):p2 = np.dot(H, [p[0], p[1], 1])return (p2[0] / p2[2], p2[1] / p2[2])if H[1, 2] < 0:  # fromim 在右边print('warp - right')# 变换 fromimif is_color:# 在目标图像的右边填充 0toim_t = np.hstack((toim, np.zeros((toim.shape[0], padding, 3))))fromim_t = np.zeros((toim.shape[0], toim.shape[1] + padding, toim.shape[2]))for col in range(3):fromim_t[:, :, col] = ndimage.geometric_transform(fromim[:, :, col], transf, (toim.shape[0], toim.shape[1] + padding))else:# 在目标图像的右边填充 0toim_t = np.hstack((toim, np.zeros((toim.shape[0], padding))))fromim_t = ndimage.geometric_transform(fromim, transf, (toim.shape[0], toim.shape[1] + padding))else:  # fromim 在左边print('warp - left')# 为了补偿填充效果,在左边加入平移量H_delta = np.array([[1, 0, 0], [0, 1, -delta], [0, 0, 1]])H = np.dot(H, H_delta)# 变换 fromimif is_color:# 在目标图像的左边填充 0toim_t = np.hstack((np.zeros((toim.shape[0], padding, 3)), toim))fromim_t = np.zeros((toim.shape[0], toim.shape[1] + padding, toim.shape[2]))for col in range(3):fromim_t[:, :, col] = ndimage.geometric_transform(fromim[:, :, col], transf, (toim.shape[0], toim.shape[1] + padding))else:# 在目标图像的左边填充 0toim_t = np.hstack((np.zeros((toim.shape[0], padding)), toim))fromim_t = ndimage.geometric_transform(fromim, transf, (toim.shape[0], toim.shape[1] + padding))# 协调后返回(将 fromim 放置在 toim 上)if is_color:# 所有非黑色像素alpha = ((fromim_t[:, :, 0] > 0) | (fromim_t[:, :, 1] > 0) | (fromim_t[:, :, 2] > 0))for col in range(3):toim_t[:, :, col] = fromim_t[:, :, col] * alpha + toim_t[:, :, col] * ~alphaelse:alpha = (fromim_t > 0)toim_t = fromim_t * alpha + toim_t * ~alphareturn toim_t

对于通用的geometric_transform() 函数,我们需要指定能够描述像素到像素间映射的函数。在这个例子中,transf()函数就是该指定的函数。该函数通过将像素和H相乘,然后对齐次坐标进行归一化来实现像素间的映射。通过查看H中的平移量,我们可以决定应该将该图像填补到左边还是右边。当该图像填补到左边时,由于目标图像中点的坐标也变化了,所以在“左边”情况中,需要在单应性矩阵中加入平移。简单起见,我们同样使用0像素的技巧来寻找alpha图。现在在图像中使用该操作,函数如下所示:

# 扭曲图像
delta = 2000  # 用于填充和平移# 读取图像
im1 = np.array(Image.open(imname[1]))
im2 = np.array(Image.open(imname[2]))# 图像拼接
im_12 = warp.panorama(H_12, im1, im2, delta, delta)im1 = np.array(Image.open(imname[0]))
im_02 = warp.panorama(np.dot(H_12, H_01), im1, im_12, delta, delta)im1 = np.array(Image.open(imname[3]))
im_32 = warp.panorama(H_32, im1, im_02, delta, delta)im1 = np.array(Image.open(imname[j + 1]))  # 确保 imname[j + 1] 是一个有效的索引
im_42 = warp.panorama(np.dot(H_32, H_43), im1, im_32, delta, 2 * delta)

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

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

相关文章

vue3+vite+axios+mock从接口获取模拟数据实战

文章目录 一、安装相关组件二、在vite.config.js中配置vite-plugin-mock插件三、实现mock服务四、调用api接口请求mock数据方法一、直接使用axios 请求mock 数据方法二、对axios进行封装统一请求mock数据 五、实际运行效果 在用Vue.js开发前端应用时通常要与后端服务进行交互&a…

Linux Ubuntu 桌面环境概览

目录 一、Ubuntu桌面环境的特点&#xff1a;简约而不简单 二、Ubuntu桌面环境的组成&#xff1a;细节见真章 Ubuntu&#xff0c;这个名字在开源操作系统领域如同璀璨明星&#xff0c;以其卓越的桌面环境和用户体验赢得了全球用户的青睐。今天&#xff0c;就让我们一起深入探索…

图神经网络GNN的前世今生

GNN 图神经网络&#xff08;Graph Neural Network&#xff0c;简称GNN&#xff09;已经成为处理图形结构数据的一种强大工具&#xff0c;广泛应用于社交网络分析、知识图谱、推荐系统等领域。在本文中&#xff0c;我们将深入探讨图神经网络的历史背景、关键的发展阶段以及未来可…

强制输出wParam 和 lParam,会是什么内容?

当你“强制输出” wParam 和 lParam 时&#xff0c;通常是指在调试或日志记录中直接查看这些参数的原始数值。这些值的内容会根据消息类型的不同而有所变化&#xff0c;因为 wParam 和 lParam 的意义和使用取决于它们所在的消息上下文。 下面我将详细讨论在不同消息情况下&…

白酒与家庭聚会:温馨与和谐的时光

在繁忙的都市生活中&#xff0c;家庭聚会是每个人心中那份较温暖的港湾。每当夜幕降临&#xff0c;灯火通明&#xff0c;家人们围坐在一起&#xff0c;谈笑风生&#xff0c;那份温馨与和谐仿佛能够驱散一切疲惫。而在这个温馨的时刻&#xff0c;白酒——豪迈白酒&#xff08;HO…

Python 爬虫入门(十二):正则表达式「详细介绍」

Python 爬虫入门&#xff08;十二&#xff09;&#xff1a;正则表达式 前言一、正则表达式的用途二、正则表达式的基本组成元素2.1 特殊字符2.2 量词2.3 位置锚点2.4 断言2.5 字符集2.6 字符类2.6.1 基本字符类2.6.2 常见字符类简写2.6.3 POSIX字符类2.6.4 组合使用 三、 正则表…

如何使用ssm实现亿互游在线平台设计与开发+vue

TOC ssm118亿互游在线平台设计与开发vue 绪论 1.1研究背景 时代的发展&#xff0c;我们迎来了数字化信息时代&#xff0c;它正在渐渐的改变着人们的工作、学习以及娱乐方式。计算机网络&#xff0c;Internet扮演着越来越重要的角色&#xff0c;人们已经离不开网络了&#x…

2024世界机器人大会盛大开幕,卓翼飞思携无人智能领域产品集中亮相 !

开放创新 聚享未来&#xff01;万众瞩目的2024世界机器人大会暨博览会于8月21日在北京亦创国际会展中心盛大开幕。大会聚焦机器人技术与产业前沿趋势&#xff0c;展示机器人创新应用赋能千行百业的多元场景&#xff0c;全球顶尖的机器人科学家、行业领袖、创新精英汇聚一堂&…

Pandas教程:使用Pandas合并多个Excel文件

目录 1. 环境准备 1.1 安装Pandas 1.2 准备工作 2. 基本概念 2.1 Pandas简介 2.2 DataFrame的基本操作 3. 读取Excel文件 4. 合并多个Excel文件 4.1 获取文件列表 4.2 读取并合并数据 4.3 处理重复数据 5. 数据存储 6. 完整示例代码 7.代码优化 7.1用类去重新组…

vue3动态引入图片不显示问题

方法1.(打包后动态引用的图片未被打包入工程中,webpack,vite) 1.图片放到public 目录会更省事&#xff0c;不管是开发环境还是生产环境&#xff0c;可以始终以根目录保持图片路径的一致. 假设&#xff1a; 静态文件目录&#xff1a;src/assets/images/ 我们的目标静态文件在 …

Python实现打印http请求信息例子解析

示例代码 import http.clientdef print_http_info(host, path):conn http.client.HTTPConnection(host)method GETurl pathprint(f"{- * 30} 请求信息 {- * 30}")print(f"主机: {host}")print(f"方法: {method}")print(f"URL: {url}&qu…

深入理解Spring Boot日志框架与配置

目录 Spring Boot日志框架概述Spring Boot默认日志框架&#xff1a;Logback日志配置文件日志级别的调整日志输出配置日志格式化日志轮转和归档集成其他日志框架日志管理工具最佳实践总结 Spring Boot日志框架概述 Spring Boot 支持多种日志框架&#xff0c;如 Logback、Log4…

使用HAL库实现按键控制LED和蜂鸣器

下载STM32CubeMX实现项目的初始配置&#xff08;寄存器操作)&#xff0c;下载keil对程序进行编译烧写 在STM32CubeMX中将PB0/PB1设置为输入引脚作为按键&#xff0c;PA6/PA4设置为输出引脚作为led和Beep&#xff0c;将按键引脚设置为上拉输入&#xff1a; 创建项目完成后在kei…

【jvm】程序计数器的特征

目录 1. 说明2. 线程私有3. 存储指令地址4. 不会发生内存溢出5. 生命周期与线程相同 1. 说明 1.JVM&#xff08;Java虚拟机&#xff09;中的程序计数器&#xff08;Program Counter Register&#xff09;&#xff0c;简称PC寄存器&#xff0c;具有几个显著的特征&#xff0c;这…

C语言 | Leetcode C语言题解之第365题水壶问题

题目&#xff1a; 题解&#xff1a; bool canMeasureWater(int jug1Capacity, int jug2Capacity, int targetCapacity) {int j1 jug1Capacity < jug2Capacity ? jug1Capacity : jug2Capacity, j2 jug1Capacity > jug2Capacity ? jug1Capacity : jug2Capacity;if (ta…

Umi-OCR 文字识别工具

免费开源的离线orc识别功能 git地址 感谢大佬的贡献 Umi-OCR 文字识别工具 使用说明 • 下载地址 • 更新日志 • 提交Bug 免费&#xff0c;开源&#xff0c;可批量的离线OCR软件 适用于 Windows7 x64 、Linux x64 免费&#xff1a;本项目所有代码开源&#x…

推荐系统三十六式学习笔记:产品篇36 | 组建推荐团队及工程师的学习路径

目录 团队组建个人成长总结 如果你是老板&#xff0c;或者是公司里的推荐系统包工头&#xff0c;那么你一定会关心&#xff1a;要凑齐多少人才能开始搬砖&#xff1f; 一个推荐系统复杂度没有上限&#xff0c;但是有最低标准&#xff0c;所以下面在估算推荐系统团队规模时&…

SpringBoot (面试篇)

什么是SpringBoot 通过SpringBoot&#xff0c;可以轻松的创建独立的&#xff0c;基于生产级别的Spring的应用程序&#xff0c;您可以“运行”它们。大多数SpringBoot应用程序要最少的Sprig配置 为什么要用SpringBoot 快速开发 快速整合 配置简化 内嵌服务容器 SpringBoot与…

Verilog刷题笔记59

题目: Exams/m2014 q6c 解题&#xff1a; module top_module (input [6:1] y,input w,output Y2,output Y4);assign Y2y[1]&w0;assign Y4(y[2]&w1)|(y[3]&w1)|(y[5]&w1)|(y[6]&w1);endmodule结果正确: 注意点: 起初&#xff0c;我的代码有错误,代码如下…

9 正则表达式:Java爬虫和正则表达式、String中的正则表达式方法(基本语法7)

文章目录 前言一、正则表达式1 [ ] 语法(1)[ABC] 和 [^ABC](2)[A-Z]和[a-zA-Z]小总结2 特殊字符语法(\w 这些)3 数量符4 \ 、()、 |5 锚点 ^ 和 $,\b,\B6 (?i) : 忽略其后面的大小写 ---- 这个Java是可以的,其他语言我不知道(正则表达式虽然大多通用,但也有部分是…