圆环circle circle.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 #include "circle.h" #include "motor.h" #include "main.h" enum circle_type_e circle_type = CIRCLE_NONE;const char *circle_type_name[CIRCLE_NUM] = { "CIRCLE_NONE" , "CIRCLE_LEFT_BEGIN" , "CIRCLE_RIGHT_BEGIN" , "CIRCLE_LEFT_RUNNING" , "CIRCLE_RIGHT_RUNNING" , "CIRCLE_LEFT_IN" , "CIRCLE_RIGHT_IN" , "CIRCLE_LEFT_OUT" , "CIRCLE_RIGHT_OUT" , "CIRCLE_LEFT_END" , "CIRCLE_RIGHT_END" , }; int64_t circle_encoder;int none_left_line = 0 , none_right_line = 0 ;int have_left_line = 0 , have_right_line = 0 ;void check_circle () { if (circle_type == CIRCLE_NONE && Lpt0_found && !Lpt1_found && is_straight1) { circle_type = CIRCLE_LEFT_BEGIN; } if (circle_type == CIRCLE_NONE && !Lpt0_found && Lpt1_found && is_straight0) { circle_type = CIRCLE_RIGHT_BEGIN; } } void run_circle () { int64_t current_encoder = get_total_encoder(); if (circle_type == CIRCLE_LEFT_BEGIN) { track_type = TRACK_RIGHT; if (rpts0s_num < 0.2 / sample_dist) { none_left_line++; } if (rpts0s_num > 1.0 / sample_dist && none_left_line > 2 ) { have_left_line++; if (have_left_line > 1 ) { circle_type = CIRCLE_LEFT_IN; none_left_line = 0 ; have_left_line = 0 ; circle_encoder = current_encoder; } } } else if (circle_type == CIRCLE_LEFT_IN) { track_type = TRACK_LEFT; if (rpts0s_num < 0.1 / sample_dist || current_encoder - circle_encoder >= ENCODER_PER_METER * (3.14 * 1 / 2 )) { circle_type = CIRCLE_LEFT_RUNNING; } } else if (circle_type == CIRCLE_LEFT_RUNNING) { track_type = TRACK_RIGHT; if (Lpt1_found) rpts1s_num = rptsc1_num = Lpt1_rpts1s_id; if (Lpt1_found && Lpt1_rpts1s_id < 0.4 / sample_dist) { circle_type = CIRCLE_LEFT_OUT; } } else if (circle_type == CIRCLE_LEFT_OUT) { track_type = TRACK_LEFT; if (is_straight1) { circle_type = CIRCLE_LEFT_END; } } else if (circle_type == CIRCLE_LEFT_END) { track_type = TRACK_RIGHT; if (rpts0s_num < 0.2 / sample_dist) { none_left_line++; } if (rpts0s_num > 1.0 / sample_dist && none_left_line > 3 ) { circle_type = CIRCLE_NONE; none_left_line = 0 ; } } else if (circle_type == CIRCLE_RIGHT_BEGIN) { track_type = TRACK_LEFT; if (rpts1s_num < 0.2 / sample_dist) { none_right_line++; } if (rpts1s_num > 1.0 / sample_dist && none_right_line > 2 ) { have_right_line++; if (have_right_line > 1 ) { circle_type = CIRCLE_RIGHT_IN; none_right_line = 0 ; have_right_line = 0 ; circle_encoder = current_encoder; } } } else if (circle_type == CIRCLE_RIGHT_IN) { track_type = TRACK_RIGHT; if (rpts1s_num < 0.1 / sample_dist || current_encoder - circle_encoder >= ENCODER_PER_METER * (3.14 * 1 / 2 )) { circle_type = CIRCLE_RIGHT_RUNNING; } } else if (circle_type == CIRCLE_RIGHT_RUNNING) { track_type = TRACK_LEFT; if (Lpt0_found) rpts0s_num = rptsc0_num = Lpt0_rpts0s_id; if (Lpt0_found && Lpt0_rpts0s_id < 0.4 / sample_dist) { circle_type = CIRCLE_RIGHT_OUT; } } else if (circle_type == CIRCLE_RIGHT_OUT) { track_type = TRACK_RIGHT; if (is_straight0) { circle_type = CIRCLE_RIGHT_END; } } else if (circle_type == CIRCLE_RIGHT_END) { track_type = TRACK_LEFT; if (rpts1s_num < 0.2 / sample_dist) { none_right_line++; } if (rpts1s_num > 1.0 / sample_dist && none_right_line > 2 ) { circle_type = CIRCLE_NONE; none_right_line = 0 ; } } } void draw_circle () {}
circle.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #ifndef CIRCLE_H #define CIRCLE_H enum circle_type_e { CIRCLE_NONE = 0 , CIRCLE_LEFT_BEGIN, CIRCLE_RIGHT_BEGIN, CIRCLE_LEFT_IN, CIRCLE_RIGHT_IN, CIRCLE_LEFT_RUNNING, CIRCLE_RIGHT_RUNNING, CIRCLE_LEFT_OUT, CIRCLE_RIGHT_OUT, CIRCLE_LEFT_END, CIRCLE_RIGHT_END, CIRCLE_NUM, }; extern const char *circle_type_name[CIRCLE_NUM];extern enum circle_type_e circle_type ;void check_circle () ;void run_circle () ;void draw_circle () ;#endif
非圆环模式下判断圆环 单边L角点, 单边长直道
1 2 3 4 5 6 7 8 9 void check_circle () { if (circle_type == CIRCLE_NONE && Lpt0_found && !Lpt1_found && is_straight1) { circle_type = CIRCLE_LEFT_BEGIN; } if (circle_type == CIRCLE_NONE && !Lpt0_found && Lpt1_found && is_straight0) { circle_type = CIRCLE_RIGHT_BEGIN; } }
圆环执行 run_circle()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 void run_circle () { int64_t current_encoder = get_total_encoder(); if (circle_type == CIRCLE_LEFT_BEGIN) { track_type = TRACK_RIGHT; if (rpts0s_num < 0.2 / sample_dist) { none_left_line++; } if (rpts0s_num > 1.0 / sample_dist && none_left_line > 2 ) { have_left_line++; if (have_left_line > 1 ) { circle_type = CIRCLE_LEFT_IN; none_left_line = 0 ; have_left_line = 0 ; circle_encoder = current_encoder; } } } else if (circle_type == CIRCLE_LEFT_IN) { track_type = TRACK_LEFT; if (rpts0s_num < 0.1 / sample_dist || current_encoder - circle_encoder >= ENCODER_PER_METER * (3.14 * 1 / 2 )) { circle_type = CIRCLE_LEFT_RUNNING; } } else if (circle_type == CIRCLE_LEFT_RUNNING) { track_type = TRACK_RIGHT; if (Lpt1_found) rpts1s_num = rptsc1_num = Lpt1_rpts1s_id; if (Lpt1_found && Lpt1_rpts1s_id < 0.4 / sample_dist) { circle_type = CIRCLE_LEFT_OUT; } } else if (circle_type == CIRCLE_LEFT_OUT) { track_type = TRACK_LEFT; if (is_straight1) { circle_type = CIRCLE_LEFT_END; } } else if (circle_type == CIRCLE_LEFT_END) { track_type = TRACK_RIGHT; if (rpts0s_num < 0.2 / sample_dist) { none_left_line++; } if (rpts0s_num > 1.0 / sample_dist && none_left_line > 3 ) { circle_type = CIRCLE_NONE; none_left_line = 0 ; } } else if (circle_type == CIRCLE_RIGHT_BEGIN) { track_type = TRACK_LEFT; if (rpts1s_num < 0.2 / sample_dist) { none_right_line++; } if (rpts1s_num > 1.0 / sample_dist && none_right_line > 2 ) { have_right_line++; if (have_right_line > 1 ) { circle_type = CIRCLE_RIGHT_IN; none_right_line = 0 ; have_right_line = 0 ; circle_encoder = current_encoder; } } } else if (circle_type == CIRCLE_RIGHT_IN) { track_type = TRACK_RIGHT; if (rpts1s_num < 0.1 / sample_dist || current_encoder - circle_encoder >= ENCODER_PER_METER * (3.14 * 1 / 2 )) { circle_type = CIRCLE_RIGHT_RUNNING; } } else if (circle_type == CIRCLE_RIGHT_RUNNING) { track_type = TRACK_LEFT; if (Lpt0_found) rpts0s_num = rptsc0_num = Lpt0_rpts0s_id; if (Lpt0_found && Lpt0_rpts0s_id < 0.4 / sample_dist) { circle_type = CIRCLE_RIGHT_OUT; } } else if (circle_type == CIRCLE_RIGHT_OUT) { track_type = TRACK_RIGHT; if (is_straight0) { circle_type = CIRCLE_RIGHT_END; } } else if (circle_type == CIRCLE_RIGHT_END) { track_type = TRACK_LEFT; if (rpts1s_num < 0.2 / sample_dist) { none_right_line++; } if (rpts1s_num > 1.0 / sample_dist && none_right_line > 2 ) { circle_type = CIRCLE_NONE; none_right_line = 0 ; } } }
详细解读
get_total_encoder() 1 2 3 int64_t get_total_encoder () { return (int64_t ) ((motor_l.total_encoder + motor_r.total_encoder) / 2 ); }
这段代码定义了一个函数 get_total_encoder()
,用于计算左右两个电机的编码器总和并取平均值。具体步骤如下:
获取左电机和右电机的编码器值 motor_l.total_encoder
和 motor_r.total_encoder
。 将左右两个电机的编码器值相加得到总和。 将总和除以2取平均值,然后以 int64_t
类型返回结果。
track_type:单边得中线的边
1 2 3 4 if (rpts0s_num < 0.1 / sample_dist || current_encoder - circle_encoder >= ENCODER_PER_METER * (3.14 * 1 / 2 )) { circle_type = CIRCLE_LEFT_RUNNING; }
这段代码的逻辑似乎与之前的代码相关,用于检测编码器在打表过1/4圆时的状态,并进行相应的修正。让我来解释一下这段代码的作用:
首先判断条件 rpts0s_num < 0.1 / sample_dist
,如果条件成立,说明小车还未行驶到1/4圆的位置,或者右线未被检测到。
或者判断条件 current_encoder - circle_encoder >= ENCODER_PER_METER * (3.14 * 1 / 2)
,这个条件看起来是在判断当前编码器值与开始打表时的编码器值之差是否超过了1/4圆的周长对应的编码器值。
如果以上两个条件有一个成立,就会将 circle_type
设置为 CIRCLE_LEFT_RUNNING
,表示小车正在左环行驶的阶段。
在检测到小车行驶过1/4圆或者右线为转弯时将 circle_type
修改为 CIRCLE_LEFT_RUNNING
,可能是为了校准小车在左环赛道上行驶时的特定状态。这段代码与之前的代码一起,似乎在实现一个复杂的状态机来控制小车在左环赛道上的运动。如果您有更多的问题或需要进一步解释,请随时告诉我。
什么是编码器打表: 编码器打表(Encoder Look-Up Table)通常指的是在编码器模块中使用查找表(Look-Up Table)来进行编码的过程。在信号处理领域或者通信系统中,编码器的作用是将输入信号转换为特定的编码形式,以便传输、存储或处理。而编码器打表的概念是通过预先构建一个表格,将输入信号映射到特定的编码值,从而实现编码的过程。
在这种情况下,编码器打表的优势在于可以提高编码的速度和效率。通过查找表,编码器可以以常量时间复杂度快速查找到对应的编码值,而不必每次都进行复杂的计算或逻辑判断。这样可以加快编码过程,特别是在需要频繁编码大量数据时,可以节省时间和计算资源。
总的来说,编码器打表是一种提高编码效率的方法,通过预先建立编码映射表,可以在编码过程中快速准确地找到对应的编码值,提高系统性能和速度。
迷宫巡线部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 AT_DTCM_SECTION_ALIGN_INIT (const int dir_front[4 ][2 ], 8 ) = {{0 , -1 }, {1 , 0 }, {0 , 1 }, {-1 , 0 }}; AT_DTCM_SECTION_ALIGN_INIT (const int dir_frontleft[4 ][2 ], 8 ) = {{-1 , -1 }, {1 , -1 }, {1 , 1 }, {-1 , 1 }}; AT_DTCM_SECTION_ALIGN_INIT (const int dir_frontright[4 ][2 ], 8 ) = {{1 , -1 }, {1 , 1 }, {-1 , 1 }, {-1 , -1 }};
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 AT_ITCM_SECTION_INIT (void findline_lefthand_adaptive (image_t *img, int block_size, int clip_value, int x, int y, int pts[][2 ], int *num)) { assert (img && img->data); assert (num && *num >= 0 ); assert (block_size > 1 && block_size % 2 == 1 ); int half = block_size / 2 ; int step = 0 , dir = 0 , turn = 0 ; while (step < *num && half < x && x < img->width - half - 1 && half < y && y < img->height - half - 1 && turn < 4 ) { int local_thres = 0 ; for (int dy = -half; dy <= half; dy++) { for (int dx = -half; dx <= half; dx++) { local_thres += AT (img, x + dx, y + dy); } } local_thres /= block_size * block_size; local_thres -= clip_value; int current_value = AT (img, x, y); int front_value = AT (img, x + dir_front[dir][0 ], y + dir_front[dir][1 ]); int frontleft_value = AT (img, x + dir_frontleft[dir][0 ], y + dir_frontleft[dir][1 ]); if (front_value < local_thres) { dir = (dir + 1 ) % 4 ; turn++; } else if (frontleft_value < local_thres) { x += dir_front[dir][0 ]; y += dir_front[dir][1 ]; pts[step][0 ] = x; pts[step][1 ] = y; step++; turn = 0 ; } else { x += dir_frontleft[dir][0 ]; y += dir_frontleft[dir][1 ]; dir = (dir + 3 ) % 4 ; pts[step][0 ] = x; pts[step][1 ] = y; step++; turn = 0 ; } } *num = step; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 AT_ITCM_SECTION_INIT (void findline_righthand_adaptive (image_t *img, int block_size, int clip_value, int x, int y, int pts[][2 ], int *num)) { assert (img && img->data); assert (num && *num >= 0 ); assert (block_size > 1 && block_size % 2 == 1 ); int half = block_size / 2 ; int step = 0 , dir = 0 , turn = 0 ; while (step < *num && 0 < x && x < img->width - 1 && 0 < y && y < img->height - 1 && turn < 4 ) { int local_thres = 0 ; for (int dy = -half; dy <= half; dy++) { for (int dx = -half; dx <= half; dx++) { local_thres += AT (img, x + dx, y + dy); } } local_thres /= block_size * block_size; local_thres -= clip_value; int current_value = AT (img, x, y); int front_value = AT (img, x + dir_front[dir][0 ], y + dir_front[dir][1 ]); int frontright_value = AT (img, x + dir_frontright[dir][0 ], y + dir_frontright[dir][1 ]); if (front_value < local_thres) { dir = (dir + 3 ) % 4 ; turn++; } else if (frontright_value < local_thres) { x += dir_front[dir][0 ]; y += dir_front[dir][1 ]; pts[step][0 ] = x; pts[step][1 ] = y; step++; turn = 0 ; } else { x += dir_frontright[dir][0 ]; y += dir_frontright[dir][1 ]; dir = (dir + 1 ) % 4 ; pts[step][0 ] = x; pts[step][1 ] = y; step++; turn = 0 ; } } *num = step; }
逆透视 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void warpPerspective (InputArray src, OutputArray dst, InputArray M, Size dsize, int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar &borderValue = Scalar()) { CV_INSTRUMENT_REGION () CV_Assert (!src.empty ()); CV_Assert (src.depth () == CV_8U || src.depth () == CV_32F); CV_Assert (M.rows () == 3 && M.cols () == 3 ); Mat mapx (dsize, CV_32F) ; Mat mapy (dsize, CV_32F) ; Mat transform (2 , 3 , CV_64F) ; M.getMat ().colRange (0 , 2 ).convertTo (transform, transform.type ()); buildWarpPerspectiveMaps (transform, flags, dsize, mapx, mapy); remap (src, dst, mapx, mapy, flags, borderMode, borderValue); }
这段代码是OpenCV中的warpPerspective函数的实现,用于对输入图像进行透视变换。下面是对代码中的函数及参数的详细解释:
warpPerspective函数参数解释:
src :输入图像,类型为InputArray。
dst :输出图像,类型为OutputArray。
M :3x3的变换矩阵,描述了透视变换,类型为InputArray。
dsize :输出图像的尺寸,类型为Size。
flags :插值方法,用于指定在进行像素值插值时要使用的方法,默认为INTER_LINEAR。
borderMode :边界像素插值模式,用于指定当目标图像像素位于源图像之外时的处理方式,默认为BORDER_CONSTANT。
borderValue :边界填充的像素值,类型为Scalar。
warpPerspective函数主要步骤解释:
CV_INSTRUMENT_REGION :用于性能测量和优化的宏定义。
CV_Assert :断言,用于判断条件是否成立,如果条件不成立则会抛出一个异常。
Mat mapx 和Mat mapy :分别用于存储X和Y方向的映射结果。
Mat transform :是一个2x3的矩阵,用于保存M矩阵的前两列,并将其转换为CV_64F类型。
buildWarpPerspectiveMaps :根据透视变换矩阵和指定的插值方法,构建出目标图像在源图像上的映射关系。
remap :根据生成的映射关系对输入图像进行透视变换,并存储结果到输出图像dst中。 M.getMat().colRange(0, 2).convertTo(transform, transform.type());详细解释 这行代码是针对OpenCV中的矩阵操作。让我逐步解释一下:
M.getMat()
: 这部分获取一个OpenCV的矩阵M
。
.colRange(0, 2)
: 这个部分表示对该矩阵M
的列进行切片操作,只选择从第0列到第2列(不包括第2列)的部分。
.convertTo(transform, transform.type())
: 这一部分是将切片后的列数据进行类型转换并赋值给另外一个矩阵transform
。这里的transform
是一个目标矩阵,它将包含转换后的数据。transform.type()
返回transform
矩阵的类型。
因此,整体来看,这行代码的作用是从矩阵M
中选择第0列到第1列的数据,并将其转换为指定类型,然后存储到矩阵transform
中。
这是 buildWarpPerspectiveMaps
函数的伪代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void buildWarpPerspectiveMaps (Size src_size, Matx33f M, bool inverse_map, Mat& xmap, Mat& ymap) { int width = src_size.width; int height = src_size.height; xmap.create (height, width, CV_32F); ymap.create (height, width, CV_32F); for (int y = 0 ; y < height; y++) { for (int x = 0 ; x < width; x++) { Vec3f src_pt = Vec3f (x, y, 1 ); Vec3f dst_pt = M * src_pt; float map_x, map_y; if (!inverse_map) { map_x = dst_pt[0 ] / dst_pt[2 ]; map_y = dst_pt[1 ] / dst_pt[2 ]; } else { map_x = src_pt[0 ] - dst_pt[0 ]; map_y = src_pt[1 ] - dst_pt[1 ]; } xmap.at <float >(y, x) = map_x; ymap.at <float >(y, x) = map_y; } } }
这个伪代码实现描述了 buildWarpPerspectiveMaps
函数的大致操作步骤。实际的 buildWarpPerspectiveMaps
函数是 OpenCV 中用于构建透视变换映射的函数,具体实现会更加复杂并且包含了更多细节。如果你需要实际的代码实现,建议查阅 OpenCV 的源代码或者官方文档中提供的具体函数实现。希望这个简要描述能够帮助到你,如果有其他问题或者需要更多帮助,请随时告诉我。
注意事项:
输入图像src不能为空。
输入图像src的深度必须为CV_8U或CV_32F。
变换矩阵M必须为一个3x3矩阵。
warpPerspective函数实现了透视变换的核心逻辑,通过构建变换矩阵并利用remap函数实现对图像的透视变换操作。
如果你有任何关于这段代码的疑问或需要进一步解释,请随时告诉我。
@brief Applies a perspective transformation to an image.
The function warpPerspective transforms the source image using the specified matrix:
[$\texttt{dst} (x,y) = \texttt{src} \left ( \frac{M_{11} x + M_{12} y + M_{13}}{M_{31} x + M_{32} y + M_{33}} , \frac{M_{21} x + M_{22} y + M_{23}}{M_{31} x + M_{32} y + M_{33}} \right )$]
when the flag #WARP_INVERSE_MAP is set. Otherwise, the transformation is first inverted with invert and then put in the formula above instead of M. The function cannot operate in-place.
@param src input image. @param dst output image that has the size dsize and the same type as src . @param M $ 3\times 3 $ transformation matrix. @param dsize size of the output image. @param flags combination of interpolation methods (#INTER_LINEAR or #INTER_NEAREST) and the optional flag #WARP_INVERSE_MAP, that sets M as the inverse transformation ( $\texttt{dst}\rightarrow\texttt{src} $ ). @param borderMode pixel extrapolation method (#BORDER_CONSTANT or #BORDER_REPLICATE). @param borderValue value used in case of a constant border; by default, it equals 0.
@sa warpAffine, resize, remap, getRectSubPix, perspectiveTransform