本帖最后由 aramy 于 2024-5-27 08:58 编辑
非常漂亮的小板子。非常喜欢,可惜板子上的机器学习使用的是C来完成的,自己只玩过python的,还玩的不好,有点崩溃了。记录一下这些日子,折腾的过程吧。
一、镜像烧写。
在官网https://wiki.luckfox.com/zh/Luckfox-Pico/Luckfox-Pico-prepare提供了linux的镜像。提供的有ubuntu的镜像和buildroot镜像。
1镜像选择。Ubuntu的镜像,读取摄像头有问题,而且后续官方的例子都是基于buildroot镜像的,所以这里选择buildroot的镜像。手头板子是MAX版本的,所以选择对应的镜像。
2、镜像烧写。按官方文档,按下boot按键,再插入USB线。自己的win电脑始终无法通过SocToolKit工具烧写,这里我使用linux下的烧写方法烧写镜像。
这个镜像与收到板卡上自带的镜像文件是一致的。可以直接使用。使用ssh登录,登录账号:root;登录密码:luckfox;静态IP地址:172.32.0.93
二、开发环境搭建。
开发环境搭建,这里我是使用docker方式搭建。参考https://wiki.luckfox.com/zh/Luckfox-Pico/Luckfox-Pico-SDK,使用docker还是比较简单的。
docker run -it --name luckfox -v /home/aramy/luckfoxtech:/home/luckfoxtech luckfoxtech/luckfox_pico:1.0 /bin/bash
首先尝试在这个环境下编译一下镜像文件。这一步超漫长(大概4小时+),制作镜像不是必要操作。但是下载luckfox-pico这个源码环境包是必须的,后边很多都依赖这里边的工具。
git clone https://gitee.com/LuckfoxTECH/luckfox-pico.git
cd luckfox-pico
git submodule update --init
#在本机上运行
cd tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/
source env_install_toolchain.sh
#进入docker的开发环境
./build.sh lunch
#选择8
./build.sh clean
尝试跑一下RKNN推理测试。
git clone https://github.com/rockchip-linux/rknn-toolkit2
先下载源码包,进入docker的环境,先配置一下 TOOLCHAIN交叉工具链环境变量。
#这里需要按自己搭建的环境进行修改。
export RK_RV1106_TOOLCHAIN=/home/luckfoxtech/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf
cd /home/luckfoxtech/rknpu2/examples/RV1106_RV1103/rknn_yolov5_demo
chmod +x build-linux_RV1106.sh
./build-linux_RV1106.sh
编译完成后,生成一个install的文件夹。里边rknn_yolov5_demo为可执行文件。./model/RV1106/yolov5s-640-640.rknn为训练模型文件。将install文件夹下的所有文件 拷贝到RV1106的板子上,就可以跑例程了。拷贝文件我这里使用的是用ssh协议。在win下可以使用pscp,或者是ssh的图形化工具。
这里是使用自己的两张图片,进行识别,可以看出,第一张识别出两个cup,第二张识别出banana。官方提供的例程还是挺强大的。是一个分类例程,不过它只能识别coco_80_labels_list.txt文件定义了的80种物品。
三、尝试手写数字识别。
官方提供的例程是基于C语言的,而自己仅仅是在PC上使用Python跑过机器学习的例程。瞬间感觉差距太遥远了。
好在群里有老师,提供了例程,并且指导了如何实现,自己才磕磕碰碰地在RV1106上跑起来了手写数字识别。按自己的理解,整理了个处理流程图。
第一个学习的例程:https://github.com/luckfox-eng29/luckfox_pico_rtsp_opencv,这是一个获取摄像头图片,然后推流的项目。首先修改src下的main.cc文件,将摄像头的分辨率降低,然后在docker环境下编译该项目。
int width = 640;
int height = 480;
export LUCKFOX_SDK_PATH=/home/luckfoxtech/luckfox-pico/
mkdir build
cd build
cmake ..
make && make install
将编译完成的luckfox_rtsp_opencv_demo/luckfox_rtsp_opencv文件传到RV1106板子上。关闭RV1106上在运行的推流进程,然后给luckfox_rtsp_opencv添加可执行权限,并运行它。就可以是用VLC查看开发板的摄像头的图像了。
RkLunch-stop.sh
chmod 755 luckfox_rtsp_opencv
./luckfox_rtsp_opencv
第二个学习的例程:https://github.com/knva/luckfox_pico_rtsp_opencv
从这位老师的例程中,就可以看见整个手写识别的全流程实现。核心部分就是,从摄像头获取图片,然后寻找到数字部分的区域,截取该区域,转换为28X28灰度图,然后推理获得分类概率。在老师的指导下,终于在自己的电脑上完成了编译,并在RV1106开发板上跑了起来。
第三个学习的例程:https://gitee.com/luyism/luckfox_rtsp_mnist
从这个例程可以看出,核心思路还是没有变化。在处理上增加了许多处理。
// 在图像中找到数字的轮廓,同时减小找到轮廓时的抖动
cv::Rect find_digit_contour(const cv::Mat &image) {
// 预处理图像
cv::Mat gray, blurred, edged;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);
cv::Canny(blurred, edged, 30, 150);
// 应用形态学操作
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::dilate(edged, edged, kernel);
cv::erode(edged, edged, kernel);
// 查找轮廓,声明一个变量来存储轮廓
std::vector<std::vector<cv::Point>> contours;
cv::findContours(edged, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
if (contours.empty()) {
return cv::Rect();
}
// 找到最大的轮廓
auto largest_contour = std::max_element(contours.begin(), contours.end(),
[](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {
return cv::contourArea(a) < cv::contourArea(b);
});
// **轮廓面积过滤**:在找到轮廓之后,可以排除那些面积过小的轮廓。这样可以减少不必要的小轮廓对整体结果的影响。
if (cv::contourArea(*largest_contour) < 10) {
return cv::Rect();
}
// **轮廓形状过滤**:除了面积外,还可以考虑其他形状特征,如轮廓宽高比。这样可以排除一些不规则的轮廓,从而提高准确性。
cv::Rect bounding_box = cv::boundingRect(*largest_contour);
float aspect_ratio = static_cast<float>(bounding_box.width) / bounding_box.height;
if (aspect_ratio < 0.2 || aspect_ratio > 3) {
return cv::Rect();
}
// **轮廓稳定性检测**:
// 通过比较当前帧和之前几帧的轮廓位置来判断轮廓的稳定性。
// 如果多帧之间的轮廓位置变化较小,则可以认为轮廓比较稳定,不需要进行过多的调整。
static std::vector<cv::Rect> prev_bounding_boxes;
if (prev_bounding_boxes.size() > 5) {
prev_bounding_boxes.erase(prev_bounding_boxes.begin());
}
prev_bounding_boxes.push_back(bounding_box);
if (prev_bounding_boxes.size() == 5) {
float avg_width = 0.0;
float avg_height = 0.0;
for (const auto& box : prev_bounding_boxes) {
avg_width += box.width;
avg_height += box.height;
}
avg_width /= prev_bounding_boxes.size();
avg_height /= prev_bounding_boxes.size();
float width_diff = std::abs(bounding_box.width - avg_width) / avg_width;
float height_diff = std::abs(bounding_box.height - avg_height) / avg_height;
if (width_diff > 0.1 || height_diff > 0.1) {
return cv::Rect();
}
}
// 对图像边框每个方向扩大15个像素
bounding_box.x = std::max(0, bounding_box.x - 15);
bounding_box.y = std::max(0, bounding_box.y - 15);
bounding_box.width = std::min(image.cols - bounding_box.x, bounding_box.width + 30);
bounding_box.height = std::min(image.rows - bounding_box.y, bounding_box.height + 30);
// 返回最大轮廓的边界框
return bounding_box;
}
最终运行起来的结果,效果还是蛮赞的。
接下来,努力去读源代码,理解编程的思路和细节,去消化一下,在C环境下去实现机器学习的功能。