ros标定源码判定条件过程

记录ros自带标定源码的实现过程
ros标定源码:ros标定源码

运行命令行

rosrun camera_calibration cameracalibrator.py --size 6x7 --square 0.043 image:=/camera_image
lvfu@lvfu-ThinkPad-E14:~/Project_of_calibration0325/Project_of_calibration/image_pipeline-melodic$ rosrun camera_calibration cameracalibrator.py --size 6x7 --square 0.043 image:=/camera_image
Gtk-Message: 10:02:14.031: Failed to load module "canberra-gtk-module"
*** Added sample 1, p_x = 0.641, p_y = 0.446, p_size = 0.109, skew = 0.018
*** Added sample 2, p_x = 0.427, p_y = 0.452, p_size = 0.111, skew = 0.023
*** Added sample 3, p_x = 0.176, p_y = 0.479, p_size = 0.102, skew = 0.023
*** Added sample 4, p_x = 0.044, p_y = 0.461, p_size = 0.134, skew = 0.143
*** Added sample 5, p_x = 0.129, p_y = 0.428, p_size = 0.264, skew = 0.077
*** Added sample 6, p_x = 0.348, p_y = 0.416, p_size = 0.274, skew = 0.026
*** Added sample 7, p_x = 0.553, p_y = 0.417, p_size = 0.264, skew = 0.044
*** Added sample 8, p_x = 0.700, p_y = 0.479, p_size = 0.251, skew = 0.048
*** Added sample 9, p_x = 0.908, p_y = 0.510, p_size = 0.200, skew = 0.099
*** Added sample 10, p_x = 0.295, p_y = 0.521, p_size = 0.163, skew = 0.023
*** Added sample 11, p_x = 0.024, p_y = 0.532, p_size = 0.116, skew = 0.001
*** Added sample 12, p_x = 0.105, p_y = 0.561, p_size = 0.114, skew = 0.093
*** Added sample 13, p_x = 0.281, p_y = 0.551, p_size = 0.075, skew = 0.101
*** Added sample 14, p_x = 0.758, p_y = 0.674, p_size = 0.110, skew = 0.161
*** Added sample 15, p_x = 0.659, p_y = 0.792, p_size = 0.122, skew = 0.170
*** Added sample 16, p_x = 0.510, p_y = 0.895, p_size = 0.130, skew = 0.124

在有bag包运行的情况下对应的运行结果输出如上,记录四个参数分别为p_x, p_y, p_size, skew(翻转程度),一行代表捕获到的一张图片,如:

请添加图片描述一些参数编辑的地方,以及对参数的解释。
--size : 标定版点的个数:NxM
--square : 标定版小格子以米为单位的大小:0.043
image : 话题名

camera_calibration cameracalibrator.py

 group.add_option("-s", "--size",
                     action="append", default=[],
                     help="chessboard size as NxM, counting interior corners (e.g. a standard chessboard is 7x7)")
 group.add_option("-q", "--square",
                     action="append", default=[],
                     help="chessboard square size in meters")

此node的定义在camera_calibration/src/camera_calibration/camera_calibrator.py
node入口的定义在此文件内,如下:

class OpenCVCalibrationNode(CalibrationNode):
    """ Calibration node with an OpenCV Gui """

此文件中的CalibrationNode类别调用camera_calibration/src/camera_calibration/calibrator.py里的class MonoCalibrator(Calibrator):类别,也就是下面这个部分,来检测输入的图片里面是否有棋盘格,并用色彩显示角点。


下采样输入图片

使用OpenCV库来对输入图像进行下采样(downsampling)到近似VGA分辨率(通常为640x480像素),并在缩小后的图像上检测棋盘格标定目标(calibration target)的角点。之后,我们可以将这些角点坐标按照下采样的比例放大回原始图像尺寸。

/camera_calibration/calibrator.py

当图像尺寸(总像素个数)大于VGA时,将图像缩放到近似VGA(像素个数比率的平方根),并以浮点数记录x和y方向的缩放scale。此后利用get_coners()成员函数检查角点。get_coners()函数进一步调用_get_corners()私有函数。

_get_corners()函数中,使用cv2.findChessboardCorners()函数进行棋盘格的检测。棋盘格的patternSize(columns, rows)次序,和其他OpenCV函数一致。在用户指定的flag基础上,增加了cv2.CALIB_CB_ADAPTIVE_THRESHcv2.CALIB_CB_NORMALIZE_IMAGE。棋盘格检测之后,在当前downsample过的image内查看检查出的角点是否远离图像边缘,默认的最小距离是8
pixels。对于行列数量不一致的棋盘格,检查检测到的棋盘格是不是从上至下的方向。此后找到所有角点之间的相对距离的最小值,利用这个最小值的一半(强制转换为int)作为refine radius. 使用cv2.cornerSubPix()函数进行refine。所用的criteria是( cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1 )

返回到downsample_and_detect()函数,将在小尺寸上detect到的角点重新映射到原始图像尺寸上。使用之前downsample的scale作为radius再次进行refine,criteria不变。

        # Scale the input image down to ~VGA size 将输入图像降成640*480
        height = img.shape[0]
        width = img.shape[1]
        scale = math.sqrt( (width*height) / (640.*480.) )
        if scale > 1.0:
            scrib = cv2.resize(img, (int(width / scale), int(height / scale)))
        else:
            scrib = img
        # Due to rounding, actual horizontal/vertical scaling may differ slightly
        x_scale = float(width) / scrib.shape[1]
        y_scale = float(height) / scrib.shape[0]

棋盘格检测

if self.pattern == Patterns.Chessboard:
    # Detect checkerboard 进行标定板的检测。
    (ok, downsampled_corners, ids, board) = self.get_corners(scrib, refine = True)

使用 cvFindChessboardCorners 函数在图像中查找棋盘格的角点。检查所有棋盘格。如果给定多个尺寸的棋盘格,则返回检测到的第一个棋盘格的角点。如果使用的是ChArUco棋盘格,还会返回标记ID,否则 ids 为 None。返回 (ok, corners, ids, board)

    def get_corners(self, img, refine = True):
        """
        Use cvFindChessboardCorners to find corners of chessboard in image.

        Check all boards. Return corners for first chessboard that it detects
        if given multiple size chessboards.

        If a ChArUco board is used, the marker IDs are also returned, otherwise
        ids is None.

        Returns (ok, corners, ids, board)
        """

        for b in self._boards:
            if self.pattern == Patterns.Chessboard: # 如果
                (ok, corners) = _get_corners(img, b, refine, self.checkerboard_flags) # 调用 _get_corners 函数获取棋盘格的角点。
                ids = None
            '''
            如果 self.pattern 是 Patterns.Chessboard(棋盘格):
				调用 _get_corners 函数获取棋盘格的角点。
				ids 被设置为 None,因为棋盘格没有标记ID。

            '''
            elif self.pattern == Patterns.ChArUco:
                (ok, corners, ids) = _get_charuco_corners(img, b, refine)
            else:
                (ok, corners) = _get_circles(img, b, self.pattern)
                ids = None
            if ok:
                return (ok, corners, ids, b)
		 '''
    	如果成功检测到角点(即 ok 为 True),则返回 (ok, corners, ids, b)。
    	如果遍历完所有棋盘格或标记后仍未找到角点,则返回 (False, None, None, None)。
		'''
        return (False, None, None, None)

检测角点

得到一张图片中特定棋盘格的角点。

  1. 函数定义:
    _get_corners(img, board, refine = True, checkerboard_flags=0):这个函数接受一个图像 img、一个棋盘对象 board、一个可选的 refine 参数(用于决定是否细化角点)和一个可选的 checkerboard_flags 参数(用于 cv2.findChessboardCorners 的额外标志)。

  2. 图像预处理:
    首先,它检查图像 img 的形状,如果图像是彩色图像(具有三个通道),则将其转换为灰度图像 mono。如果已经是灰度图像,则直接使用。

  3. 寻找棋盘角点:
    使用 OpenCV 的 cv2.findChessboardCorners 函数在灰度图像 mono 上寻找棋盘角点。棋盘的大小(行数和列数)由 board 对象的 n_cols 和 n_rows 属性提供。
    checkerboard_flags 参数可以传递额外的标志给 findChessboardCorners 函数,用于控制角点检测的行为。

  4. 检查角点位置:
    如果 ok 为 False(即没有找到角点),则直接返回 (ok, corners)。
    如果找到了角点,则检查它们是否都位于图像边框内(距离图像边缘大于 BORDER 个像素)。如果不是,则将 ok 设置为 False。

  5. 检查棋盘是否是正方形:
    如果棋盘的行数(board.n_rows)不等于列数(board.n_cols),则棋盘不是正方形。
    对于非正方形棋盘:
    检查第一个角点(corners[0, 0, 1])的y坐标是否大于最后一个角点(corners[-1, 0, 1])的y坐标。如果是,说明角点数组是从下到上的顺序,因此使用numpy.flipud将其翻转为从上到下的顺序。
    对于正方形棋盘:
    计算从第一个角点到最后一个角点的向量(direction_corners)。 如果这个向量的任何分量(在这里是x和y分量)都不是非负的,那么角点数组的顺序可能需要调整。
    @ 如果向量的所有分量都是0(numpy.all(direction_corners)返回False并且numpy.any(direction_corners)也返回False),则表明角点可能在一个直线上,这通常是不正确的。在这种情况下,简单地使用numpy.flipud将角点数组上下翻转。
    @ 如果向量的x分量是正数(direction_corners[0][0]为True),说明角点是从左到右、从上到下的顺序,但可能需要旋转90度以匹配通常的从上到下、从左到右的顺序。使用numpy.rot90进行旋转,并将结果重新整形为正确的形状。
    @ 如果向量的x分量不是正数(即y分量可能是负数或0),则角点可能是从上到下、从右到左的顺序,需要旋转270度(或等价地,先旋转90度,然后旋转180度,或旋转3次90度)。同样使用numpy.rot90进行旋转,并将结果重新整形。

  6. 细化通过 cv2.findChessboardCorners 检测到的棋盘角点
    使用角点之间最小距离的一半作为半径。这个半径应该足够大以能够捕捉到正确的角点,但又不能太大以至于在搜索窗口中包含了错误的角点。计算角点之间的最小距离来确定细化搜索的半径,并使用cv2.cornerSubPix来细化角点位置。
    @ 函数返回 (ok, corners),其中 ok 是一个布尔值,表示是否成功找到并细化了角点,corners 是细化后的角点坐标。

def _get_corners(img, board, refine = True, checkerboard_flags=0):
    """
    Get corners for a particular chessboard for an image
    """
    h = img.shape[0]
    w = img.shape[1]
    if len(img.shape) == 3 and img.shape[2] == 3:
        mono = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    else:
        mono = img
    (ok, corners) = cv2.findChessboardCorners(mono, (board.n_cols, board.n_rows), flags = cv2.CALIB_CB_ADAPTIVE_THRESH |
                                              cv2.CALIB_CB_NORMALIZE_IMAGE | checkerboard_flags)
    if not ok:
        return (ok, corners)

    # If any corners are within BORDER pixels of the screen edge, reject the detection by setting ok to false
    # NOTE: This may cause problems with very low-resolution cameras, where 8 pixels is a non-negligible fraction
    # of the image size. See http://answers.ros.org/question/3155/how-can-i-calibrate-low-resolution-cameras
    BORDER = 8
    if not all([(BORDER < corners[i, 0, 0] < (w - BORDER)) and (BORDER < corners[i, 0, 1] < (h - BORDER)) for i in range(corners.shape[0])]):
        ok = False

    # Ensure that all corner-arrays are going from top to bottom.
    if board.n_rows!=board.n_cols:
        if corners[0, 0, 1] > corners[-1, 0, 1]:
            corners = numpy.copy(numpy.flipud(corners))
    else:
        direction_corners=(corners[-1]-corners[0])>=numpy.array([[0.0,0.0]])

        if not numpy.all(direction_corners):
            if not numpy.any(direction_corners):
                corners = numpy.copy(numpy.flipud(corners))
            elif direction_corners[0][0]:
                corners=numpy.rot90(corners.reshape(board.n_rows,board.n_cols,2)).reshape(board.n_cols*board.n_rows,1,2)
            else:
                corners=numpy.rot90(corners.reshape(board.n_rows,board.n_cols,2),3).reshape(board.n_cols*board.n_rows,1,2)

    if refine and ok:
        # Use a radius of half the minimum distance between corners. This should be large enough to snap to the
        # correct corner, but not so large as to include a wrong corner in the search window.
        min_distance = float("inf")
        for row in range(board.n_rows):
            for col in range(board.n_cols - 1):
                index = row*board.n_rows + col
                min_distance = min(min_distance, _pdist(corners[index, 0], corners[index + 1, 0]))
        for row in range(board.n_rows - 1):
            for col in range(board.n_cols):
                index = row*board.n_rows + col
                min_distance = min(min_distance, _pdist(corners[index, 0], corners[index + board.n_cols, 0]))
        radius = int(math.ceil(min_distance * 0.5))
        cv2.cornerSubPix(mono, corners, (radius,radius), (-1,-1),
                                      ( cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1 ))

    return (ok, corners)

重映射原尺寸

将上面的角点位置返回到downsample_and_detect()函数,将在小尺寸上detect到的角点重新映射到原始图像尺寸上。使用之前downsample的scale作为radius再次进行refine,criteria不变。

# Scale corners back to full size image
            corners = None
            if ok:
                if scale > 1.0:
                    # Refine up-scaled corners in the original full-res image
                    # TODO Does this really make a difference in practice?
                    corners_unrefined = downsampled_corners.copy()
                    corners_unrefined[:, :, 0] *= x_scale
                    corners_unrefined[:, :, 1] *= y_scale
                    radius = int(math.ceil(scale))
                    if len(img.shape) == 3 and img.shape[2] == 3:
                        mono = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
                    else:
                        mono = img
                    cv2.cornerSubPix(mono, corners_unrefined, (radius,radius), (-1,-1),
                                                  ( cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1 ))
                    corners = corners_unrefined
                else:
                    corners = downsampled_corners

得到db数据(X,Y,Size,Skew)

在camera_calibration/src/camera_calibration/calibrator.py文件中的class Calibrator(object):类别中统计每个X,Y,Size,Skew,这些数据由self.db记录, self.db(parameters, image) 是一个存储了图像样本及其对应参数的列表,这些参数用于某种形式的校准或分析。这里的 parameters 是一个包含四个元素的元组 (X, Y, size, skew),每个元素都已经被归一化到 [0, 1] 的范围内。

       
        # self.db is list of (parameters, image) samples for use in calibration. parameters has form
        # (X, Y, size, skew) all normalized to [0,1], to keep track of what sort of samples we've taken
        # and ensure enough variety.
        self.db = []

获取参数代码如下(第一个函数,下面是调用的函数):

提取棋盘格角点的 x 和 y 坐标。
根据棋盘类型,找到棋盘的外围角点。
计算棋盘的外围面积和倾斜度(或偏斜度)。
计算归一化的 X、Y 坐标、归一化的尺寸和倾斜度,这些值将作为描述棋盘格视图的参数。
   def get_parameters(self, corners, ids, board, size):
        """
        Return list of parameters [X, Y, size, skew] describing the checkerboard view.
        """
        (width, height) = size
        Xs = corners[:,:,0]
        Ys = corners[:,:,1]
        if board.pattern == 'charuco':
            outside_corners = _get_largest_rectangle_corners(corners, ids, board)
        else:
            outside_corners = _get_outside_corners(corners, board)
        area = _calculate_area(outside_corners)
        skew = _calculate_skew(outside_corners)
        border = math.sqrt(area)
        # For X and Y, we "shrink" the image all around by approx. half the board size.
        # Otherwise large boards are penalized because you can't get much X/Y variation.
        #计算归一化的 X 和 Y 坐标(p_x 和 p_y)。这些坐标通过从平均 x 和 y 坐标中减去棋盘“边界”的一半,并除 
        #以棋盘的实际尺寸(减去边界)来得到。这样可以确保即使对于大型棋盘,X 和 Y 坐标也不会因为角点位置的
        #变化范围大而受到惩罚。
        p_x = min(1.0, max(0.0, (numpy.mean(Xs) - border / 2) / (width  - border)))
        p_y = min(1.0, max(0.0, (numpy.mean(Ys) - border / 2) / (height - border)))
        p_size = math.sqrt(area / (width * height))
        # 计算归一化的尺寸(p_size)。这是通过计算实际面积与棋盘面积的比例的平方根来得到的。
        params = [p_x, p_y, p_size, skew]
        # 将归一化的 X、Y 坐标、归一化的尺寸和倾斜度组合成一个列表(params),并返回它。
        return params

# 下面是上面函数调用的函数

def _get_outside_corners(corners, board):
    """
    Return the four corners of the board as a whole, as (up_left, up_right, down_right, down_left).
    """
    xdim = board.n_cols
    ydim = board.n_rows

    if board.pattern != "charuco" and corners.shape[1] * corners.shape[0] != xdim * ydim:
        raise Exception("Invalid number of corners! %d corners. X: %d, Y: %d" % (corners.shape[1] * corners.shape[0],
                                                                                xdim, ydim))
    if board.pattern == "charuco" and corners.shape[1] * corners.shape[0] != (xdim-1) * (ydim-1):
        raise Exception(("Invalid number of corners! %d corners. X: %d, Y: %d\n  for ChArUco boards, " +
                "_get_largest_rectangle_corners handles partial views of the target") % (corners.shape[1] *
                    corners.shape[0], xdim-1, ydim-1))

    up_left    = corners[0,0]
    up_right   = corners[xdim - 1,0]
    down_right = corners[-1,0]
    down_left  = corners[-xdim,0]

    return (up_left, up_right, down_right, down_left)

#计算面积大小
def _calculate_area(corners):
    """
    Get 2d image area of the detected checkerboard.
    The projected checkerboard is assumed to be a convex quadrilateral, and the area computed as
    |p X q|/2; see http://mathworld.wolfram.com/Quadrilateral.html.
    """
    (up_left, up_right, down_right, down_left) = corners
    a = up_right - up_left
    b = down_right - up_right
    c = down_left - down_right
    p = b + c
    q = a + b
    return abs(p[0]*q[1] - p[1]*q[0]) / 2.

# 计算扭转角度大小
def _calculate_skew(corners):
    """
    Get skew for given checkerboard detection.
    Scaled to [0,1], which 0 = no skew, 1 = high skew
    Skew is proportional to the divergence of three outside corners from 90 degrees.
    """
    # TODO Using three nearby interior corners might be more robust, outside corners occasionally
    # get mis-detected
    up_left, up_right, down_right, _ = corners

    def angle(a, b, c):
        """
        Return angle between lines ab, bc
        """
        ab = a - b
        cb = c - b
        return math.acos(numpy.dot(ab,cb) / (numpy.linalg.norm(ab) * numpy.linalg.norm(cb)))

    skew = min(1.0, 2. * abs((math.pi / 2.) - angle(up_left, up_right, down_right)))
    return skew

用以下函数进行判断基于给定参数的棋盘检测是否应该被添加到self.db数据库中。

    def is_good_sample(self, params, corners, ids, last_frame_corners, last_frame_ids):
        """
        Returns true if the checkerboard detection described by params should be added to the database.
        """
        if not self.db: #如果数据库是空的,则直接加入数据库中。
            return True

        def param_distance(p1, p2):
            return sum([abs(a-b) for (a,b) in zip(p1, p2)])

        db_params = [sample[0] for sample in self.db]
        d = min([param_distance(params, p) for p in db_params])
        # 从数据库中提取所有样本的参数(db_params)。
        # 计算当前参数与数据库中每个样本参数之间的距离,并找到最小值 d。
        #print "d = %.3f" % d #DEBUG
        # TODO What's a good threshold here? Should it be configurable?
        if d <= 0.2: # 如果距离小于0.2的话,就不要这个了
            return False

        if self.max_chessboard_speed > 0:
            if not self.is_slow_moving(corners, ids, last_frame_corners, last_frame_ids):
                return False # 如果不是慢慢的移动的话也不要
        # 不然就是要的。
        # All tests passed, image should be good for calibration
        return True

判断样本是否足够

  1. 检查数据库是否存在:
    如果 self.db 为空(即没有样本),则方法返回 None。

  2. 计算参数范围:
    遍历 self.db 中的所有样本,提取每个样本的参数,并计算这些参数的最小值和最大值。这是通过 lmin 和 lmax 函数实现的,这两个函数分别计算两个序列的逐元素最小值和最大值。

  3. 忽略尺寸和倾斜的最小值:
    由于可能不想基于棋盘的最小尺寸或最小倾斜度来判断是否足够好,因此将 min_params 中的 size 和 skew 设置为0。这意味着在评估参数多样性时,不会考虑这些参数的最小值。

  4. 计算每个参数的进度:
    对于每个参数,它计算参数范围(由 self.param_ranges 指定,self.param_ranges = [0.7, 0.7, 0.4, 0.5])与当前观察到的最小和最大参数值之间的“进度”。这个“进度”是基于参数实际覆盖的范围与所需覆盖范围的比率来计算的。如果参数覆盖了所需范围的全部或大部分,则进度为1.0。

  5. 判断样本是否足够好:
    如果数据库中的样本数量达到或超过40个,或者所有参数的进度都为1.0(即所有参数都达到了足够的覆盖范围),则设置 self.goodenough 为 True。这意味着有足够的样本多样性来进行校准。

  6. 返回参数信息:
    方法返回一个列表,其中包含参数名(self._param_names,尽管在给出的代码片段中未定义)、最小参数值、最大参数值和每个参数的进度。这可以用于调试或可视化目的,以了解哪些参数已经得到了足够的覆盖,哪些还需要更多的样本。

    def compute_goodenough(self): #########
        if not self.db:
            return None

        # Find range of checkerboard poses covered by samples in database
        all_params = [sample[0] for sample in self.db]
        min_params = all_params[0]
        max_params = all_params[0]
        for params in all_params[1:]:
            min_params = lmin(min_params, params)
            max_params = lmax(max_params, params)
        # Don't reward small size or skew
        min_params = [min_params[0], min_params[1], 0., 0.]

        # For each parameter, judge how much progress has been made toward adequate variation
        progress = [min((hi - lo) / r, 1.0) for (lo, hi, r) in zip(min_params, max_params, self.param_ranges)]
        # If we have lots of samples, allow calibration even if not all parameters are green
        # TODO Awkward that we update self.goodenough instead of returning it
        self.goodenough = (len(self.db) >= 40) or all([p == 1.0 for p in progress])

        return list(zip(self._param_names, min_params, max_params, progress))
        
	def lmin(seq1, seq2):
    	""" Pairwise minimum of two sequences """
    	return [min(a, b) for (a, b) in zip(seq1, seq2)]

	def lmax(seq1, seq2):
    	""" Pairwise maximum of two sequences """
    	return [max(a, b) for (a, b) in zip(seq1, seq2)] 

camera_calibration/src/camera_calibration/camera_calibrator.py

按钮变化代码

    def button(self, dst, label, enable):
        dst.fill(255)
        size = (dst.shape[1], dst.shape[0])
        if enable:
            color = (155, 155, 80)
        else:
            color = (224, 224, 224)
        cv2.circle(dst, (size[0] // 2, size[1] // 2), min(size) // 2, color, -1)
        (w, h) = self.getTextSize(label)
        self.putText(dst, label, ((size[0] - w) // 2, (size[1] + h) // 2), (255,255,255))

    def buttons(self, display):
        x = self.displaywidth
        self.button(display[180:280,x:x+100], "CALIBRATE", self.c.goodenough)
        self.button(display[280:380,x:x+100], "SAVE", self.c.calibrated)
        self.button(display[380:480,x:x+100], "COMMIT", self.c.calibrated)

绘制线条颜色变化

def redraw_monocular(self, drawable):
        height = drawable.scrib.shape[0]
        width = drawable.scrib.shape[1]

        display = numpy.zeros((max(480, height), width + 100, 3), dtype=numpy.uint8)
        display[0:height, 0:width,:] = drawable.scrib
        display[0:height, width:width+100,:].fill(255)

        self.buttons(display)############################
        if not self.c.calibrated:
            if drawable.params:
                 for i, (label, lo, hi, progress) in enumerate(drawable.params):
                    (w,_) = self.getTextSize(label)
                    self.putText(display, label, (width + (100 - w) // 2, self.y(i)))
                    color = (0,255,0)
                    if progress < 1.0:
                        color = (0, int(progress*255.), 255)
                    cv2.line(display,
                            (int(width + lo * 100), self.y(i) + 20),
                            (int(width + hi * 100), self.y(i) + 20),
                            color, 4)

        else:
            self.putText(display, "lin.", (width, self.y(0)))
            linerror = drawable.linear_error
            if linerror < 0:
                msg = "?"
            else:
                msg = "%.2f" % linerror
                #print "linear", linerror
            self.putText(display, msg, (width, self.y(1)))

        self._last_display = display
        self.queue_display.put(display)

这段代码定义了一个名为 redraw_monocular 的方法,它用于重新绘制一个单目视觉界面

获取图像尺寸:
从 drawable 对象的 scrib 属性中获取图像的高度和宽度。

height = drawable.scrib.shape[0]  
width = drawable.scrib.shape[1]

创建新的显示窗口:
创建一个新的 NumPy 数组 display,用于显示。这个数组比原始图像稍大,宽度增加了 100 像素,高度则确保至少为 480 像素(如果原始图像高度小于 480 像素的话)。这个额外的空间可能是为了添加一些文本或按钮。

display = numpy.zeros((max(480, height), width + 100, 3), dtype=numpy.uint8)

复制图像到显示窗口:
将原始图像 drawable.scrib 复制到 display 数组的相应位置。

display[0:height, 0:width,:] = drawable.scrib

在右侧添加白色条带:
在 display 数组的右侧(即原始图像的右侧)添加一个 100 像素宽的白色条带。

display[0:height, width:width+100,:].fill(255)

调用按钮绘制方法(注释标记部分):
调用 buttons 方法(也即右下角三个按钮)来在 display 上绘制按钮或其他控件。

self.buttons(display)

如果尚未校准:
如果 self.c.calibrated 为 False(即尚未进行校准),则根据 drawable.params(可能包含校准进度信息)在右侧白色条带上绘制文本和线条。线条的长度和颜色表示每个校准参数的进度。

if not self.c.calibrated:  
# ... (绘制校准进度的文本和线条)

如果已校准:
如果已进行校准,则绘制“lin.”和线性误差(可能是校准结果的某种度量)。如果 linear_error 小于 0,则显示“?”,否则显示线性误差的值。

else:  
    # ... (绘制“lin.”和线性误差)

更新显示并排队:
将 display 数组保存到 self._last_display 属性中,并将其放入 self.queue_display 队列中。这个队列可能用于在其他线程或进程中更新实际的显示窗口。

self._last_display = display  
self.queue_display.put(display)

详细解释线条绘制部分:

检查是否已校准:
这行代码检查是否已经完成校准。如果没有,它将进入以下的校准进度绘制代码块。

if not self.c.calibrated:

检查是否存在校准参数:确保 drawable.params 列表(或其他数据结构)不为空,即存在要显示的校准参数。

if drawable.params:

遍历校准参数:

for i, (label, lo, hi, progress) in enumerate(drawable.params):

遍历 drawable.params 列表中的每个元素。每个元素都是一个四元组,包含:

label:参数的标签(如 "X", "Y", "Size", "Skew" 等)。
lo 和 hi:表示参数范围的最低和最高值(已归一化到 [0, 1])。
progress:表示该参数校准进度的浮点数(范围在 0.0 到 1.0 之间)。

获取文本标签的宽度:
使用 self.getTextSize 方法(该方法未在提供的代码段中定义)来获取文本标签的宽度,以便在界面上正确放置它。

(w,_) = self.getTextSize(label)

绘制文本标签:
使用 self.putText 方法(该方法也未在提供的代码段中定义)在界面上绘制文本标签。文本被放置在右侧白色条带的中心位置,垂直位置由 self.y(i) 确定(可能是根据索引 i 计算的垂直坐标)。

self.putText(display, label, (width + (100 - w) // 2, self.y(i)))

设置线条颜色:
线条的颜色根据校准进度 progress 来设置。如果进度为 1.0(即完全校准),则线条为绿色;否则,线条颜色将从绿色渐变到蓝色,渐变程度取决于 progress 的值。

color = (0,255,0)  # 默认绿色  
if progress < 1.0:  
    color = (0, int(progress*255.), 255)  # 渐变到蓝色

绘制表示参数范围的线条:
使用 OpenCV 的 cv2.line 函数在界面上绘制一条表示参数范围的线条。线条的起始和结束位置根据归一化的 lo 和 hi 值计算,垂直位置与文本标签相同但稍微下移一些(通过 + 20 实现)。线条的颜色和宽度也根据之前的设置确定。

cv2.line(display,  
         (int(width + lo * 100), self.y(i) + 20),  
         (int(width + hi * 100), self.y(i) + 20),  
         color, 4)

请添加图片描述

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值