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

news2024/11/13 21:50:45

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-9
    C1 = diag([1/maxstd, 1/maxstd, 1])
    C1[0][2] = -m[0]/maxstd
    C1[1][2] = -m[1]/maxstd
    fp = dot(C1, fp)

    # ---映射对应点---
    m = mean(tp[:2], axis=1)
    maxstd = max(std(tp[:2], axis=1)) + 1e-9
    C2 = diag([1/maxstd, 1/maxstd, 1])
    C2[0][2] = -m[0]/maxstd
    C2[1][2] = -m[1]/maxstd
    tp = 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-9
    C1 = diag([1/maxstd, 1/maxstd, 1])
    C1[0][2] = -m[0]/maxstd
    C1[1][2] = -m[1]/maxstd
    fp_cond = dot(C1, fp)

    # --- 映射对应点 ---
    m = mean(tp[:2], axis=1)
    C2 = C1.copy()  # 两个点集,必须都进行相同的缩放
    C2[0][2] = -m[0]/maxstd
    C2[1][2] = -m[1]/maxstd
    tp_cond = dot(C2, tp)

    # 因为归一化后点的均值为0,所以平移量为0
    A = 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 和 C
    tmp = V[:2].T
    B = 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, color


def 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 * mask

    return 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 Delaunay
 
x,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')

        # 变换 fromim
        if is_color:
            # 在目标图像的右边填充 0
            toim_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:
            # 在目标图像的右边填充 0
            toim_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)

        # 变换 fromim
        if is_color:
            # 在目标图像的左边填充 0
            toim_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:
            # 在目标图像的左边填充 0
            toim_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] * ~alpha
    else:
        alpha = (fromim_t > 0)
        toim_t = fromim_t * alpha + toim_t * ~alpha

    return 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.coloradmin.cn/o/2071848.html

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

相关文章

音视频封装格式之FLV

FLV&#xff08;Flash Video&#xff09;是一种常见的视频文件格式&#xff0c;FLV 格式最初是由 Adobe 公司开发的&#xff0c;旨在为网络视频提供一种高效、可扩展且易于流式传输的解决方案。随着在线视频的迅速发展&#xff0c;FLV 因其良好的兼容性和流式传输性能&#xff…

4.2较难的栈和队列OJ

本篇博客来手撕三道稍有难度的栈和队列相关OJ&#xff0c;题目均已插入超链接&#xff0c;点击即可跳转~ 一、设计循环队列 1、题目描述 设计你的循环队列实现。 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;先进先出&#xff0c;即first in fir…

day39.动态规划+MySQL数据库复习

一.动态规划: 198.打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定…

深入浅出消息队列----【Broker 集群】

深入浅出消息队列----【Broker 集群】 单 master多 master多 master 多 slave 异步复制多 master 多 slave 同步复制Dledger 本文仅是文章笔记&#xff0c;整理了原文章中重要的知识点、记录了个人的看法 文章来源&#xff1a;编程导航-鱼皮【yes哥深入浅出消息队列专栏】 Brok…

LeetCode100.删除链表的倒数第 N 个结点

1. 题目大意 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 进阶&#xff1a;你能尝试使用一趟扫描实现吗&#xff1f; 2. 思路分析 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]我们可以先遍历一遍链表求出…

Data Localization Software Architecture - SAAS Localization CyberWin Future

一、Future Window Industry Application Cross-platform Architecture The cross-platform architecture of Future Window adopts Hybird (hybrid mode mobile application), which is a mobile application development model that combines the characteristics of native …

golang学习笔记——grom连接mysql

GORM 指南 The fantastic ORM library for Golang aims to be developer friendly. Golang的ORM库旨在对开发人员友好。 特性 全功能 ORM关联 (Has One&#xff0c;Has Many&#xff0c;Belongs To&#xff0c;Many To Many&#xff0c;多态&#xff0c;单表继承)Create&…

【Redis】渐进式遍历和数据库管理

渐进式遍历和数据库管理 渐进式遍历scan 数据库管理切换数据库清除数据库 渐进式遍历 Redis 使⽤ scan 命令进⾏渐进式遍历键&#xff0c;进⽽解决直接使⽤ keys 获取键时可能出现的阻塞问题。每次 scan 命令的时间复杂度是 O(1)&#xff0c;但是要完整地完成所有键的遍历&…

数学建模笔记(三):拟合算法

前面介绍的插值方法&#xff0c;我们可以发现他的特点在于最终得到的曲线一定要经过已经给出的样本点的&#xff0c;而这次要介绍的拟合方法的区别在于&#xff0c;拟合不要求曲线一定要经过所有的样本点&#xff0c;只要这个曲线与样本点之间的误差足够小&#xff0c;距离足够…

unity AssetBundle 使用_什么是AssetBundle_导入必要的插件_创建AssetBundles_AB包资源下载_大文件下载

一、什么是AssetBundle&#xff1f; 定义AssetBundle。 AssetBundle 是一个存档文件&#xff0c;包含可在运行时由 Unity 加载的特定于平台的非代码资源&#xff08;比如模型、纹理、预制件、音频剪辑甚至整个场景&#xff09;。AssetBundle 可以表示彼此之间的依赖关系&…

PD取电快充协议方案

PD快充协议是通过调整电压和电流来提供不同的充电功率。它采用了一种基于USB-C端口的通信协议&#xff0c;实现了充电器于设备之间的信息交换。在充电过程中设备会向充电器发出请求&#xff0c;要求提供不同的电压和电流&#xff0c;充电器接收到请求后&#xff0c;会根据设备的…

数据本地化软件架构-SAAS本地化未来之窗行业应用跨平台架构

一、未来之窗行业应用跨平台架构 未来之窗跨平台架构采用Hybird&#xff08;混合模式移动应用&#xff09;是一种结合了原生应用&#xff08;Native App&#xff09;和网页应用&#xff08;Web App&#xff09;特点的移动应用开发模式。 二、特点 2.1. 开发成本相对较低 - 与…

C++码表之Unicode

今日诗词&#xff1a; 折花逢驿使&#xff0c;寄与陇头人。 江南无所有&#xff0c;聊赠一枝春。 ——《赠范晔诗》【南北朝】陆凯 引言&#xff1a; 上一期我们说到了ASCII码表&#xff0c;这是一种现如今不是那么通用的机制&#xff0c;随着计算机的普及&#xff0c;越来越…

电脑ip地址为什么会自己变更?电脑ip怎么改

在当今这个高度依赖互联网的时代&#xff0c;IP地址作为网络设备的身份标识&#xff0c;其稳定性和可管理性对于网络通信的顺畅至关重要。然而&#xff0c;许多用户可能会遇到电脑IP地址自动变更的情况&#xff0c;这不仅可能影响到网络连接的稳定性&#xff0c;还可能给远程访…

怎么使用win10录屏快捷键 ?分享三款录屏工具

录屏软件在我们日常生活中越来越常见&#xff0c;无论是做教程、记录游戏精彩瞬间还是进行远程教学&#xff0c;一个好的录屏软件都能让我们的生活变得更加便捷。不少人也会选怎win10自带的录屏&#xff0c;使用WIN10 录屏快捷键【winG】&#xff0c;就可以唤出录屏工具&#x…

【Node】【1】node和nvm安装

安装nvm、node、npm 安装node 18 &#xff0c;最简单的办法是使用nvm&#xff0c;就不用手动安装了&#xff0c;那么就得先安装nvm。 NVM 是Node Version Manager&#xff0c;用于管理 Node.js 版本。你设备上的不同项目可能使用不同版本的 Node.js。通过 nvm&#xff0c;用户…

Redis | 非关系型数据库Redis的初步认识

本节内容相对理论&#xff0c;着重看基础通用命令这一节 Redis 非关 kv型{字典} 概念应用ubuntu安装配置 windows添加密码 可能问题【ubuntu】远程连接 基础通用命令 ⭐ 概念 特点&#xff1a; 1、开源的&#xff0c;使用C编写&#xff0c;基于内存且支持持久化 2、没有表 支持…

ssrf例题分析

我们第一步先在ubuntu中解压web-ssrfme.zip&#xff0c;更新镜像后重启容器。 我们可以看到代码中成功拉取到ssrfme镜像 ig(preg_match(/file\:\/\/&#xff5c;dict\:\/\/.&#xff5c;\.\.\/&#xff5c;127.0.0.1&#xff5c;localhost/is,$url,$match)) 使用端口访问文件&…

【Linux系列】SH 与 BASH 的区别:深入解析与使用案例

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Leetcode JAVA刷刷站(93)复原IP地址

一、题目概述 二、思路方向 为了解决这个问题&#xff0c;我们可以使用回溯算法。回溯算法在这里非常适用&#xff0c;因为我们需要尝试在字符串中的不同位置插入点&#xff08;.&#xff09;来分割出可能的IP地址段&#xff0c;并且需要验证每个段是否满足条件&#xff08;即…