第七届全国大学生工程训练大赛垃圾分类
- 前言
- 一、机械结构设计
* - 1.Solidworks建模
- 2.建模的不足以及改进
- - 3.整体实物
- 二、视觉识别部分
* - 1.引入库
- 2.识别部分
- 三、上下位机通信方式:
* - 1.高低电平通信:
- 1.2高低电平树莓派部分:
- 2.stm32串口通信部分:
- 四、下位机电机驱动部分
* - 1.电机:涡轮蜗杆电机(履带负载较大,不可直接用步进直流电机)
- 2.有关于延时的改进:
- 3.stm32主函数:
- 五、炸电机:
文章目录
- 前言
- 一、机械结构设计
* - 1.Solidworks建模
- 2.建模的不足以及改进
- - 3.整体实物
- 二、视觉识别部分
* - 1.引入库
- 2.识别部分
- 三、上下位机通信方式:
* - 1.高低电平通信:
- 1.2高低电平树莓派部分:
- 2.stm32串口通信部分:
- 四、下位机电机驱动部分
* - 1.电机:涡轮蜗杆电机(履带负载较大,不可直接用步进直流电机)
- 2.有关于延时的改进:
- 3.stm32主函数:
- 五、炸电机:
前言
本人有幸代表内蒙古工业大学参加内蒙古自治区的全国工程训练大赛省赛,并在初赛取得前三名的成绩。可惜后来决赛由于树莓派死机导致程序崩溃,从而无缘国赛。但是经过测试,我们的识别程序可以做到识别率95%,分类准确率90%以上。
硬件设备:树莓派4B+8G(用于视觉识别以及播放视频)
stm32f103zet6 (用于下位机控制电机进行分类)
机械结构设计:双层履带交叉分拣
本人具有嵌入式开发经验两年,可代指导单片机,linux,物联网视觉识别,各种机器人的大创、毕设、项目 具体联系:qq
1549843074
一、机械结构设计
1.Solidworks建模
示例:如图所示,采用双层履带结构
; 2.建模的不足以及改进
1.挡板的添加
从上履带识别后,移交至第二层履带时。会出现飞出去的情况导致分类失败,于是我们在履带两侧以及垃圾桶整体四周加上挡板。这样情况大大改善。
挡板:
2.履带防滑
当进行调试过程中,如果出现瓶子、电池等容易滚的物体,很容易在投掷过程中滚下去导致无法实现识别。为此,我们决定在履带上用胶水粘上小突起,经检验这样能很好解决这个问题。
; 3.整体实物
整体实物图:
俯视图:
二、视觉识别部分
1.引入库
代码如下(示例):
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import io
import time
import numpy as np
import cv2
import RPi.GPIO as GPIO
from PIL import Image
from tflite_runtime.interpreter import Interpreter
2.识别部分
代码如下:
def load_labels(path):
with open(path, 'r') as f:
return {i: line.strip() for i, line in enumerate(f.readlines())}
def set_input_tensor(interpreter, image):
tensor_index = interpreter.get_input_details()[0]['index']
input_tensor = interpreter.tensor(tensor_index)()[0]
input_tensor[:, :] = image
def classify_image(interpreter, image, top_k=1):
"""Returns a sorted array of classification results."""
set_input_tensor(interpreter, image)
interpreter.invoke()
output_details = interpreter.get_output_details()[0]
output = np.squeeze(interpreter.get_tensor(output_details['index']))
if output_details['dtype'] == np.uint8:
scale, zero_point = output_details['quantization']
output = scale * (output - zero_point)
ordered = np.argpartition(-output, top_k)
return [(i, output[i]) for i in ordered[:top_k]]
def main():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'--model', help='File path of .tflite file.', required=True)
parser.add_argument(
'--labels', help='File path of labels file.', required=True)
args = parser.parse_args()
labels = load_labels(args.labels)
interpreter = Interpreter(args.model)
interpreter.allocate_tensors()
_, height, width, _ = interpreter.get_input_details()[0]['shape']
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH,640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
key_detect = 0
times=1
while (key_detect==0):
ret,image_src =cap.read(0)
frame_width=image_src.shape[1]
frame_height=image_src.shape[0]
cut_d=int((frame_width-frame_height)/2)
crop_img=image_src[0:frame_height,cut_d:(cut_d+frame_height)]
image=cv2.resize(crop_img,(224,224),interpolation=cv2.INTER_AREA)
start_time = time.time()
if (times==1):
results = classify_image(interpreter, image)
elapsed_ms = (time.time() - start_time) * 1000
label_id, prob = results[0]
print(labels[label_id],prob)
num=int(label_id)
cv2.putText(crop_img,labels[label_id] + " " + str(round(prob,3)), (5,30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 1, cv2.LINE_AA)
times=times+1
if (times>1):
times=1
cv2.imshow('Detecting....',crop_img)
if cv2.waitKey(1) & 0xFF == ord('q'):
key_detect = 1
cap.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
以上基于tenserflow,tenserflow适合在树莓派上跑,但是如果数据集过大就会崩溃(我们就是因为这个原因,止步于省赛)建议数据集采样图片时控制在2000张左右,不然会崩
三、上下位机通信方式:
1.高低电平通信:
最开始因为下位机仅仅需要接受树莓派识别结果,而结果种类只有四种,于是乎最开始想到的是:树莓派往gpio写高低电平,stm32浮空输入电平结果,通过排列组合进行通信。源码如下:
communicate.h:
#define Type_2 PEin(10)
#define Type_3 PEin(11)
#define Type_4 PEin(12)
#define Type_5 PEin(13)
#define Nothing 0
#define hazardous_waste 1
#define other_waste 2
#define Recyclable_waste 3
#define Kitchen_waste 4
void communicate_Init(void);
int adjust(void);
communicate.c:
void communicate_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_10 |GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_SetBits(GPIOE,GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13);
}
int adjust(void){
delay_ms(10);
if(Type_2==0&&Type_3==1&&Type_4==1&&Type_5==1)
return Recyclable_waste;
if(Type_2==1&&Type_3==0&&Type_4==1&&Type_5==1)
return hazardous_waste;
if(Type_2==1&&Type_3==1&&Type_4==0&&Type_5==1)
return other_waste;
if(Type_2==1&&Type_3==1&&Type_4==1&&Type_5==0)
return Kitchen_waste;
return 0;
}
1.2高低电平树莓派部分:
GPIO.setmode(GPIO.BCM)
GPIO.setup(2,GPIO.OUT)
GPIO.setup(3,GPIO.OUT)
GPIO.setup(4,GPIO.OUT)
GPIO.setup(17,GPIO.OUT)
if num == 0:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
print('')
elif num == 1:
GPIO.output(2,GPIO.LOW)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
print('可回收垃圾')
elif num == 2:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.LOW)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
print('有害垃圾')
elif num == 3:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.LOW)
GPIO.output(17,GPIO.HIGH)
print('其他垃圾')
elif num == 4:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.LOW)
print('厨余垃圾')
else:
GPIO.output(2,GPIO.HIGH)
GPIO.output(3,GPIO.HIGH)
GPIO.output(4,GPIO.HIGH)
GPIO.output(17,GPIO.HIGH)
2.stm32串口通信部分:
void usb_communicate(void){
u16 t;
u16 len;
if(USART_RX_STA&0x8000){
delay_ms(100);
delay_ms(100);
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;
printf("\r\n您发送的消息为:\r\n\r\n");
for(t=0;t<len;t++)
{
USART_SendData(USART1, USART_RX_BUF[t]);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);
}
}
}
}
int USB_adjust(void){
usb_communicate();
if(USART_RX_BUF[0]=='0'){
USART_RX_STA=0;
return Nothing ;
}
if(USART_RX_BUF[0]=='1'){
USART_RX_STA=0;
return Recyclable_waste ;
}
if(USART_RX_BUF[0]=='2'){
USART_RX_STA=0;
return hazardous_waste ;
}
if(USART_RX_BUF[0]=='3'){
USART_RX_STA=0;
return other_waste;}
if(USART_RX_BUF[0]=='4'){
USART_RX_STA=0;
return Kitchen_waste;}
USART_RX_STA=0;
return EOF;
}
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
四、下位机电机驱动部分
1.电机:涡轮蜗杆电机(履带负载较大,不可直接用步进直流电机)
驱动:l298N(12V)
#ifndef __MOTOR_H
#define __MOTOR_H
#include "sys.h"
#define IN_1 PFout(1)
#define IN_2 PFout(2)
#define IN_3 PFout(3)
#define IN_4 PFout(4)
#define ZHENGXIANG 0
#define FANXIANG 1
#define STOP 2
void motor_Init(void);
void zongxiang_run(u16 model);
void hengxiang_run(u16 model);
void _delay_s(u16 s);
void stop(void);
void Recyclable_waste_work(void);
void hazardous_waste_work(void);
void other_waste_waste_work(void);
void Kitchen_waste_waste_work(void);
#endif
#include "motor.h"
#include "delay.h"
#define ZHENGXIANG 0
#define FANXIANG 1
#define STOP 2
void motor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOF, &GPIO_InitStructure);
}
void hengxiang_run(u16 model){
switch(model){
case ZHENGXIANG : {
IN_3=0;
IN_4=1;
break;
}
case FANXIANG :{
IN_3=1;
IN_4=0;
break;
}
case STOP:{
IN_3=1;
IN_4=1;
break;
}
}
}
void zongxiang_run(u16 model){
switch(model){
case ZHENGXIANG : {
IN_1=0;
IN_2=1;
break;
}
case FANXIANG :{
IN_1=1;
IN_2=0;
break;
}
case STOP:{
IN_1=1;
IN_2=1;
break;
}
}
}
void _delay_s(u16 s){
int i;
for(i=0;is;i++)
delay_ms(1000);
}
void Recyclable_waste_work(void){
hengxiang_run(FANXIANG);
zongxiang_run(FANXIANG);
_delay_s(5);
zongxiang_run(STOP);
hengxiang_run(STOP);
_delay_s(1);
}
void hazardous_waste_work(void){
zongxiang_run(ZHENGXIANG);
hengxiang_run(FANXIANG);
_delay_s(5);
zongxiang_run(STOP);
hengxiang_run(STOP);
_delay_s(1);
}
void other_waste_waste_work(void){
hengxiang_run(ZHENGXIANG);
zongxiang_run(ZHENGXIANG);
_delay_s(5);
zongxiang_run(STOP);
hengxiang_run(STOP);
_delay_s(1);
}
void Kitchen_waste_waste_work(void){
hengxiang_run(ZHENGXIANG);
zongxiang_run(FANXIANG);
_delay_s(5);
zongxiang_run(STOP);
hengxiang_run(STOP);
_delay_s(1);
}
void stop(void){
zongxiang_run(STOP);
hengxiang_run(STOP);
}
2.有关于延时的改进:
本人在调试时发现keil环境中:delay_ms(1000)和delay_ms(3000)差别不大
竟然有一下发现:
void _delay_s(u16 s){
int i;
for(i=0;is;i++)
delay_ms(1000);
}
3.stm32主函数:
int main(void)
{
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
LED_Init();
LCD_Init();
Adc_Init();
POINT_COLOR=RED;
LCD_ShowString(60,50,200,16,16,"Elite STM32");
LCD_ShowString(60,70,200,16,16,"ADC TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2015/1/14");
POINT_COLOR=BLUE;
LCD_ShowString(60,130,200,16,16,"ADC_CH0_VAL:");
LCD_ShowString(60,150,200,16,16,"ADC_CH0_VOL:0.000V");
motor_Init();
while(1)
{
if(adjust()){
delay_ms(200);
delay_ms(200);
switch(adjust()){
case hazardous_waste:{
hazardous_waste_work();
_delay_s(5);
break;}
case other_waste :{
other_waste_waste_work();
_delay_s(5);
break;}
case Recyclable_waste :{
Recyclable_waste_work();
_delay_s(5);
break;}
case Kitchen_waste :{
Kitchen_waste_waste_work();
_delay_s(5);
break;}
default :{
stop();
break;}
}
}
}
}
五、炸电机:
在调试过程中,我们炸了六个电机驱动,弄坏了一个步进电机
原因:一夜炸了六个电机驱动,我们依次排查电路接线、程序,最后竟然是电池的原
因!!!
机械由秦俊酉大力支持
Original: https://blog.csdn.net/qq_45290757/article/details/115670890
Author: 十六师兄带你玩转嵌入式
Title: 第七届工程训练大赛垃圾分类
相关阅读
Title: Pointnet以及Pointnet++论文笔记
Tips:写这篇Blog主要目的是在写的过程中加深对网络的理解,可能很多地方个人理解有偏差或者表述不明白。然后建议大家看了原论文再来看这个blog,这个Blog不是论文的翻译。有问题请留言或者qq交流:1981425845。大家一起讨论,共同进步。有做3D视觉的可以一起来讨论呀。
目录
- 论文及代码链接
- Pointnet部分
* - 论文研究背景
- Pointnet的主要工作
- 网络结构分析
- Pointnet网络总结
- Pointnet++部分
* - 论文研究背景
- Pointnet++的主要工作
- 网络结构分析
- Pointnet++网络总结
论文及代码链接
- Poinenet
- 论文链接:论文链接
- 代码链接
官方代码(TF1.0.1)
大佬复现版本(Pytorch1.0) - Pointnet++
- 论文链接:论文链接
- 代码链接:
官方代码(TF1.2)
大佬复现版本(Pytorch1.3,里头也有Poinenet的实现)
Pointnet部分
论文研究背景
- 点云是3D物体的最自然的表现形式,具有最全面的物体的几何特征,然而点云具有无序性(irregular)。如果我们设可以直接处理点云的网络为f f f,那么,f f f应该满足式子(1),也就是我们无论以什么顺序输入点云,输出应该是一样的。这就导致了以往的用于2D图片的网络结构无法直接用于处理点云数据,因为现有的2D处理网络要求输入数据是有序的,我们可以试想一下,如果我们把一个图片中的像素数据随机打乱之后再扔到网络中去处理,得到的结果肯定是不同的
f ( x 1 , x 2 , x 3 , . . . ) = f ( x n , x n − 1 , x n − 2 , . . . ) = . . . ( 1 ) f(x_{1},x_{2},x_{3},...)=f(x_{n},x_{n-1},x_{n-2},...)=... (1)f (x 1 ,x 2 ,x 3 ,...)=f (x n ,x n −1 ,x n −2 ,...)=...(1 ) - 现有的3D物体的检测与分割方法存在各种各样的问题,主要表现有: voxel-based方法 采用体素化的方法,将3D空间化为一个一个的网格,根据该网格中是否具有点云数据来决定网格的值,我们可以把体素理解成3D像素。voxel-based方法的思想是通过牺牲分辨率来换取数据的有序性,从而采用3DCNN来进行数据处理。然而,由于点云数据往往具有稀疏性,这会导致如果体素化的分辨率过低,也就是每个体素网格比较大,会造成比较大的信息损失,而当体素化的分辨率过高,又会造成较大的内存开销与计算量增加。(内存开销与计算量增加与分辨率成立方关系)。 multiview方法 :将3D点云从不同方向投影为2D图片,采用2D中的CNN进行处理,这已经破坏了3D数据的几何机构,对于分类任务或许可以,但是对于分割任务无能为力,这部分所需要的特征已经在投影过程中丢失。
Pointnet的主要工作
Pointnet的目标是设计出一个网络,该网络可以不考虑输入的顺序,也就是不同的输入顺序情况下,网络的输出是一样的,这样就可以直接对点云数据进行处理了。
- 处理点云无序性的问题 ——采用对称函数(Symmetry Function)
对称函数是什么,其实我们很早就接触了,比如,加法这个函数就是对称函数,x1+x2=x2+x1,文章中,作者采用了max-pooling作为对称函数来聚合点云的特征,当然,我们也可以使用average-pooling等,只不过作者发现取max效果更好。同时,作者在文章中通过数学推导证明了Pointnet理论上可以逼近任何一个对称函数。(个人感觉没有必要手推一遍) - 针对于刚体的不变性
点云是定义在欧式空间中的集合,其具有旋转不变性与平移不变性,平移不变性好解决,直接采用局部坐标就可以处理(注:文章中没有考虑平移不变性,应该是数据集中的点云数据空间位置都差不多,不需要考虑这个)。对于旋转不变性,本文采用了T-net,其直接与嵌入整个网络中,且使用最终的(分类或者分割)Loss更新参数,所以可以这么理解,T-net的作用就是让点云在进行处理之前,旋转到一个比较好的位置,这个位置有什么特点呢,就是相同的点云数据与网络参数,这个位置的Loss比其他位置的Loss低。后面的Feature对齐网络也可以这么理解,但是由于feature的维度太高,加入feature网络会引入很多参数,所以需要加入正则避免过拟合。(个人感觉T-net的作用不明显,在训练中加与不加差距不大,感觉就是加了几个全连接层)
网络结构分析
我们先讲分类部分,上图中,蓝色部分为分类网络。先不考虑T-net,不考虑Batch_size,我们设输入点云数据C C C的维度为的(n,3),首先,点云经过input-transforme层,里头主要是几个一维卷积层,一维卷积对点云进行point-wise操作,对点云的point-wise特征进行提取,依据x,y,z(不考虑法线的情况下)特征,生成新的特征表述。在本文中,input-transforme将(n,3)的点云数据变成了(n,64)的特征表述。在mlp层,通过全连接层对提取出来的特征进行进一步的处理,输出为(n,1024)。最后,在特征维度进行max-pooling,注意是在特征维度,也就是对特征的每一个channel取最大值,最后生成一个点云的全局特征描述,然后这个问题就转变成了一个多分类问题了。
分割问题可以理解成每个点的分类问题,我们通过input-transforme已经得到了点的特征了,但是这个特征没有全局信息,Pointnet的方法是直接将分类网络中的全局特征直接复制到每个点上,得到(n,64+1024)的特征描述,最后在进行点的分类操作,也就是点云的分割操作。
; Pointnet网络总结
- 采用了对称函数来解决点云的无序性,所以可以直接将原始点云作为输入
- 相对于voxel-based方法,其所占用显存空间较小
- 网络中仅仅提取了点的特征与全局特征,没有利用局部特征,而局部特征对于点云处理而言,是十分重要的。 Pointnet++主要就是针对这个进行改进。
Pointnet++部分
论文研究背景
- 如上所述,Pointnet无法获取点云的局部特征,只能得到每个点的特征与全局特征,而从我们的直觉来看,局部特征是点云数据中的一个十分重要的特征。
- 真实采样的点云,其采样密度往往有区别,甚至同一个点云的不同部分,采样密度也是不一样的。要保证算法的有效性,就需要保证算法在不同采样密度下的鲁棒性。
Pointnet++的主要工作
- Poinetnet无法提取局部特征——把点云分割成多个区域,递归地使用Pointnet来聚合局部特征
- 通过Dropout与MSG(或者MRG,代码中使用的是MSG,其实MRG操作起来更简单)来提高算法对点云采样密度的鲁棒性。
- 通过基于距离的上采样与跨层连接来实现点云的点特征的生成,这个点特征包含了局部特征与全局特征,具有的信息量比Poinetnet生成的点特征多。
下面,具体介绍一下这三个部分:
聚合局部特征: 这一部分的主要思想就是把点云分成很多个小部分,每个小部分采用Pointnet提取特征,这其实就是把这个区域的局部特征提取出来赋予到区域中心点上(注意不是几何中心,后头会说明)。可以看作对点云进行了"降采样",点的数量变少了。然后我们重复如上步骤,就可以一步一步提取更大范围的局部特征。(是不是感觉和2DCNN有点类似,通过递归一步一步扩大了"降采样"后点云中的每个点的感受野),我们可以看一下下图以便更好理解。
Poinetnet网络前面我们已经介绍过了,那么这一部分我们需要解决的问题就是怎么Sampling与Grouping,也就是怎么确定每个区域的中心点与每个区域的大小。Sampling我们采用(最远点采样)FPS来解决,FPS就是每次选取与集合中剩下所有点距离最远的点作为采样点。使用FPS的好处是可以使采样点均匀地遍布于整个点云。对于Grouping,我们可以使用K近邻搜索(KNN)与半径邻域搜索,代码中采用的是半径邻域搜索,文章说这个方法效果更好。
解决点云采样密度不均匀
文章提供了两个相互独立地途径去解决这个问题:
1、在训练的时候,对点云进行降采样,模拟采样密度不均匀的情况,这个比较好理解。
2、进行多尺度的局部特征提取,具体如下图所示:
上图分别展示了MSG与MRG(注意我红色框出来的部分,不同框颜色是不一样的,表示了不同尺度的特征,我刚开始看没注意到这个给我看懵了),MRG是图b,很好理解,前面也说了,通过递归,剩余点云中的点的感受野是不断增大的,所以需要得到 当前递归层点的多尺度的特征,我们只需要将当前递归层点的特征与上一层递归层的对应的点们的特征进行concat(连接)就行了,都不需要在进行多余的计算了。MSG的话就是改变前面Grouping的大小,比如改变搜索的半径,然后将不同的半径进行concat就行,会增加一点额外的计算量。代码中使用的是MSG。
通过上采样的特征传递
为什么要进行上采样,因为我们要进行点云分割,我们需要知道每个点的特征。我们当然不能用最初始的点特征来做分类。一个比较好的思路从最后一个Pointnet递归层开始,通过将特征传递到周围点,然后与上一层的特征相加。也就是说除了最后一层外,每一层的点的特征有两个部分组成:1、递归使用Pointnet生成的点特征。2、下一层递归层通过上采样得到的特征。第一个我们已经知道了,最主要的是第二个怎么计算。
本文中上采样的话是通过基于距离的K近邻特征传递来实现的(原文是inverse distance weighted average based on k nearest neighbors,我是按我的理解意译的),具体公式如下:
需要说明一下这个K近邻具体是谁的近邻。如果我们需要从第K个Pointnet递归层上采样生成第K-1层的点特征,那么我们寻找的是第K-1层点的近邻,而这个点的近邻点应该是在K层中的点云中寻找。(注意,无论怎么递归使用Pointnet递归,我们一直保留着点的坐标信息,而且都是在同一个坐标系下的(和下采样差不多)。给一张图应该会好理解一点,图中K设置为3。)
; 网络结构分析
总体来说就是先递归地通过Pointnet提取局部特征。最后分类的话就是再通过一个Pointnet提取全局特征然后做分类,分割的话就是通过逐层上采样得到信息量最大的点特征然后做分割,具体的话大家可以自己琢磨一下,有利于整体理解整个网络。
Pointnet++网络总结
- 通过递归的使用Pointnet,把整体点云分成很多个小点云,提取局部空间特征
- 通过Dropout与多尺度特征聚合来解决采样率不均匀的问题
- 个人的一些思考:Pointnet虽然相比于voxel-based方法所占用内存更少,但是Pointnet在数据处理上有点浪费时间,包括FPS,近邻搜索等,每进行一次Pointnet计算就需要计算一次近邻搜索,不想voxel-based方法是规则存储的,进行卷积的时候不需要再去通过计算来寻找近邻。所以后面有一篇论文结合了Point-based与voxel-based的特点,叫PVConv,大家可以去看看。Pointnet++通过FPS方法进行Sampling,这容易受野点的影响。总而言之,Pointnet系列作为直接对点云进行处理的开山之作,其重要性还是不言而喻的。Pointnet系列也成了很多其他算法用来提取特征的常用的Backbone。
Original: https://blog.csdn.net/weixin_41271939/article/details/123985236
Author: 慢下去、静下来
Title: Pointnet以及Pointnet++论文笔记