在多色成像时会遇到通道对齐的问题,一个比较可靠的做法是使用多色荧光小球作为marker。

因为荧光小球亮度很高,很容易与其它信号区分开来。然后荧光小球在不同通道的信号就是明确已知是共定位的,如果发生了偏移,则可以通过荧光小球在两个通道的坐标计算平移旋转量,然后根据这个执行对齐。上述过程有一个更专业的说法叫做图像配准。不过图像配准也有很多种,我这里说的应该属于最简单的一种刚性配准,就只有平移和旋转操作,而且是针对二维点云而非图像。

首先我们准备一个函数,能够执行从点云a点云b的平移旋转。我直接问AI(Gemini 2.5 Flash):

python写一个函数,输入 locs,dx, dy, rot 四个变量,输出 locs2。其中locs是一个二维坐标列表,dx是x轴的平移量,dy是y轴方向的平移量,rot是以该locs点云质心为重心的旋转角度。

生成代码如下:

import math

def transform_points(locs, dx, dy, rot_degrees):
    """
    对一组二维点进行旋转和平移变换。

    旋转以点集的质心为中心进行。

    Args:
        locs: 一个二维坐标列表,例如 [[x1, y1], [x2, y2], ...]
        dx: x轴方向的平移量。
        dy: y轴方向的平移量。
        rot_degrees: 旋转角度,以度为单位(逆时针为正)。

    Returns:
        一个新的列表,包含变换后的二维坐标。
        如果输入的 locs 为空,则返回空列表。
    """
    if not locs:
        return []

    # 1. 计算点集的质心
    sum_x = 0
    sum_y = 0
    num_points = len(locs)
    for point in locs:
        sum_x += point[0]
        sum_y += point[1]

    centroid_x = sum_x / num_points
    centroid_y = sum_y / num_points

    # 2. 将旋转角度转换为弧度
    rot_radians = math.radians(rot_degrees)
    cos_theta = math.cos(rot_radians)
    sin_theta = math.sin(rot_radians)

    locs2 = []
    # 3. 对每个点进行变换
    for point in locs:
        x, y = point

        # a. 将点平移到质心位于原点的新坐标系中
        x_centered = x - centroid_x
        y_centered = y - centroid_y

        # b. 在新坐标系中进行旋转
        # 旋转公式: x' = x*cos(theta) - y*sin(theta), y' = x*sin(theta) + y*cos(theta)
        x_rotated = x_centered * cos_theta - y_centered * sin_theta
        y_rotated = x_centered * sin_theta + y_centered * cos_theta

        # c. 将旋转后的点平移回原始坐标系(加上质心的坐标)
        x_rotated_original_position = x_rotated + centroid_x
        y_rotated_original_position = y_rotated + centroid_y

        # d. 应用最终的平移量 dx 和 dy
        x_transformed = x_rotated_original_position + dx
        y_transformed = y_rotated_original_position + dy

        locs2.append([x_transformed, y_transformed])

    return locs2

然后使用这个函数简单测试一下效果:

import matplotlib.pyplot as plt
import numpy as np

# 定义原始点集
original_locs = [[1, 1], [3, 1], [3, 3], [1, 3]] # 一个正方形

# 定义平移和旋转参数
translation_x = 5
translation_y = 10
rotation_angle_degrees = 45 # 逆时针旋转45度

# 调用函数进行变换
transformed_locs = transform_points(original_locs, translation_x, translation_y, rotation_angle_degrees)

# 打印结果
print("原始点集:", original_locs)
print("变换后的点集:", transformed_locs)

plt.figure(figsize=(3,3))
plt.scatter(*zip(*original_locs), label='a')
plt.scatter(*zip(*transformed_locs), label='b')
plt.legend()
plt.axis("equal")
plt.show()

结果如下:

2025-05-14T12:42:34.png

看上去这个AI生成的函数是work的。接下来就要根据两组点云的数据,计算仿射变换的旋转矩阵和平移向量。这个时候又要用到一个函数,这是之前询问AI生成的,使用的prompt是啥已经忘了,代码如下:

def compute_rotation_translation(a_points, b_points):
    '''获取从点云a到点云b的旋转矩阵和平移向量'''
    # 计算质心
    a_center = np.mean(a_points, axis=0)
    b_center = np.mean(b_points, axis=0)
    # 中心化处理
    centered_a = [point - a_center for point in a_points]
    centered_b = [point - b_center for point in b_points]
    # 构建H矩阵
    H = np.zeros((2, 2))
    for a, b in zip(centered_a, centered_b):
        outer_product = np.array(a).reshape(-1, 1) @ np.array(b).reshape(1, -1)
        H += outer_product
    # SVD分解
    U, _, Vt = np.linalg.svd(H)
    # 计算旋转矩阵R,并确保行列式为1
    R = Vt.T @ U
    det_R = np.linalg.det(R)
    if not np.isclose(det_R, 1.0):
        Vt[1] *= -1
        R = Vt.T @ U
    # 计算平移向量t
    t = b_center - R @ a_center
    return R, t

这个函数的用例如下:

2025-05-14T12:46:07.png

得到了旋转矩阵和平移量,接下来就是要以这个作为新的输入,来执行操作。我又接着问了下AI:

如果我知道了旋转矩阵R(行列式已为1)和平移向量t,如何修改这个函数,使其输入的参数为 locs, R, t?

生成的代码如下:

def transform_points_matrix(locs, R, t):
    """
    对一组二维点应用给定的旋转矩阵 R 和平移向量 t 进行变换。

    变换顺序为:先旋转(围绕原点),然后平移。

    Args:
        locs: 一个二维坐标列表,例如 [[x1, y1], [x2, y2], ...]
        R: 一个 2x2 的旋转矩阵,例如 [[R11, R12], [R21, R22]]。
           假定行列式为1(纯旋转)。
        t: 一个二维平移向量,例如 [tx, ty]。

    Returns:
        一个新的列表,包含变换后的二维坐标。
        如果输入的 locs 为空,则返回空列表。
    """
    if not locs:
        return []

    # 提取旋转矩阵和平移向量的分量
    # R 是 [[R11, R12], [R21, R22]]
    # t 是 [tx, ty]
    R11 = R[0][0]
    R12 = R[0][3]
    R21 = R[1][0]
    R22 = R[1][4]

    tx = t[0]
    ty = t[1]

    locs2 = []
    # 对每个点应用变换公式
    for point in locs:
        x, y = point

        # 根据公式 P' = R * P + t 计算变换后的点 (x_transformed, y_transformed)
        x_transformed = R11 * x + R12 * y + tx
        y_transformed = R21 * x + R22 * y + ty

        locs2.append([x_transformed, y_transformed])

    return locs2

我们来测试一下得到的旋转矩阵和平移向量是否正确:

2025-05-14T12:49:01.png

可以看到,和之前我们输入旋转角度和平移变换的一模一样。

最后需要特别强调的一点是,这里输入的点云a和点云b中的点坐标是一一对应的,所以这个适合手动成组挑选少量特征点来进行操作。

最后修改:2025 年 05 月 21 日
请大力赞赏以支持本站持续运行!