省电赛需求,要在单片机平台上使用摄像头模组采集图像,通过串口通信或者其他通信方式传输到上位机,由上位机来识别并发送指令给下位机。识别目标是绿色的未成熟柚子,如下图。通过颜色的识别是不太现实的了,但幸好柚子形状近似圆形,所以想到通过使用Hough变换检测圆,从而检测柚子。
用来模拟在树上的未成熟柚子
1.硬件平台
STM32F103ZET6单片机
OV7670带FIFO摄像头模组,最高支持320*240分辨率
一台有USB接口的电脑
2.上位机软件
我使用Qt进行开发。这个我个人还是比较有经验的。这里主要是RGB565转RGB888编码算法和串口通信的编写。
RGB565是一种颜色编码方式,相对于一般的RGB888(红绿蓝各8位,三个字节)的存储方式更节省空间,一般用在存储空间比较少的场合,比如嵌入式系统。她可以存储256*256=65536种颜色,图片质量相比而言不会受到太大影响。
RGB565
RGB565转RGB888的主要思路是截取有效的5/6/5位,然后通过左移和右移使其实际只有8位(高8位为0),最后拼接到代表RGB888编码的uint类型变量。
/************颜色编码转换*************/#define RGB888_RED 0x00ff0000#define RGB888_GREEN 0x0000ff00#define RGB888_BLUE 0x000000ff#define RGB565_RED 0xf800#define RGB565_GREEN 0x07e0#define RGB565_BLUE 0x001f//编码转换unsigned short RGB888ToRGB565(unsigned int n888Color){ unsigned short n565Color = 0; // 获取RGB单色,并截取高位 unsigned char cRed = (n888Color & RGB888_RED) >> 19; unsigned char cGreen = (n888Color & RGB888_GREEN) >> 10; unsigned char cBlue = (n888Color & RGB888_BLUE) >> 3; // 连接 n565Color = (cRed << 11) + (cGreen << 5) + (cBlue << 0); return n565Color; }unsigned int RGB565ToRGB888(unsigned short n565Color){ unsigned int n888Color = 0; // 获取RGB单色,并填充低位 unsigned char cRed = (n565Color & RGB565_RED) >> 8; unsigned char cGreen = (n565Color & RGB565_GREEN) >> 3; unsigned char cBlue = (n565Color & RGB565_BLUE) << 3; // 连接 n888Color = (cRed << 16) + (cGreen << 8) + (cBlue << 0); return n888Color; }
在MainWindow种初始化一些参数
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //初始化时获取可用的串口 initSerialPort(); //创建一个串口对象 tc = QTextCodec::codecForName("GBK"); //支持汉字编码 serial = new QSerialPort; //当串口准备好读数据时,调用读取数据的函数 connect(serial,SIGNAL(readyRead()),this,SLOT(readSerialData())); //接收数据框设置为只读模式 // ui->textEdit->setReadOnly(true); //初始化波特率,默认921600 baudBox=new QComboBox; QStringList baudItems; baudItems<<"1382400"<<"921600"<<"46080"<<"256000"<<"230400" <<"128000"<<"115200"<<"76800"<<"57600"<<"43000"<<"38400"<<"19200" <<"14400"<<"9600"<<"4800"<<"2400"<<"1200"; ui->baudBox->addItems(baudItems); ui->baudBox->setCurrentIndex(1); ui->sendButton->setEnabled(false);//开启串口之前限制发送按钮}
不断刷新串口列表
//鼠标点击刷新当前存在的串口void MainWindow::mousePressEvent(QMouseEvent *e) { serial->clear(); //清掉所有串口,重新检测当前串口 ui->portBox->clear();//删除当前所以已经存在的串口号 initSerialPort(); }void MainWindow::initSerialPort() { QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts(); if(infos.isEmpty()) { // QMessageBox::information(this,"提示信息","当前没有可用的串口"); return; } //将搜索串口号添加到下拉列表中 foreach (QSerialPortInfo info, infos) { ui->portBox->addItem(info.portName()); } }
打开串口时根据下拉框的参数进行初始化
//打开或者关闭串口void MainWindow::on_openSerialButton_toggled(bool checked) { if(ui->connectPushButton->text() == "断开") { QMessageBox::information(this,"提示信息","请关闭WIFI连接"); return; } //这里可以输出一些串口出错的信息 if(serial->error()==serial->DeviceNotFoundError) { QMessageBox::information(this,"提示信息","串口打开失败"); return; } if(checked) { ui->openSerialButton->setText(tr("关闭串口")); //设置串口名 serial->setPortName(ui->portBox->currentText()); //打开串口 serial->open(QIODevice::ReadWrite); //设置波特率 serial->setBaudRate(ui->baudBox->currentText().toInt()); //设置数据位 serial->setDataBits(QSerialPort::Data8); //设置奇偶校验 serial->setParity(QSerialPort::NoParity); //设置停止位 serial->setStopBits(QSerialPort::OneAndHalfStop); //关闭串口设置按钮,使能数据发送按钮 ui->portBox->setEnabled(false); ui->baudBox->setEnabled(false); ui->sendButton->setEnabled(true); } else { ui->openSerialButton->setText(tr("打开串口")); //关闭串口 serial->clear(); serial->close(); //使能串口设置按钮,关闭数据发送按钮 ui->portBox->setEnabled(true); ui->baudBox->setEnabled(true); ui->sendButton->setEnabled(false); } }
读取和清除
//串口读取数据void MainWindow::readSerialData() {//接收的时候要将QByteArray转换成String才能够方便的显示在textEdit中 QByteArray buf; buf=serial->readAll(); if(buf!=NULL) { data+=(tc->toUnicode(buf)); } buf.clear(); }//清除数据接收区的数据void MainWindow::on_clearDataButton_clicked() { data.clear(); random_color(); ui->leftLabel->setText("柚子呢?"); ui->rightLabel->setText("柚子呢?"); update(); // ui->textEdit->clear();}
3.图像识别
由于不需要颜色信息,我在OV7670模式选择中使用黑白模式,这样噪点会比较少
原始图像
有时依旧会有不少噪点,先来一发高斯模糊再说
//读取图像并定义处理过程中使用的MatMat originalImageL =imread("/original_left.png"); Mat cannyImageL; Mat closeImageL; Mat contoursImageL = Mat::zeros(originalImageL.size(),CV_8U);;vector<vector<Point>> contoursL; Mat outputImageL = originalImageL.clone();//滤波去除噪点GaussianBlur(originalImageL,originalImageL,cv::Size(BLUR_SIZE,BLUR_SIZE),1.5);
cv::Size(BLUR_SIZE,BLUR_SIZE)是卷积核的大小,最后一个参数是高斯函数的σ。效果如下,稍微有点糊但无伤大雅。
高斯模糊去噪
Canny边缘检测。参数要多次尝试
//Canny边缘检测Canny(originalImageL,cannyImageL,CANNY_VAR/2,CANNY_VAR);
Canny算子通常基于Soble算子。通过一个高阈值的Sobel和低阈值的Sobel综合考量可以得到比较令人满意的结果。柚子的大概轮廓出来了,虽然周围的树叶的轮廓会对识别有干扰,但没关系。
Canny
闭运算去除细小纹理
//结构元素Mat element5(M_R_SIZE,M_R_SIZE,CV_8U,cv::Scalar(1)); morphologyEx(cannyImageL,closeImageL,MORPH_CLOSE,element5);
闭运算的定义是对图像先膨胀后腐蚀。比结构元素小的空隙和间隙会被闭合滤波器消除。可以看到柚子周围的小轮廓基本消除了。
Close
闭运算的定义是对图像先膨胀后腐蚀。比结构元素小的空隙和间隙会被闭合滤波器消除。
提取连续区域中的轮廓(非必需)
findContours(closeImageL,contoursL, CV_RETR_LIST,CV_CHAIN_APPROX_NONE); drawContours(contoursImageL, contoursL, -1, Scalar(255, 0, 255));
第三个参数使用CV_RETR_LIST 指提取所有轮廓。其他的值可以提取其中的层次结构,这里不介绍。经实验发现这一步可以略过,但加上的话更加直观。
findContours
Hough变换检测圆形
std::vector<cv::Vec3f> circlesL; HoughCircles(contoursImageL,circlesL,HOUGH_GRADIENT,2,CIRCLES_BAR, CANNY_VAR,HOUGH_STD,R_MIN,R_MAX);std::vector<cv::Vec3f>::const_iterator itcL = circlesL.begin();while(itcL!=circlesL.end()) { cv::circle(outputImageL,cv::Point((*itcL)[0],(*itcL)[1]),(*itcL)[2], cv::Scalar(188),4); cout << cv::Point((*itcL)[0],(*itcL)[1]) << (*itcL)[2] <<endl; ++itcL; }
HoughCircles这个函数比较有意思,有必要看看函数原型。
void HoughCircles( InputArray image, OutputArray circles, int method, double dp, double minDist, double param1 = 100, double param2 = 100, int minRadius = 0, int maxRadius = 0 );
源码中参数解释的翻译大概如下
§ method 使用的检测方法,一般使用霍夫梯度法,它的标识符为CV_HOUGH_GRADIENT
§ dp 用来检测圆心的累加器图像的分辨率于输入图像之比的倒数
§ minDist 圆之间最小的距离
§ param1 第三个参数 method设置的检测方法的对应的第一个参数,对于CV_HOUGH_GRADIENT为传递给canny边缘检测算子的高阈值
§ param2 第三个参数 method设置的检测方法的对应的第二个参数,表示在检测阶段圆心的累加器阈值,越低检测出的圆越多
§ minRadius 圆最小半径
§ minRadius 圆最大半径
作者:Jacob杨帮帮
链接:https://www.jianshu.com/p/e78afd18d411
共同學(xué)習(xí),寫(xiě)下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章
100積分直接送
付費(fèi)專(zhuān)欄免費(fèi)學(xué)
大額優(yōu)惠券免費(fèi)領(lǐng)