香橙派 AIpro上手体验并验证车道线识别算法
1.前言
最近入手了一块香橙派AIpro,体验了一下,感觉还不错,在这里分享给大家,大家可以做个参考。
2.开箱
整套产品包含一块主板、一个电源插头和一条双端Type-C的数据线,打开盒子可以看到主板,主板前后都有海绵保护,包装得还可以:
主板上已经装好了散热器,焊好了天线,这里注意下上电时天线上的导电胶带不要和板子接触,以免造成板子元器件短路:
主板侧面有一个Typc-C供电口,两个HDMI显示输出口,中间有个音频接口,再右边是一个Type-C接口,这个Type-C接口只支持USB3.0的设备,也就是说USB2.0和1.0的鼠标或键盘等设备是不能接到这个这个接口的,鼠标和键盘要用右边的两个USB接口。但这个Type-C接口可以接USB3.0的移动硬盘之类设备。最右边还有一个千兆以太网口。
板子另一侧有一排40pin的GPIO,两个Key,一个是关机键,一个是重启键。还有两个LED指示灯,靠近关机键的那个LED是电源指示灯,接上电源就亮,左边那个LED可以用程序控制,默认是Linux内核跑起来之后会点亮,系统关闭后熄灭。
40pin GPIO的定义如下,包含一路SPI、一路IIC、三路UART和一路PWM输出,其余引脚已经默认配置为GPIO模式,可以直接使用,有复用功能的引脚需要修改 DTS 配置才能作为GPIO使用。
下图是主机背面的样子,来的时候带了一张32G内存卡,此外还可以看到两个拨码开关,可以用来选择启动方式,支持SSD、EMMC和SD三种启动方式。
其他的还有MIPI摄像头等接口,这里就不一一列举了,需要的话可以再使用手册中查找相关说明。
3.开机
插上电源后自动开机,注意显示器要插到HDMI0接口,也就是靠近USB的那个,目前只有HDMI0支持显示系统桌面。出厂默认安装Ubuntu系统,支持Xface桌面,这默认用户名是HwHiAiUser,默认密码是Mind@123:
登录之后,先试一下联网,有线网直接插上就可以自动识别连接,WIFI也可以正常连接,ping一下看是否能正常联网:
4.系统自带算法模型
出产时预装的系统中,自带了几个常见的算法模型样例,有目标识别、语音识别等等,是基于JupyterLab做的,具体怎么使用样例可以参考用户手册,这里就不详细展开讲了,我们这里来测试一下第一个目标识别算法样例,首先测试一下图片,图片是样例自带的,结果如下:
我们再去网上随便找一张图片试试效果:
5.车道线识别算法测试
我们再自己做个小算法测试一下。车道线检测是自动驾驶或辅助驾驶中的一个基本算法,检测到车道线后才可能实现车道偏离预警、车道保持辅助以及更高级别的领航辅助等功能。算法的原理和实现网上都能找到不少资料,这里不再赘述。测试使用的算法代码如下:
blur_ksize = 5 # 高斯模糊核大小
canny_lthreshold = 50 # Canny边缘检测低阈值
canny_hthreshold = 150 # Canny边缘检测高阈值
# 霍夫变换参数
rho = 1 #rho的步长,即直线到图像原点(0,0)点的距离
theta = np.pi / 180 #theta的范围
threshold = 15 #累加器中的值高于它时才认为是一条直线
min_line_length = 40 #线的最短长度,比这个短的都被忽略
max_line_gap = 20 #两条直线之间的最大间隔,小于此值,认为是一条直线
#img是输入的图像,verticess是兴趣区的四个点的坐标(三维的数组)
def roi_mask(img, vertices):
mask = np.zeros_like(img) #生成与输入图像相同大小的图像,并使用0填充,图像为黑色
mask_color = 255
cv2.fillPoly(mask, vertices, mask_color) #使用白色填充多边形,形成蒙板
masked_img = cv2.bitwise_and(img, mask) #img&mask,经过此操作后,兴趣区域以外的部分被蒙住了,只留下兴趣区域的图像
return masked_img
# 对图像进行画线
def draw_lines(img, lines, color=[255, 255, 0], thickness=2):
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(img, (x1, y1), (x2, y2), color, thickness)
def hough_lines(img, rho, theta, threshold,
min_line_len, max_line_gap):
lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]),
minLineLength=min_line_len,
maxLineGap=max_line_gap)
line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8) #生成绘制直线的绘图板,黑底
# draw_lines(line_img, lines)
draw_lanes(line_img, lines)
return line_img
def draw_lanes(img, lines, color=[255, 255, 0], thickness=8):
left_lines, right_lines = [], [] #用于存储左边和右边的直线
for line in lines: #对直线进行分类
for x1, y1, x2, y2 in line:
k = (y2 - y1) / (x2 - x1)
if k < 0:
left_lines.append(line)
else:
right_lines.append(line)
if (len(left_lines) <= 0 or len(right_lines) <= 0):
return img
clean_lines(left_lines, 0.1) #弹出左侧不满足斜率要求的直线
clean_lines(right_lines, 0.1) #弹出右侧不满足斜率要求的直线
left_points = [(x1, y1) for line in left_lines for x1, y1, x2, y2 in line] #提取左侧直线族中的所有的第一个点
left_points = left_points + [(x2, y2) for line in left_lines for x1, y1, x2, y2 in line] #提取左侧直线族中的所有的第二个点
right_points = [(x1, y1) for line in right_lines for x1, y1, x2, y2 in line] #提取右侧直线族中的所有的第一个点
right_points = right_points + [(x2, y2) for line in right_lines for x1, y1, x2, y2 in line] #提取右侧侧直线族中的所有的第二个点
left_vtx = calc_lane_vertices(left_points, 325, img.shape[0]) #拟合点集,生成直线表达式,并计算左侧直线在图像中的两个端点的坐标
right_vtx = calc_lane_vertices(right_points, 325, img.shape[0]) #拟合点集,生成直线表达式,并计算右侧直线在图像中的两个端点的坐标
cv2.line(img, left_vtx[0], left_vtx[1], color, thickness) #画出左侧直线
cv2.line(img, right_vtx[0], right_vtx[1], color, thickness) #画出右侧直线
#将不满足斜率要求的直线弹出
def clean_lines(lines, threshold):
slope = [(y2 - y1) / (x2 - x1) for line in lines for x1, y1, x2, y2 in line]
while len(lines) > 0:
mean = np.mean(slope) #计算斜率的平均值,因为后面会将直线和斜率值弹出
diff = [abs(s - mean) for s in slope] #计算每条直线斜率与平均值的差值
idx = np.argmax(diff) #计算差值的最大值的下标
if diff[idx] > threshold: #将差值大于阈值的直线弹出
slope.pop(idx) #弹出斜率
lines.pop(idx) #弹出直线
else:
break
#拟合点集,生成直线表达式,并计算直线在图像中的两个端点的坐标
def calc_lane_vertices(point_list, ymin, ymax):
x = [p[0] for p in point_list] #提取x
y = [p[1] for p in point_list] #提取y
fit = np.polyfit(y, x, 1) #用一次多项式x=a*y+b拟合这些点,fit是(a,b)
fit_fn = np.poly1d(fit) #生成多项式对象a*y+b
xmin = int(fit_fn(ymin)) #计算这条直线在图像中最左侧的横坐标
xmax = int(fit_fn(ymax)) #计算这条直线在图像中最右侧的横坐标
return [(xmin, ymin), (xmax, ymax)]
cap = cv2.VideoCapture('lane.jpg')
if (cap.isOpened()): # 打开成功
flag = 1
else:
flag = 0
num = 0
if (flag):
while (True):
ret,frame = cap.read()
if ret == False:
break
plt.imshow(frame)
gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) #图像转换为灰度图
blur_gray = cv2.GaussianBlur(gray, (blur_ksize, blur_ksize), 0, 0) #使用高斯模糊去噪声
edges = cv2.Canny(blur_gray, canny_lthreshold, canny_hthreshold) #使用Canny进行边缘检测
roi_vtx = np.array([[(0, frame.shape[0]), (460, 325),
(520, 325), (frame.shape[1], frame.shape[0])]]) ##目标区域的四个点坐标,roi_vtx是一个三维的数组
roi_edges = roi_mask(edges, roi_vtx) #对边缘检测的图像生成图像蒙板,去掉不感兴趣的区域,保留兴趣区
line_img = hough_lines(roi_edges, rho, theta, threshold,
min_line_length, max_line_gap) #使用霍夫直线检测,并且绘制直线
res_img = cv2.addWeighted(frame, 0.8, line_img, 1, 0) #将处理后的图像与原图做融合
plt.imshow(res_img)
测试使用的输入图片如下:
运行后的结果如下图所示,我们可以看到车道线被用黄色线标记了出来:
当前只是实现了图片中的车道线检测,后续会继续实现视频中的车道线检测,因为时间的原因目前只能测试这么多,后面会实现更多的算法来进行测试。
6.总结
我其实主业是做MCU的,SOC之前用的并不多,所以这次体验地也比较粗浅,但仍然能感觉到这块开发板对于我这种小白来说是非常容易上手的,拿到手之后就已经给装好了系统,开机即用,官方提供的使用手册也很详细,按照手册很快就能在板子上验证自己的算法。板载接口也很丰富,两个HDMI接口、Type-C、有线网口、无线天线等等一应俱全,可以满足日常使用的绝大部分需求。美中不足的可能是官方系统目前还不能支持双屏桌面输出,期待日后够完善。之前买过一块树莓派,对比下来感觉香橙派AIpro更“亲民”,不只是在价格,更是在一些小的细节上,比如板载的两个标准HDMI接口就比树莓派的两个Mini HDMI更符合我们日常的使用习惯,省去了转接头的麻烦。虽然说目前网上能找到的各种资料还是树莓派相对多一些,但毕竟他们起步早,我们现在也有昇腾社区和论坛,相信在不久的将来国产AI芯片也一定能闯出一片自己的天地!