赞
踩
在树莓派上用OpenCV对摄像头中的图像进行处理,将图像处理后的数据通过串口通信给到下位机STM32F103C8T6,再由下位机给出控制信号,利用pid算法实现对小车运动轨迹的控制。硬件连接实物如下图所示。(本文章只讲述到树莓派与下位机之间通信的部分)
可以参考 学习笔记一:树莓派与STM32的UART通信 这篇博客的第一章,里面详细讲述了如何改变串口的映射和mini串口调试助手的安装及使用。其中在我安装好minicom后,在终端输入 minicom -D /dev/ttyAMA0 后确实出现了提示没有权限的情况,这个时候需要现在终端输入 sudo chmod 777 /dev/ttyAMA0 再输入 minicom -D /dev/ttyAMA0 就可以正常打开miniocm了。
按照上面这篇博客的步骤,确保树莓派和电脑之间可以正常通信后再进行下一步操作。
在使用OpenCV前可以再确认一下树莓派的通信是否正常,可以试着运行下面的代码。 如果串口通正常,将会间接收到从1-100的数字。
- import serial
- import time
-
- ser = serial.Serial('/dev/ttyAMA0',115200)
- num = 1
-
- while True:
-
- ser.write(str(int(num)).encode() + '\r\n')
- num += 1
- if num > 100:
- num = 1
-
- time.sleep(0.2)
在确定串口通信正常后,就可以用OpenCV来进行图像处理,在下面呈上我使用的代码。为了使对黑色的识别效果更好,我在代码中加入了高斯模糊来减小噪声,黑色的阈值选定的是60,大家也可以根据具体情况来适当改编代码。
- import cv2
- import numpy as np
- import serial
- import time
-
- def main():
- # 打开摄像头
- cap = cv2.VideoCapture(0)
-
- # 检查摄像头是否成功打开
- if not cap.isOpened():
- print("无法打开摄像头")
- return
-
- ser = serial.Serial('/dev/ttyAMA0',115200)
-
- while True:
-
- start_time = time.time()
-
- # 读取当前帧
- ret, frame = cap.read()
-
- # 检查帧是否读取正确
- if not ret:
- print("???????")
- break
-
- # 将图片转到灰度值
- gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
-
- #高斯模糊
- blurred = cv2.GaussianBlur(gray, (5, 5), 0)
-
- # 设定黑色的阈值范围
- _, threshold = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY_INV)
-
- # 寻找轮廓
- kernel = np.ones((5, 5), np.uint8)
- opening = cv2.morphologyEx(threshold, cv2.MORPH_OPEN, kernel)
-
- _, contours, hierarchy = cv2.findContours(opening, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
-
- # 绘制轮廓
- centers = []
- for contour in contours:
- # 计算轮廓的边界值
- x, y, w, h = cv2.boundingRect(contour)
- if w * h > 100: # 只显示较大的轮廓
- cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
- center_x = x + w //2
- centers.append(center_x)
-
- #发送黑色域的水平中点坐标
- if centers:
- message =str(centers[0]).encode() + b'\n'
- ser.write(message)
-
- # 显示原始图像和结果图像
- cv2.imshow('Frame', frame)
- cv2.imshow('Threshold', threshold)
-
- # 按q退出
- if cv2.waitKey(1) & 0xFF == ord('q'):
- break
-
- time.sleep(max(0,0.05 - (time.time() - start_time)))
-
- # 释放摄像头
- cap.release()
- # 关闭所有窗口
- cv2.destroyAllWindows()
-
- if __name__ == '__main__':
- main()
其中,在调用 cv2.findContours 函数时,可能会因为OpenCV版本的问题而导致返回值个数的不同,会出现 “ValueError: too many values to unpack (expected 2)” 的报错。如果在运行中出现了这个报错,可以将寻找轮廓的代码换成下面这段,这样就可以避免因版本不同而带来的问题。
- # 寻找轮廓
- try:
- # OpenCV 4.x及一些3.x版本
- contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
- except ValueError:
- # OpenCV 3.x的更早版本
- _, contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
如果代码运行无误,那么你将会得到如下图所示的结果 ,总体来说识别效果还算不错,黑色区域的轮廓也还算清晰。同时,如果你可以在电脑串口助手上收到黑色区域的水平中点值,那么树莓派的配置工作到此为止就圆满结束了。
因为本博客不涉及到控制部分,所以下位机的配置就相对比较简单了,只需要简单的串口接收模块就可以,这里就简单带过,具体有问题的可以参考我的另一篇色块追踪的博客,里面有初始化配置和串口初始化的详细过程,虽然是F407ZGT6的,但是逻辑上和F103C8T6没有太大区别,在此附上连接【OpenMV+STM32】PID控制二维自由舵机色块追踪。
因为芯片不同,所以在时钟树的配置上与F4是不同的,具体数值可以看下图。
在串口的配置上需要将UART1和USART2都打开,UART1用于与树莓派通信,而UART2用于与电脑通信,便于中间过程的调参。(USART2配置同理)
①在usart.c的最后加上串口重定向代码。
- int fputc(int ch, FILE *f)
- {
- HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xffff);
- return ch;
- }
- int fgetc(FILE *f)
- {
- uint8_t ch = 0;
- HAL_UART_Receive(&huart2, &ch, 1, 0xffff);
- return ch;
- }
②在usart.h中加入库
#include <stdio.h>
③在main.c中相应的地方加入串口初始配置
- #include <string.h>
-
- #define RxBuffer_MaxSize 256 //最大接收字节数
- char RxBuffer[RxBuffer_MaxSize],rx_buf[RxBuffer_MaxSize]; //接收数据
- uint8_t aRxBuffer; //接收中断缓冲
- uint8_t Uart1_Rx_Cnt = 0; //接收缓冲计数
-
- /* USER CODE BEGIN 2 */
- HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
- /* USER CODE END 2 */
④在main.c后加入串口回调函数
- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
- {
- UNUSED(huart);
- if(huart == &huart1){
- // HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 有数据则翻转LED灯
-
- RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;
-
- if((RxBuffer[Uart1_Rx_Cnt-1] == '\n'))
- { // 检测到帧尾
- RxBuffer[Uart1_Rx_Cnt-1] = '\0'; // 替换帧尾为字符串结束符
- strcpy(rx_buf, &RxBuffer[0]); // 复制数据到rx_buf,跳过帧头
- printf("%s\r\n", rx_buf);
- Uart1_Rx_Cnt = 0; // 重置计数器
- memset(RxBuffer, 0, sizeof(RxBuffer)); // 清空接收缓冲区
- }
-
- HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
- }
- }
由于在树莓派的串口发送中并没有设置帧头,并将帧尾设置成了'\n',所以串口接收的代码相对较简单。如果代码运行无误,且单片机与电脑间的通信顺利,则电脑端也将会收到黑色区域的水平中点值,正如如视频中所示。
黑色域水平中点值传输
本博客只是智能巡线小车中视觉的一部分,后续如果时间允许的话会将整个巡线的功能都写下来,同时之后如果有更好的图像处理代码我也会同步在此篇博客中修改。大家如果在配置过程中遇到什么问题或者发现此博客有任何问题,欢迎私信我或者直接在评论里留言。另外,如果大家现在就对控制模块感兴趣的话,不妨去看下我同实验室队友‘南极熊ii’的博客,他写过一些关于驱动电机的内容,在此附上链接。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。