这是本人的第一篇博客,之前早就想通过写博客来记录研究生期间的学习生活了,结果一直拖到开学七八周。。。之前搞的一个项目说写上来也没写,哎,懒癌晚期无救了,如果后面有时间再把它搞上来吧。
废话不多说,本篇文章的主要任务是基于树莓派4B开发板对一段老鼠实验视频的每一帧进行处理,将每一帧转化为二值图像,然后通过C++写的边缘检测算法标记出图像中老鼠的边缘点,并将每个边缘点像素的位置信息保存在一个二维数组中。要求:在树莓派上跑到30帧。
二.树莓派的基本使用1.连接树莓派
树莓派的连接方法非常多,说明书上都有详细说明,由于我的电脑主机上只有一个网线接口,因此我是通过共享手机热点进行无线连接,树莓派连接wifi需要自己创建一个配置文件(请自行百度),树莓派设置好后连接步骤如下:
1.连接树莓派电源,开启手机热点(要在电脑网络管理中将这个wifi设为共享)
2.下载advanced ip scanner软件,扫描获得树莓派IP地址(还有很多方法,推荐这种)
3.Win+r 打开运行的窗口,输入mstsc,进入windows自带的远程桌面连接,输入IP,连接
4.输入树莓派用户名:pi ,密码:raspberry即可连接到树莓派
树莓派与PC间传输文件
PC上下载FileZilla软件,通过FTP(File Transfer Protocol)协议传输文件,步骤如下:
1.在树莓派上启动FTP服务:sudo service vsftpd start
2.修改配置文件:sudo nano /etc/vsftpd.conf,修改下列配置项:
local_enable=YES
write_enable=YES
local_umask=022
(这两步操作操作一次即可)
3.打开FileZilla,输入树莓派IP、用户名和密码即可进行文件传输
这个检测算法是我看了书自己写的,算法原理如果有时间我会详细写出来。算法使用C++编写,调用OpenCv库进行了一些视频读取、图像腐蚀、膨胀、二值化等操作(这些简单处理就懒得用C++写了,而且主要是我不知道怎么用纯C++读取视频帧。。。o(╥﹏╥)o原谅我菜)核心边缘检测算法还是要用C++写,因为都用OpenCv的话到时候在树莓派上可能运行速度慢。
注:下面代码OpenCv的版本应在3.0以上,不然cvtColor()和threshold()函数最后一个参数会报错。
代码如下:
#include "opencv2/opencv.hpp"
#include <iostream>
#include <ctime>
using namespace std;
using namespace cv;
const string VIDEO_ADDR = "D:\\老鼠实验原始视频\\MPEG0002.avi"; //视频地址
const int ELEMENT_SIZE = 10; //结构元素大小
const int THREHOLD = 100; //将小于THREHOLD的像素置为MAXVAL_THREHOLD,否则设为0
const int MAXVAL_THREHOLD = 150; //为了方便画出边缘
const int IMG_ROWS = 600; //图像行数
const int IMG_COLUMNS = 800; //图像列数
const int START_ROW = 10; //从第10行,第400列开始向下找第一个边界点
const int START_COLUMNS = 400; //注:我是正中央向下找,根据不同情况可以自由变化
/*找到老鼠的边界,并在新的图像上画出*/
void get_boundaries()
{
VideoCapture capture(VIDEO_ADDR); //打开默认摄像头(视频)
Mat original_frame; //原始视频帧
Mat gray_frame; //灰度帧
Mat binary_frame; //二值帧
Mat element = getStructuringElement(MORPH_RECT, Size(ELEMENT_SIZE, ELEMENT_SIZE)); //获取自定义结构元素
/*测试检测时间所需变量*/
//double run_time;
//int count_frame = 0; //帧计数
//time_t t1 = time(0);
//time_t t2 = time(0);
while (1)
{
int boundary_point_addr[1000][2]; //保存边界的地址的二维数组
int boundary_point_num = 0; //计数找到多少个边界点
bool start_detection = true; //开始检测标志
int start_x = 0;
int start_y = 0; //第一个边界点的位置
int cur_x = 0;
int cur_y = 0; //当前边界点的位置
int cur_derection = 0; //当前探测方向
int search_times; //每个点的搜索次数
int maximum_detections = 0; //每帧最大检测次数
/*搜索方向数组,{左上, 左, 左下, 下, 右下, 右, 右上, 上}*/
int direction[8][2] = { {-1,-1}, {0,-1}, {1,-1}, {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0} };
//count_frame ++;
capture >> original_frame; //获取当前帧
cvtColor(original_frame, gray_frame, COLOR_BGR2GRAY); //转为灰度图
erode(gray_frame, gray_frame, element); //腐蚀
dilate(gray_frame, gray_frame, element); //膨胀
threshold(gray_frame, binary_frame, THREHOLD, MAXVAL_THREHOLD, THRESH_BINARY_INV); //二值化,阈值100,小于100设为MAXVAL_THREHOLD
/*寻找边界*/
for (int i = START_ROW - 1;i < IMG_ROWS;i++) //从第10行第400列向下找第一个边界点
{
if (binary_frame.ptr<uchar>(i)[START_COLUMNS] == MAXVAL_THREHOLD) //找到第一个边界点
{
binary_frame.ptr<uchar>(i)[START_COLUMNS] = 255; //将边界点置为白色方便观察
boundary_point_addr[boundary_point_num][0] = i;
boundary_point_addr[boundary_point_num][1] = START_COLUMNS; //记录第一个边界点的位置
start_x = i;
start_y = START_COLUMNS;
cur_x = i;
cur_y = START_COLUMNS;
boundary_point_num++;
while ((start_x != cur_x) || (start_y != cur_y) || start_detection) //未回到原点
{
start_detection = false;
//下一个检测位置
if (cur_x + direction[cur_derection][0] >= 0 && cur_x + direction[cur_derection][0] < IMG_ROWS)
cur_x += direction[cur_derection][0];
if (cur_y + direction[cur_derection][1] >= 0 && cur_y + direction[cur_derection][1] < IMG_COLUMNS)
cur_y += direction[cur_derection][1];
search_times = 1;
//判断是否为边界点,不是则继续改变方向检测
while (binary_frame.ptr<uchar>(cur_x)[cur_y] != MAXVAL_THREHOLD)
{
cur_derection++; //搜索方向逆时针旋转45°
if (cur_derection >= 8)
cur_derection = 0;
if (++search_times >= 8) //8领域中都没有边界点,说明是孤立点
break;
if (cur_x + direction[cur_derection][0] >= 0 && cur_x + direction[cur_derection][0] < IMG_ROWS)
cur_x += direction[cur_derection][0];
if (cur_y + direction[cur_derection][1] >= 0 && cur_y + direction[cur_derection][1] < IMG_COLUMNS)
cur_y += direction[cur_derection][1];
}
//找到了下一个边界点,位置是(cur_x, cur_y)
if (boundary_point_num % 2 == 0) //使边界点变稀疏,隔一个点记录一次
{
binary_frame.ptr<uchar>(cur_x)[cur_y] = 255; //将边界点置为白色
boundary_point_addr[boundary_point_num][0] = cur_x;
boundary_point_addr[boundary_point_num][1] = cur_y; //记录这个边界点的位置
}
boundary_point_num++;
cur_derection -= 2; //将当前检测方向顺时针回转90°作为下一次检测的初始方向
if (cur_derection < 0)
cur_derection += 8;
if (++maximum_detections >= 800) //最多检测800次
break;
}
imshow("原始视频", original_frame);
imshow("边缘检测结果", binary_frame);
waitKey();
break;
}
}
//if (count_frame == 500) //500帧的时候得到运行时间t2,求差值即可算出500帧的运行时间
//{
//t2 = time(0);
//run_time = difftime(t2, t1); //t2和t1的差值
//cerr << "The time to run 500frame is:" << run_time << endl;
//break;
//}
}
}
int main()
{
get_boundaries();
return 0;
}
在PC上的运行结果如下:
可见,通过边缘检测算法,成功将老鼠的边缘像素点检测了出来,并用白色像素点(255)做了标记。
1.树莓派环境安装OpenCv
网上安装OpenCv的教程可谓种类繁多,基于python2和python3的安装方法还不一样,基于python3的安装好像还要拓展树莓派的根目录(怕了怕了。。—. —||)但是我们并不需要那么麻烦,经过实践测试用两行代码就可以安装好:
sudo apt-get install libopencv-dev
sudo apt-get install python-opencv
安装完之后可通过python查看安装的版本:命令行输入python,回车;再输入:
import cv2
cv2.__version__
即可查看安装的OpenCv版本:
我第一次安装的版本是2.4,第二次又成了3.2了,并不是很懂。不过安装哪个版本都无所谓,只要上面的代码做相应修改即可:
如果安装的opencv2.x,修改这两行:
cvtColor(original_frame, gray_frame, CV_BGR2GRAY);
threshold(gray_frame, binary_frame, THREHOLD, MAXVAL_THREHOLD, CV_THRESH_BINARY_INV);
如果安装的opencv3.x,修改:
cvtColor(original_frame, gray_frame, COLOR_BGR2GRAY);
threshold(gray_frame, binary_frame, THREHOLD, MAXVAL_THREHOLD, THRESH_BINARY_INV);
2.通过g++编译程序
编译前注意修改视频路径,不然会报奇怪的错,我在树莓派上把路径改成了(反斜杠/表示相对地址):
const string VIDEO_ADDR = “./Vedio/MPEG0002.avi”; //视频地址
然后进入当前目录,命令行输入:
g++ `pkg-config --cflags opencv` Mice_Processing01.cpp -o mice `pkg-config --libs opencv`
意思是把Mice_Processing01.cpp编译成名为mice的可执行文件,编译好后结果如下:
然后再在命令行输入:
./mice 2> show_time.txt
注:2> show_time.txt是为了将程序中的打印信息保存到show_time.txt文件中,若无需打印可以不加。在树莓派上的运行效果如下:
3.树莓派处理速度
经测试(通过代码中注释部分),若对每一帧图像进行腐蚀和膨胀处理,树莓派处理500帧图像的时间为22s(处理速度约23帧/s,不满足要求);
若不进行腐蚀和膨胀处理,树莓派处理500帧图像的时间为5s(100帧/s,满足要求)。
而经过实验,不进行腐蚀和膨胀处理对检测结果基本无影响,因为每帧图像表示的信息较为简单,没有开处理的必要。综上,任务完成!