现在非常流行的有关创建汽车自动驾驶仪的课程。 Udacity提供的
纳米学位可能是最著名的选择。
许多人从中学习并发表自己的决定。 我也无法通过并被带走。
不同之处在于,本课程涉及根据提供的数据开发算法,而我为
机器人做了所有工作。
本课程的学生在学习计算机视觉时面临的首要任务是顺着道路前进。 关于该主题的文章很多,以下是一些最详细的文章:
所有这些都非常简单,并且工作方案简化为以下几点:
我把白色胶带粘在地板上,开始做生意。

在上面提到的作品中,您的任务是找到黄线,因此它们可以使用HLS和HSV颜色。 由于我的产品线只有白色,所以我决定不再理会它,而将自己局限于黑白滤镜。
几何形状
几何问题立即开始。 对于图片中的学生,带有箭头的条纹进入了地平线。 而且,仍然在其上检测到很多行,作者不得不将其合并。 但是,他们的线条指向正确,照片中没有碎屑。
我的情况完全不同。 电工胶带的几何形状远非直线。 地板上的眩光会产生噪音。
应用Canny之后,发生的事情是:

和霍夫线是这样的:

通过加强标准,可以排除垃圾,但是带上发现的几乎所有行都消失了。 依靠如此小的细分市场将是愚蠢的。

通常,结果非常不稳定,我想到了尝试另一种方法。
我开始寻找轮廓,而不是线条。 假设最大的电路是电气胶带,我们设法摆脱了垃圾。 (然后发现,大的白色踢脚线在框架中占据的空间比电线大。我不得不用沙发垫覆盖它)。
如果我们以轮廓的最小矩形为边界,则中间的纵向线非常适合运动矢量的作用。

灯
第二个问题是照明。 我非常成功地将轨道的一侧放在沙发的阴影下,完全不可能以相同的设置处理整个轨道的照片。 结果,我不得不在黑白滤镜上实现动态截止。 算法是这样的:如果在图片中应用滤镜后,白色过多(超过10%),则应提高阈值。 如果太少(小于3%)-忽略。 实践表明,平均可以进行3-4次迭代,找到最佳截止值。
幻数放置在单独的配置中(请参见下文),您可以将其与最佳数字一起玩。
def balance_pic(image): global T ret = None direction = 0 for i in range(0, tconf.th_iterations): rc, gray = cv.threshold(image, T, 255, 0) crop = Roi.crop_roi(gray) nwh = cv.countNonZero(crop) perc = int(100 * nwh / Roi.get_area()) logging.debug(("balance attempt", i, T, perc)) if perc > tconf.white_max: if T > tconf.threshold_max: break if direction == -1: ret = crop break T += 10 direction = 1 elif perc < tconf.white_min: if T < tconf.threshold_min: break if direction == 1: ret = crop break T -= 10 direction = -1 else: ret = crop break return ret
调整机器视觉后,可以继续进行实际运动。 该算法如下:
- 直行0.5秒
- 拍照
- 找到向量
- 如果向量的开头相对于图片的中心发生了偏移,则我们会朝正确的方向稍微滑行
- 如果向量的倾斜角度偏离垂直方向的幅度超过了必要,我们将向正确的方向
- 如果突然发生了条纹从帧中消失的情况,我们假设我们先转弯并开始朝上一步中矢量的最后转向或倾斜方向旋转
代码的
简化版本(在
Github上为Full-):
def check_shift_turn(angle, shift): turn_state = 0 if angle < tconf.turn_angle or angle > 180 - tconf.turn_angle: turn_state = np.sign(90 - angle) shift_state = 0 if abs(shift) > tconf.shift_max: shift_state = np.sign(shift) return turn_state, shift_state def get_turn(turn_state, shift_state): turn_dir = 0 turn_val = 0 if shift_state != 0: turn_dir = shift_state turn_val = tconf.shift_step if shift_state != turn_state else tconf.turn_step elif turn_state != 0: turn_dir = turn_state turn_val = tconf.turn_step return turn_dir, turn_val def follow(iterations): tanq.set_motors("ff") try: last_turn = 0 last_angle = 0 for i in range(0, iterations): a, shift = get_vector() if a is None: if last_turn != 0: a, shift = find_line(last_turn) if a is None: break elif last_angle != 0: logging.debug(("Looking for line by angle", last_angle)) turn(np.sign(90 - last_angle), tconf.turn_step) continue else: break turn_state, shift_state = check_shift_turn(a, shift) turn_dir, turn_val = get_turn(turn_state, shift_state) if turn_dir != 0: turn(turn_dir, turn_val) last_turn = turn_dir else: time.sleep(tconf.straight_run) last_turn = 0 last_angle = a finally: tanq.set_motors("ss")
结果
坦克不均匀但充满信心地沿着轨迹爬行:

这是调试图形中的GIF:

算法设置
Github代码。