ペットボトルいかだづくり 第四回

昨日は竹を使っていかだを作り浮かべてみました。

竹を使って作ったいかだは前のいかだより、頑丈で強いいかだができました。

f:id:katatsumuri527:20200530111411j:plain

午後にはこのいかだを使って川に浮いてみました。

前のいかだより安定感が出ました。

 

ペットボトルいかだづくり 第五回 - カタツムリのブログ

ペットボトルいかだづくり 第三回

昨日、川に行って試験走行をしてきました。

目標にしていた400mには全然達しなかったのですが一応、メンバー全員が乗ることができました。

乗ることができたといっても乗っている部分は水に沈んでいます。

最後にはペットボトルをつないでいた支柱が折れて、壊れました。

次回に向けて竹を用意してくれるということなので、竹を使って頑丈ないかだを作ることにします。

 

ペットボトルいかだづくり 第四回 - カタツムリのブログ

ペットボトルいかだづくり 第ニ回

ペットボトルいかだに乗る友達と、ペットボトルいかだを作りました。

完成品を紹介します。

f:id:katatsumuri527:20200523111414j:plain

f:id:katatsumuri527:20200523111445j:plain

ペットボトル同士を麻ひもで結び、土台とペットボトルを結びつけました。

日曜日に400mの試験走行を行う予定です。

 

ペットボトルいかだづくり 第三回 - カタツムリのブログ

ペットボトルいかだづくり 第一回

近所に流れている川でいかだを作って下流を目指してみることにしました。

今回作るいかだはペットボトルいかだにしました。

ペットボトルいかだはペットボトルをつなぎ合わせて作るいかだです。

家にアイロン台の中身だけがあったのでこれを土台にして作っていく予定です。

必要なペットボトルの数が、空気1Lに対して1kgの浮力があるとして僕の体重が45kgなので、2Lペットボトルだと少し余裕を持たせて30本必要ということになりました。

ペットボトルを集めるために近所でもし2Lペットボトルが余っている方がいれば協力してほしいです。

必要なペットボトルはキャップ付きで四角い形のものを30本です。

よろしくお願いします。

f:id:katatsumuri527:20200513114938j:plain

作成中のいかだ

5/23 追記

ペットボトルが30本集まりました。

ご協力ありがとうございました。

 

ペットボトルいかだづくり 第ニ回 - カタツムリのブログ

人物判定

pythonopencvを使って特定の人物がうつっているかを判定してみました。
今回は下のサイトのプログラムを改造して作りました。
symfoware.blog.fc2.com
リアルタイムで処理するプログラムです。
処理が重いのでカクカクしますがちゃんと認識します。

import numpy
import cv2
import glob
img_list = []
for i in glob.glob("./img/face/*.jpg"): #パスは顔写真の入っているファイルのパスにする
        

    test_file = i
    test_image = cv2.imread(test_file)
    img_list.append(test_image)
def calc(matcher_name, face_image):
    

    detector = cv2.ORB_create()
    keypoints1 = detector.detect(face_image)

    descripter = cv2.ORB_create()
    k1,d1 = descripter.compute(face_image, keypoints1)

    matcher = cv2.DescriptorMatcher_create(matcher_name)
    
    min_dist = 100000
    


    for i in range(len(img_list)):

        keypoints2 = detector.detect(img_list[i])

        k2,d2 = descripter.compute(img_list[i], keypoints2)

        try:
            matches = matcher.match(d1, d2)
        except:
            continue

        dist = [m.distance for m in matches]
        
        if len(dist) == 0:
            continue
        
        min_dist = min(min(dist), min_dist)
    
    return min_dist
    
cascade_path = 'cascade/haarcascade_frontalface_alt.xml' #opencvのカスケード分類器のパス
cam = cv2.VideoCapture(0)
while True:
    _, image = cam.read()

    image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    cascade = cv2.CascadeClassifier(cascade_path)

    facerect = cascade.detectMultiScale(
                    image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(1, 1))
    if len(facerect) <= 0:
        pass
    else:

        rect = facerect[0]
        for r in facerect:
            if rect[2] < r[2]:
                rect = r
            
        x = rect[0]
        y = rect[1]
        w = rect[2]
        h = rect[3]

        face_image = image_gray[y:y+h, x:x+w]


        resutl = calc('BruteForce-Hamming', face_image)
        cv2.imshow("detected_", face_image)

        key = cv2.waitKey(1) & 0xFF

        if key == ord('q'):
            break
        if resutl < 10: #ここの数字を変えることで閾値が変わる。
            print('認識しました')


cam.release()
cv2.destroyAllWindows()

このプログラムを動かすには初めに認識したい特定の人物のグレースケールの顔写真が必要なので写真を20枚ほど撮って
次のプログラムでグレースケールにし、顔の部分を切り取ります。

# -*- coding:utf-8 -*-
import numpy
import cv2
import glob
cascade_path = 'cascade/haarcascade_frontalface_alt.xml'
list_ = glob.glob("img/face/*.jpg")
print(list_)
for i in list_:
    image = cv2.imread(i)
    image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    cascade = cv2.CascadeClassifier(cascade_path)
    facerect = cascade.detectMultiScale(
                    image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(1, 1))
    
    print(i, len(facerect))
    
    if len(facerect) <= 0:
        continue
    
    rect = facerect[0]
    for r in facerect:
        if rect[2] < r[2]:
            rect = r
        
    
    x = rect[0]
    y = rect[1]
    w = rect[2]
    h = rect[3]
    
    # img[y: y + h, x: x + w] 
    cv2.imwrite(i, image_gray[y:y+h, x:x+w])

写真をグレースケールに変換し、顔部分だけを切り取ってファイルを置き換えています。

光のあたり加減や場所によって変わるので閾値を変えてください。
似たような顔のひとは識別できない可能性があります。

上のプログラムを応用して特定の猫を検出するプログラムを作りました。
作ったプログラムはこちらです。

import numpy
import cv2
import glob
img_list = []
for i in glob.glob("./img/neko/*.jpg"):
        

    test_file = i
    test_image = cv2.imread(test_file)
    img_list.append(test_image)
def calc(matcher_name, face_image):
    

    detector = cv2.ORB_create()
    keypoints1 = detector.detect(face_image)

    descripter = cv2.ORB_create()
    k1,d1 = descripter.compute(face_image, keypoints1)

    matcher = cv2.DescriptorMatcher_create(matcher_name)
    
    min_dist = 100000
    


    for i in range(len(img_list)):

        keypoints2 = detector.detect(img_list[i])
        k2,d2 = descripter.compute(img_list[i], keypoints2)
        try:
            matches = matcher.match(d1, d2)
        except:
            continue
        dist = [m.distance for m in matches]
        
        if len(dist) == 0:
            continue
        
        min_dist = min(min(dist), min_dist)
    
    return min_dist
    
cascade_path = 'cascade/neko.xml'

image = cv2.imread("img/neko.jpg") #認識させたい画像ファイルのパス
image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cascade = cv2.CascadeClassifier(cascade_path)
facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=1, minSize=(1, 1))
if len(facerect) <= 0:
    pass
else:
    rect = facerect[0]
    for r in facerect:
        if rect[2] < r[2]:
            rect = r
            
    x = rect[0]
    y = rect[1]
    w = rect[2]
    h = rect[3]
    # img[y: y + h, x: x + w] 
    face_image = image_gray[y:y+h, x:x+w]

    
    resutl = calc('BruteForce-Hamming', face_image)
    print(resutl)
    if resutl < 5:
        print('cat')
    cv2.imshow("cat", face_image)
    cv2.waitKey(1)

猫の顔を切り取るには上のプログラムのカスケード分類器の部分を置き換えてください。
使ったカスケード分類器は下のサイトで配布されていたものを使いました。
rest-term.com
同じ猫の画像が配布されているところがなかったので下のサイトの動画から写真を取り出して作りました。
movie-labo.info
猫の写真は10枚で作りました。
f:id:katatsumuri527:20200511094515p:plain
プログラムを動かすと意外ときちんと認識してくれます。
別の猫では認識しません。
背景を変えても認識します。
f:id:katatsumuri527:20200511094730j:plain
もちろんすごく似た猫は認識できない可能性はあります

アプリづくり

休みで暇なので勉強ができるアプリを作ってみました。

大体のpcの環境でも動くように今回はhtmlとcssjavascriptを使って作りました。

テストをしたのはChromeiphone上のSafari、Edgeです。

使い方

ダウンロードはこちらから。

drive.google.com


アクセスして右上のダウンロードボタンを押すとダウンロードできます。

ダウンロードをしたら解凍してください。

実行するにはpcが必要です。

windows pcの場合はhttps://www.vector.co.jp/soft/dl/winnt/net/se381431.htmlをダウンロードして実行します。

f:id:katatsumuri527:20200510120156p:plain

実行したら追加ボタンを押してダウンロードしたファイルを選択し、待ち受けを開始します ボタンをクリックします。

f:id:katatsumuri527:20200510120703p:plain

クリックしたら 左上にあるポート番号を確認して(デフォルトは80)

実行したpc上ではhttp://localhost:80にアクセスします。

別の端末上で見るときは http://実行したpcのIPアドレス:80 にアクセスします。

後ろの :80 というのがポート番号です。

macは持ってないのでやってないですかターミナルで下のように実行してください。

cd 解凍したファイルのある場所
python -m SimpleHTTPServer 80

macの場合2行目の一番最後の数字の80がポート番号です。

後はhttp://localhost:80やhttp://実行したpcのipアドレス:80にアクセスしてください。

アクセスすると青い画面にファイル名が表示されるのでクリックしてください。

f:id:katatsumuri527:20200510120728p:plain

すると下のような画面にたどり着きます。

初期状態で表示されている問題は小学5年生の理科です。

f:id:katatsumuri527:20200510120747p:plain

スマホでアクセスすると下のようになります。

f:id:katatsumuri527:20200510121210p:plain

使い方は簡単でときたい問題をクリックし、答えを選択するか入力して答え合わせをクリックします。ポップアップが出て正解か不正解かを知らせてくれます。

トップ画面の右上には正解率が表示されます。

リセットするには履歴を削除すればリセットされます。

pcで見ると右上に問題を編集というボタンがあります。(スマホにはありません)

アクセスすると問題を追加、削除できます。

タイトル、問題文、回答方法を決め、入力すると 回答を入力できるようになります。

選択式にした場合は選択肢の区切りに"、"を使います。

入力するとリアルタイムで選択肢が追加されるので答えとなるものをクリックします。

次のページを作るや、問題を追加して戻るをクリックすると問題が追加されます。

問題を削除するには削除したい問題をクリックし削除をクリックします。

変更がすべて済んだら編集したファイルをダウンロードをクリックし、ダウンロードされたファイルを解凍したzipファイルの中のmondai.csvと置き換えます。

ページを再読み込みすれば表示されます。表示されない場合は Ctrl + F5 キーを押します。

 

 

コップ運びロボット

今回は前から作っていたドリンクサーバー(夏休みの自由研究)のコップ運びロボットを紹介します。
まず初めにロボットの外観を紹介します。
作ったロボットはこちらです。
f:id:katatsumuri527:20200429145423j:plain
本当はraspberrypiを使って作りたいのですがraspberrypiで作ると動かなくなったときにハードウェアに問題があるかソフトウェアに問題があるかがわかりにくいのでまず LEGOのEV3で作ることにしました。
EV3にはPythonでプログラミングができるようev3devというosをインストールします。
ev3devのインストールにはmicrosdカードが必要です。
ev3devの公式サイト(https://www.ev3dev.org/)からosをタウンロードしてEtcher(https://www.balena.io/etcher/)などを使いsdカードに書き込みます。
仕組みはテーブルの上につけたカメラの映像をパソコンで画像処理してロボットに無線で送りロボットが指示どおりに動きます。
カメラにはiphone 5sを使いました。
iphonewebカメラにできるivcamというアプリを使ってiphoneの映像を取得しています。
f:id:katatsumuri527:20200429145052j:plain
プログラミングにはPythonを使い、画像処理にはopencvを使いました。
今回は処理を軽くするために必ずコップをピンクの折り紙の上に置く必要があります。
ロボットとパソコンの通信にはsocket通信を使いました。
ロボットには超音波センサー、ジャイロセンサーがついています。
超音波センサーは超音波を使い距離を測定するセンサー、 ジャイロセンサーは回転を検知するセンサーです

f:id:katatsumuri527:20200429145738j:plain
ロボットの中央で赤く光っているのが超音波センサー
f:id:katatsumuri527:20200429145713j:plain
本体の左側についているのがジャイロセンサー
ロボットと画像処理のプログラムを載せておきます。

画像処理のプログラム

import cv2
import numpy as np
import time
import socket
import math

LOW_COLOR_BLUE = np.array([100, 75, 75])
HIGH_COLOR_BLUE = np.array([140, 255, 255])
LOW_COLOR_GREEN = np.array([30, 64, 0])
HIGH_COLOR_GREEN = np.array([90,255,255])
LOW_COLOR_PINK = np.array([150, 100, 100])
HIGH_COLOR_PINK = np.array([188,255,255])

AREA_RATIO_THRESHOLD = 0.005

def find_specific_color(frame,AREA_RATIO_THRESHOLD,LOW_COLOR,HIGH_COLOR):
    """
    指定した範囲の色の物体の座標を取得する関数
    frame: 画像
    AREA_RATIO_THRESHOLD: area_ratio未満の塊は無視する
    LOW_COLOR: 抽出する色の下限(h,s,v)
    HIGH_COLOR: 抽出する色の上限(h,s,v)
    """
    # 高さ,幅,チャンネル数
    h,w,c = frame.shape

    # hsv色空間に変換
    hsv = cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)

    
    # 色を抽出する
    ex_img = cv2.inRange(hsv,LOW_COLOR,HIGH_COLOR)

    # 輪郭抽出
    contours,hierarchy = cv2.findContours(ex_img,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    
    # 面積を計算
    areas = np.array(list(map(cv2.contourArea,contours)))

    if len(areas) == 0 or np.max(areas) / (h*w) < AREA_RATIO_THRESHOLD:
        # 見つからなかったらNoneを返す
        
        return None
    else:
        # 面積が最大の塊の重心を計算し返す
        max_idx = np.argmax(areas)
        max_area = areas[max_idx]
        result = cv2.moments(contours[max_idx])
        x = int(result["m10"]/result["m00"])
        y = int(result["m01"]/result["m00"])
        return (x,y)
#二つのベクトルの角度を出す
def calcAngle(V1,V2):
    V1size = np.sqrt(V1[0] ** 2 + V1[1] ** 2)
    V2size = np.sqrt(V2[0] ** 2 + V2[1] ** 2)
    if V1size == 0 or V2size == 0:
        #0ベクトルエラー
        return None
    #内積を求める
    inner = (V1[0] * V2[0]) + (V1[1] * V2[1])

    cos = inner / (V1size * V2size)

    rad = math.acos(cos)
    return rad

def calc_sin(V1,V2):
    V1size = np.sqrt(V1[0] ** 2 + V1[1] ** 2)
    V2size = np.sqrt(V2[0] ** 2 + V2[1] ** 2)
    
    if V1size == 0 or V2size == 0:

        return None

    inner = np.cross(V1,V2) 

    sin = inner / (V1size * V2size)
    
    rad = math.asin(sin)
    return rad


def communication(data):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        # サーバを指定
        s.connect(('ロボットのIPアドレスを指定', 50007))
        s.sendall(data.encode())
        
# webカメラを扱うオブジェクトを取得
cap = cv2.VideoCapture(1)
c = 1
t = 0
purpose = 0
fname_img2='t4.jpg'
temp = cv2.imread(fname_img2, 0)
while True:
    ret,frame = cap.read()


    if ret is False:
        print("cannot read image")
        continue
    
    # 位置を抽出
    pos_blue = find_specific_color(
        frame,
        AREA_RATIO_THRESHOLD,
        LOW_COLOR_BLUE,
        HIGH_COLOR_BLUE
    )
    pos_green = find_specific_color(
        frame,
        AREA_RATIO_THRESHOLD,
        LOW_COLOR_GREEN,
        HIGH_COLOR_GREEN
    )
    pos_pink = find_specific_color(
        frame,
        AREA_RATIO_THRESHOLD,
        LOW_COLOR_PINK,
        HIGH_COLOR_PINK
    )
    
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    #マッチングテンプレートを実行
    match_result = cv2.matchTemplate(gray, temp, cv2.TM_CCOEFF_NORMED)
    threshold = 0.5

    #検出結果から検出領域の位置を取得
    loc=np.where(match_result >= threshold)

    w, h = temp.shape[::-1]
    for top_left in zip(*loc[::-1]):
        bottom_right = (top_left[0] + w, top_left[1] + h)
    try:
        centerX = int((top_left[0] + bottom_right[0]) / 2)
        centerY = int((top_left[1] + bottom_right[1]) / 2)
        pos_img = (centerX,centerY)
    except:
        pos_img = None
        
    if purpose == 0:
        if pos_blue and pos_green and pos_pink is not None:
            c = 1
            # 抽出した座標に丸を描く
            cv2.circle(frame,pos_blue,10,(0,0,255),-1)
            cv2.circle(frame,pos_green,10,(0,0,255),-1)
            x = int(((pos_green[0] - pos_blue[0]) / 2) + pos_blue[0])
            y = int(((pos_green[1] - pos_blue[1]) / 2) + pos_blue[1])
            cv2.circle(frame,(x,y),10,(0,0,255),-1)
            
            cv2.line(frame, pos_blue, pos_green, (0, 255, 0))
            cv2.circle(frame,pos_pink,10,(0,0,255),-1)
            cv2.line(frame, pos_pink, (x,y), (0, 255, 0))

            #点
            O = [x,y]
            P = list(pos_blue)
            Q = list(pos_pink)

            #ベクトル
            V1 = [P[0]-O[0],P[1]-O[1]]
            V2 = [Q[0]-O[0],Q[1]-O[1]]

            #角度の計算
            rad = calcAngle(V1,V2)
            sin = calc_sin(V1,V2)
            #ラジアンを角度に変換
            degree = math.degrees(rad)
            if sin > 0:
                degree = 360 - degree
            degree =  degree - 90
            if degree < 0:
                r = "r "
                degree = degree * -1
            else:

                r = "l "
            
            if (time.time() - t) >= 4:
                communication("4")
                communication(r + str(int(degree)))
                t = time.time()

                time.sleep(2)

                communication("0")
                a = (x - pos_pink[0]) ** 2
                b = (y - pos_pink[1]) ** 2
                if (a + b) < 20000:
                    communication("4")
                    communication("8")
                    time.sleep(3)
                    communication("5")
                    time.sleep(6)

                    purpose = 1
        else:
            if c == 1:
                communication("4")
                c = 0





                
    if purpose == 1:
        if pos_blue and pos_green and pos_img is not None:
            c = 1
            # 抽出した座標に丸を描く
            cv2.circle(frame,pos_blue,10,(0,0,255),-1)
            cv2.circle(frame,pos_green,10,(0,0,255),-1)
            x = int(((pos_green[0] - pos_blue[0]) / 2) + pos_blue[0])
            y = int(((pos_green[1] - pos_blue[1]) / 2) + pos_blue[1])
            cv2.circle(frame,(x,y),10,(0,0,255),-1)
            
            cv2.line(frame, pos_blue, pos_green, (0, 255, 0))
            cv2.circle(frame,pos_img,10,(0,0,255),-1)
            cv2.line(frame, pos_img, (x,y), (0, 255, 0))

            #点
            O = [x,y]
            P = list(pos_blue)
            Q = list(pos_img)

            #ベクトル
            V1 = [P[0]-O[0],P[1]-O[1]]
            V2 = [Q[0]-O[0],Q[1]-O[1]]

            #角度の計算
            rad = calcAngle(V1,V2)
            sin = calc_sin(V1,V2)
            #ラジアンを角度に変換
            degree = math.degrees(rad)
            if sin > 0:
                degree = 360 - degree

            degree = 90 - degree

            if degree < 0:
                r = "l "
                degree = degree * -1
            else:

                r = "r "
            
            if (time.time() - t) >= 5:
                communication("4")
                communication(r + str(int(degree)))
                t = time.time()

                time.sleep(3)

                communication("0")
                a = (x - pos_img[0]) ** 2
                b = (y - pos_img[1]) ** 2

                if (a + b) < 16000:

                    communication("4")
                    communication("6")
                    time.sleep(4)
                    communication("1")
                    time.sleep(1)
                    communication("4")


                    break
        else:
            if c == 1:
                communication("4")
                c = 0



    # 画面に表示する
    cv2.imshow('frame',frame)

    # キーボード入力待ち
    key = cv2.waitKey(1) & 0xFF

    # qが押された場合は終了する
    if key == ord('q'):
        break

cv2.destroyAllWindows()

ロボットのプログラム

import socket
import ev3dev.ev3 as ev3
import time
m_l = ev3.LargeMotor('outB')
m_r = ev3.LargeMotor('outC')
m_m = ev3.MediumMotor('outA')
gs = ev3.GyroSensor()
us = ev3.UltrasonicSensor()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

    s.bind(('ロボットのIPアドレスを指定', 50007))
    s.listen(1)

    while True:
        # 誰かがアクセスしてきたら、コネクションとアドレスを入れる
        conn, addr = s.accept()
        with conn:
            data = conn.recv(1024)
            data = data.decode()
            print(data)
            if data == "0":
                m_l.run_forever(speed_sp=100)
                m_r.run_forever(speed_sp=100)
            elif data == "1":
                m_l.run_forever(speed_sp=-100)
                m_r.run_forever(speed_sp=-100)
            elif data == "4":
                m_l.stop()
                m_r.stop()
            elif data == "5":
                m_m.run_to_rel_pos(position_sp=1440, speed_sp=300, stop_action='hold')
            elif data == "6":
                m_m.run_to_rel_pos(position_sp=-1440, speed_sp=500, stop_action='hold')

            elif data == "8":
                m_l.run_forever(speed_sp=100)
                m_r.run_forever(speed_sp=100)
                while us.value() >= 60:
                    time.sleep(0.1)
                m_l.stop()
                m_r.stop()                

            elif "l " in data:
                try:
                    angle = int(data[2:]) * -1
                    v = gs.value()
                    m_l.run_forever(speed_sp=-100)
                    m_r.run_forever(speed_sp=100)
                    while (gs.value() - v) > angle:
                        time.sleep(0.1)
                    m_l.stop()
                    m_r.stop()
                    
                except:
                    pass
                   
            elif "r " in data:
                try:
                    angle = int(data[2:])
                    v = gs.value()
                    m_l.run_forever(speed_sp=100)
                    m_r.run_forever(speed_sp=-100)
                    while (gs.value() - v) < angle:
                        time.sleep(0.1)
                    m_l.stop()
                    m_r.stop()
                    
                except:
                    pass

画像処理のプログラムではロボットについている緑と青の折り紙とコップの位置を表すピンクの折り紙を色で検出しすべて検出できた場合には緑と青の折り紙の間に線を引きその線の中心を求めます。
線の中心からピンクの折り紙の中心まで線を引き青の折り紙からピンクの折り紙の角度を求めます。
この時ピンクの折り紙がロボットの後ろにあるか前にあるかを判断して360度の角度を求めます。
そしてロボットに何度回ればいいかを伝えてロボットが指示どおりに動きます。
この後、前に進むプログラムを入れて次にロボットに命令が来るまで前に進みます。
これを2秒に一度実行します。
ロボットとコップの位置が近くなったらロボットが超音波センサーを使ってコップの距離を測定し、6㎝以下になったら、モーターを動かしてコップを持ち上げます。
ロボットが壊れるので持ち上げられるのは紙コップとプラコップのみです。
持ち上げた後は上のように角度を調節しながら走るプログラムのピンクの折り紙の座標をテンプレートマッチングで取得したカタツムリの絵の座標に入れ替えてカタツムリの絵のある場所まで持ってきてくれます。

ロボットのほうのプログラムはsocket通信を使って受け取った信号どおりに制御するプログラムです。
これを実際に動かすと
f:id:katatsumuri527:20200429152933p:plain
f:id:katatsumuri527:20200429154354p:plain
f:id:katatsumuri527:20200429154400p:plain
画面上に点と線が表示されてロボットが動きます。

実際に動いている写真を載せておきます。
f:id:katatsumuri527:20200429155608j:plainf:id:katatsumuri527:20200429155822j:plain


コップ運びロボット(EV3版)