openbot使用手机做大脑,这使得我们可以做很多的AI算法来实现诸如循迹,避障,跟随,寻光,人体感应等传感器,使得整个项目的设计更简单和更经济,减少的很多的传感器都会降低硬件DIY的难度和花费。缺点是需要学习一下caffee或tensorFolw Lite,根据我的感觉,也可以使用这些芯片减少来开发的难度,国内网店上这些芯片价格并不高,几块钱就能搞定,如果没有手机,使用传感器是个很好的选择。连无线充电模块都能买到,那些不使用手机的机器人应该是使用了这些芯片。
openbot的android代码位于根目录的android目录下,包含两个模块,一个控制,一个机器人app。
IOS目录是IOS 的app
doc目录是说明文档。
opencode很有意思,是一个在线开发固件的平台,通过拖放就可以,这可以节省很多写代码的时间。
policy是一个驾驶策略训练的平台,需要有比较强劲的电脑和tensorflow知识,主要是采集吧
python手机控制训练。
不要害怕动手。自己买很便宜,购买时自己可以购买一些杜邦线和arduino的扩展板,我也是后来才知道,arduino uno有Arduino sensor shield V5.0的扩展板,arduino有cnc shield v4的扩展板,结合杜邦线,都是几块钱,就能让接入更加轻松,这样调试会更方便,而且省掉很多焊接。
android代码需要的android studio版本比较新,老版需要自己适配gradle,安装好以后就可以插上arduino测试。
然后就可以自己自己开发加一些表情集,为了提高复用性,我倾向于不使用控件而是基本的绘图api来实现,这可以使之十分方便的移植到其他语言和平台。
一个简单的自定义VIEW:
public class FaceView extends View{
private static final String TAG = "FaceView";
private Paint mPaint;
private RectF oval;
//是否圆眼
private boolean m_bCircle=true ;
//是否显示嘴
private boolean isShowMouth=false;
//表情索引
private int mFace=0;
//view Width
private int m_VW = 0;
//view height
private int m_VH = 0;
//X轴中心
private float mRadiusX = 0;
//Y轴中心
private float mRadiusY = 0;
//圆角半径
private float m_rx = 0;
private float m_ry = 0;
//左眼矩形位置
private float m_left_Leye = 0;
private float m_left_Teye = 0;
private float m_left_Reye = 0;
private float m_left_Beye = 0;
//左眼中心
private float m_left_Xeye = 0;
private float m_left_Yeye = 0;
//圆眼的半径
private float m_eye_R = 0;
//右眼位置
private float m_right_Leye = 0;
private float m_right_Teye = 0;
private float m_right_Reye = 0;
private float m_right_Beye = 0;
//右眼中心
private float m_right_Xeye = 0;
private float m_right_Yeye = 0;
private RectF m_l_oval;
private RectF m_r_oval;
//表情添加过渡动画
private ValueAnimator va;
private float mCurrentSingle = 0f;
private boolean isRun = false;
view初始化,为了调试方便,我在view加入了一个单击切换表情的操作。
private void initView() {
mPaint= new Paint();
mPaint.setAntiAlias(true);
oval = new RectF();
mPaint.setColor(Color.YELLOW);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mFace++;
if (mFace > 20){
mFace = 0;
}
isRun = false;
invalidate();
}
});
m_l_oval = new RectF();
m_r_oval = new RectF();
initAni();
}
private void initAni() {
Log.i("jiaXXX", "enter drawAni");
if (va == null) {
Log.i("jiaXXX", "enter drawAni========");
va = ObjectAnimator.ofFloat(0f, 1);
va.setDuration(300);
//va.setRepeatCount(ValueAnimator.INFINITE);
//va.setRepeatCount(0);
va.setInterpolator(new AccelerateInterpolator());
//va.start();
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentSingle = (float) animation.getAnimatedValue();
invalidate();
Log.i("jiaXXX", "enter drawAni========, onAnimationUpdate");
}
});
va.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
Log.i("jiaXXX", "enter drawAni========, onAnimationEnd");
if (va != null) {
Log.i("jiaXXX", "enter drawAni========, onAnimationEnd----");
//va.end();
va.cancel();
//va = null;
//isRun = false;
}
}
@Override
public void onAnimationCancel(Animator animation) {
Log.i("jiaXXX", "enter drawAni======== onAnimationCancel");
if (va != null) {
Log.i("jiaXXX", "enter drawAni======== onAnimationCancel----");
//va = null;
//isRun = false;
}
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
}
显示表情:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
m_VW = getWidth();
m_VH = getHeight();
Log.i("jia", "w="+m_VW);
Log.i("jia", "h="+m_VH);
//view 中心
mRadiusX = m_VW / 2;
mRadiusY = m_VH / 2;
//眼睛半径
if ( m_VW > m_VH){
m_eye_R = m_VH / 4;
}else {
m_eye_R = m_VW / 5;
}
//左眼中心
if ( m_VW > m_VH){
m_left_Xeye = mRadiusX-m_eye_R-(m_eye_R/3);
}else {
m_left_Xeye = m_VW / 4;
}
//右眼中心
m_left_Yeye = m_VH / 2;
if ( m_VW > m_VH) {
m_right_Xeye = mRadiusX+m_eye_R+(m_eye_R/3);
}else {
m_right_Xeye = m_VW * 3 / 4;
}
m_right_Yeye = m_VH / 2;
//默认方眼的四个顶点位置,为了好看,方眼y方向做一放缩
m_left_Leye = m_left_Xeye - m_eye_R;
m_left_Teye = m_left_Yeye - m_eye_R*1.2f;
m_left_Reye = m_left_Xeye + m_eye_R;
m_left_Beye = m_left_Yeye + m_eye_R*1.2f;
//当眼睛为方的时,圆角半径
m_rx = m_eye_R/10;
m_ry = m_eye_R/10;
m_right_Leye = m_right_Xeye - m_eye_R;
m_right_Teye = m_right_Yeye - m_eye_R*1.2f;
m_right_Reye = m_right_Xeye + m_eye_R;
m_right_Beye = m_right_Yeye + m_eye_R*1.2f;
//m_left_Xeye = m_left_Xeye+(float) Math.sin(mCurrentSingle * 50) * 8;
//m_left_Yeye = m_left_Xeye+(float) Math.sin(mCurrentSingle * 50) * 8;
if (mFace == 0) {
drawFace(canvas);
}else if (mFace == 1) {
drawFace1(canvas);
}else if (mFace == 2) {
drawFace2(canvas);
}else if (mFace == 3) {
drawFace3(canvas);
}else if (mFace == 4) {
drawFace4(canvas);
}else if (mFace == 5) {
drawFace5(canvas);
}else if (mFace == 6) {
drawFace6(canvas);
}else if (mFace == 7) {
drawFace7(canvas);
}else if (mFace == 8) {
drawFace8(canvas);
}else if (mFace == 9) {
drawFace9(canvas);
}else if (mFace == 10) {
drawFace10(canvas);
}else if (mFace == 11) {
drawFace11(canvas);
}else if (mFace == 12) {
drawFace12(canvas);
}else if (mFace == 13) {
drawFace13(canvas);
}else if (mFace == 14) {
drawFace14(canvas);
}else if (mFace == 15) {
drawFace15(canvas);
}else if (mFace == 16) {
drawFace16(canvas);
}else{
drawFace(canvas);
}
//invalidate();
//drawAni(canvas);
}
初始化的眼睛从小到大一个睁眼的动作
private void drawFace(Canvas canvas){
mPaint.setColor(Color.WHITE);
if (m_bCircle){
//canvas.drawCircle(m_left_Xeye, m_left_Yeye, m_eye_R, mPaint);
//canvas.drawCircle(m_right_Xeye, m_right_Yeye, m_eye_R, mPaint);
m_l_oval.set(m_left_Leye, m_left_Yeye-m_eye_R*mCurrentSingle, m_left_Reye, m_left_Yeye+m_eye_R*mCurrentSingle);
//绘制圆弧 圆弧所在的椭圆对象 圆弧的起始角度 圆弧的角度 是否显示半径连线
canvas.drawArc(m_l_oval, 0, 360, true, mPaint);
m_r_oval.set(m_right_Leye, m_right_Yeye-m_eye_R*mCurrentSingle, m_right_Reye, m_right_Yeye+m_eye_R*mCurrentSingle);
//绘制圆弧 圆弧所在的椭圆对象 圆弧的起始角度 圆弧的角度 是否显示半径连线
canvas.drawArc(m_r_oval, 0, 360, true, mPaint);
if (!isRun) {
Log.i("jiaXXX", "drawAni");
isRun = true;
//drawAni(canvas);
va.start();
}
}
else
{
canvas.drawRoundRect(m_left_Leye, m_left_Teye, m_left_Reye, m_left_Beye, m_rx, m_ry, mPaint);
canvas.drawRoundRect(m_right_Leye, m_right_Teye, m_right_Reye, m_right_Beye, m_rx, m_ry, mPaint);
}
}
//闭眼,叹气
private void drawFace1(Canvas canvas){
mPaint.setColor(Color.WHITE);
if (m_bCircle){
m_l_oval.set(m_left_Leye, m_left_Teye+m_eye_R*mCurrentSingle, m_left_Reye, m_left_Beye-m_eye_R*mCurrentSingle);
//绘制圆弧 圆弧所在的椭圆对象 圆弧的起始角度 圆弧的角度 是否显示半径连线
canvas.drawArc(m_l_oval, 0, 360, false, mPaint);
m_r_oval.set(m_right_Leye, m_right_Teye+m_eye_R*mCurrentSingle, m_right_Reye, m_right_Beye-m_eye_R*mCurrentSingle);
//绘制圆弧 圆弧所在的椭圆对象 圆弧的起始角度 圆弧的角度 是否显示半径连线
canvas.drawArc(m_r_oval, 0, 360, false, mPaint);
if (!isRun) {
Log.i("jiaXXX", "drawAni");
isRun = true;
//drawAni(canvas);
va.start();
}
}
else
{
canvas.drawRoundRect(m_left_Leye, m_left_Teye+m_eye_R, m_left_Reye, m_left_Beye-m_eye_R, m_rx, m_ry, mPaint);
canvas.drawRoundRect(m_right_Leye, m_right_Teye+m_eye_R, m_right_Reye, m_right_Beye-m_eye_R, m_rx, m_ry, mPaint);
}
}
//下半睁
private void drawFace2(Canvas canvas){
mPaint.setColor(Color.WHITE);
if (m_bCircle){
m_l_oval.set(m_left_Leye, m_left_Yeye-m_eye_R, m_left_Reye, m_left_Yeye+m_eye_R);
//绘制圆弧 圆弧所在的椭圆对象 圆弧的起始角度 圆弧的角度 是否显示半径连线
canvas.drawArc(m_l_oval, 0, 180, true, mPaint);
RectF r1= new RectF();
r1.set(m_left_Leye, m_left_Yeye-(m_eye_R/5)-(m_eye_R*4/5)*(1-mCurrentSingle), m_left_Reye, m_left_Yeye+(m_eye_R/5)+(m_eye_R*4/5)*(1-mCurrentSingle));
//绘制圆弧 圆弧所在的椭圆对象 圆弧的起始角度 圆弧的角度 是否显示半径连线
Paint p = new Paint();
p.setColor(Color.BLACK);
canvas.drawArc(r1, 0, -180, true, mPaint);
m_r_oval.set(m_right_Leye, m_right_Yeye-m_eye_R, m_right_Reye, m_right_Yeye+m_eye_R);
//绘制圆弧 圆弧所在的椭圆对象 圆弧的起始角度 圆弧的角度 是否显示半径连线
canvas.drawArc(m_r_oval, 0, 180, true, mPaint);
RectF r2= new RectF();
r2.set(m_right_Leye, m_right_Yeye-(m_eye_R/5)-(m_eye_R*4/5)*(1-mCurrentSingle), m_right_Reye, m_right_Yeye+(m_eye_R/5)+(m_eye_R*4/5)*(1-mCurrentSingle));
//绘制圆弧 圆弧所在的椭圆对象 圆弧的起始角度 圆弧的角度 是否显示半径连线
canvas.drawArc(r2, 0, -180, true, mPaint);
if (!isRun) {
Log.i("jiaXXX", "drawAni");
isRun = true;
va.start();
}
}
else
{
canvas.drawRoundRect(m_left_Leye, m_left_Teye+m_eye_R, m_left_Reye, m_left_Beye, m_rx, m_ry, mPaint);
canvas.drawRoundRect(m_right_Leye, m_right_Teye+m_eye_R, m_right_Reye, m_right_Beye, m_rx, m_ry, mPaint);
}
}
//上半睁
private void drawFace3(Canvas canvas){
mPaint.setColor(Color.WHITE);
if (m_bCircle){
canvas.drawCircle(m_left_Xeye, m_left_Yeye, m_eye_R, mPaint);
canvas.drawCircle(m_right_Xeye, m_right_Yeye, m_eye_R, mPaint);
Paint p = new Paint();
p.setColor(Color.BLACK);
canvas.drawRect(m_left_Leye, mRadiusY+m_eye_R/3+(m_eye_R*2/3)*(1-mCurrentSingle), m_right_Reye, m_right_Beye, p);
if (!isRun) {
Log.i("jiaXXX", "drawAni");
isRun = true;
va.start();
}
}
else
{
canvas.drawRoundRect(m_left_Leye, m_left_Teye, m_left_Reye, m_left_Beye-m_eye_R, m_rx, m_ry, mPaint);
canvas.drawRoundRect(m_right_Leye, m_right_Teye, m_right_Reye, m_right_Beye-m_eye_R, m_rx, m_ry, mPaint);
}
}