AIラジコン

このエントリーをはてなブックマークに追加

手書き文字でラジコンを操作します

使い方

このアプリにはGoogleCloudApiのAPIキーが必要です.

  1. Google Cloud Platform のダッシュボードに行きます
    APIとサービスより,ライブラリを選択します

  1. APIライブラリの検索画面が出るのでvisionで検索します

  1. Cloud VisionAPIを選択します

  1. 有効にするボタンを押します

  1. 有効化がおわったら,APIとサービスのダッシュボードに戻り,認証情報を選択します

  1. 認証情報を作成から,APIキーを選択します

  1. 表示されたAPIキーをHTMLにコピーして使います.HTMLのこちらにコピーしてからスタートボタンを押してください

Program

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Video Capture Example</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <script src="https://obniz.io/js/jquery-3.2.1.min.js"></script>
  <script src="https://unpkg.com/obniz@1.7.1/obniz.js"></script>
</head>
<body>

<div id="obniz-debug"></div>

<div>
  <div class="control">
    <input type="text" id="google_api_key" placeholder="google api key" />
    <button id="startAndStop">Start</button>
  </div>
</div>
<p class="err" id="errorMessage"></p>
<div style="display:flex;">
  <video id="videoInput" autoplay playsinline width=320 height=240 style="border:1px solid;width:320px;margin-right:10px"></video>
  <canvas id="canvasOutput" width=320 height=240 style="-webkit-font-smoothing:none" hidden style="display:none"></canvas>
  <div id="log" style="border:1px solid;height: 240px;overflow-y:scroll;width:320px"></div>
</div>

<script type="text/javascript">

  let power = 40;

  let rMotor, lMotor;
  let inOperation = false;
  
  let mediaDevices = navigator.mediaDevices || ((navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia) ? {
    getUserMedia(c) {
        return new Promise(((y, n) => {
            (navigator.mozGetUserMedia || navigator.webkitGetUserMedia).call(navigator, c, y, n);
        }));
    }
} : null);
  
  obniz = new Obniz("OBNIZ_ID_HERE");
  obniz.onconnect = async () => {
    obniz.display.print("ready");
    lMotor = obniz.wired("DCMotor", {forward: 0, back: 1});
    rMotor = obniz.wired("DCMotor", {forward: 11, back: 10});

    lMotor.power(0);
    rMotor.power(0);

    lMotor.forward();
    rMotor.forward();

  };

  function moveTo(lPower, rPower) {
    if (!lMotor || !rMotor) {
      return
    }
    lMotor.power(Math.abs(lPower));
    rMotor.power(Math.abs(rPower));
    lMotor.move(lPower > 0);
    rMotor.move(rPower > 0);

  }


  let streaming = false;
  let videoInput = document.getElementById('videoInput');
  let startAndStop = document.getElementById('startAndStop');
  let canvasOutput = document.getElementById('canvasOutput');
  let canvasContext = canvasOutput.getContext('2d');

  function successCallback(stream) {
    document.getElementById("videoInput").srcObject = stream;
    onVideoStarted();
  }

  function errorCallback(err) {
    console.error('mediaDevice.getUserMedia() error:', err);
  }


  startAndStop.addEventListener('click', () => {

    if (!streaming) {

      const medias = {
        audio: false, video: {
          facingMode: "user"
        }
      };

      mediaDevices.getUserMedia({
         video: true,
         audio: true
     })
     .then(successCallback).catch(errorCallback);


    } else {
      onVideoStopped();
    }

  });

  function onVideoStarted() {
    streaming = true;
    startAndStop.innerText = 'Stop';
    start();
  }

  function onVideoStopped() {
    streaming = false;
    canvasContext.clearRect(0, 0, canvasOutput.width, canvasOutput.height);
    startAndStop.innerText = 'Start';
  }

  async function start() {
    const FPS = 100;

    function processVideo() {
      try {
        if (!streaming) {
          return;
        }

        let begin = Date.now();
        let base64String = capture();

        postToGoogle(base64String);

        setTimeout(processVideo, 0);
      } catch (err) {
        console.error(err);
      }
    }

    // schedule the first one.
    setTimeout(processVideo, 0);

  }

  function capture() {
    if (!streaming) {
      return null;
    }
    canvasContext.drawImage(videoInput, 0, 0, canvasOutput.width, canvasOutput.height);
    return canvasOutput.toDataURL('image/webp');
  }

  //引数はbase64形式の文字列
  function toBlob(base64) {
    var bin = atob(base64.replace(/^.*,/, ''));
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
      buffer[i] = bin.charCodeAt(i);
    }
    // Blobを作成
    try {
      var blob = new Blob([buffer.buffer], {
        type: 'image/png'
      });
    } catch (e) {
      return false;
    }
    return blob;
  }

  function postToGoogle(base64Img) {
    if (inOperation) {
      return;
    }
    inOperation = true;

    var base64Img = base64Img.split(",")[1];
    var apiKey = document.getElementById("google_api_key").value;
    var body = {
      "requests": [
        {
          "image": {
            "content": base64Img
          },
          "features": [
            {
              "type": "LABEL_DETECTION"
            },
            {
              "type": "TEXT_DETECTION",
              "maxResults": 1
            }
          ]
        }
      ]
    };


    //request to google api
    fetch("https://vision.googleapis.com/v1/images:annotate?key=" + apiKey, {
      body: JSON.stringify(body),
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      }
    }).then(function (response) {
      return response.json();
    }).then(async function (json) {
      if (isKeywordsIncludes(json.responses, ["右", "みぎ", "right"])) {
        obniz.display.clear();
        obniz.display.print("みぎ!");
        _log("右むく");
        moveTo(power, -1 * power);
      }
      else if (isKeywordsIncludes(json.responses, ["left", "左", "ひだり"])) {
        _log("左向く");
        obniz.display.clear();
        obniz.display.print("ひだり!");
        moveTo( -1 *power, power);
      }
      else if (isKeywordsIncludes(json.responses, ["stop", "止", "とまれ"])) {
        obniz.display.clear();
        obniz.display.print("ぶれーき!");
        _log("止まる");
        moveTo(0, 0);
      }
      else if (isKeywordsIncludes(json.responses, ["すすめ", "進", "go", "前", "forward"])) {
        _log("すすむ");
        obniz.display.clear();
        obniz.display.print("まっすぐ!");
        moveTo(power, power);
      }
      else if (isKeywordsIncludes(json.responses, ["バック", "後", "back", "もどれ", "もどる"])) {
        _log("もどる");
        obniz.display.clear();
        obniz.display.print("ばっくします!")
        moveTo(-1 * power, -1 * power);
      }
      else if (isKeywordsIncludes(json.responses, ["こっち","こちら", "here"])) {
        let pos = isKeywordsIncludes(json.responses, ["こっち", "こちら","here"]);
        let sum = 0;
        for (let p of pos.vertices) {
          sum += p.x;
        }
        let avg = sum / 4;
        let rotate = (avg - canvasOutput.width / 2) / canvasOutput.width;
        moveTo(rotate * power, (1 - rotate) * power);
      } else {
        _log("keyward nothing");
        moveTo(0, 0);
      }
      inOperation = false;
    });

  }

  function isKeywordsIncludes(results, keywards) {
    if (!Array.isArray(keywards)) {
      keywards = [keywards];
    }
    for (let result of results) {
      if (result && result.labelAnnotations) {
        for (let anotation of result.labelAnnotations) {
          for (let keyward of keywards) {
            if (anotation.description.toLowerCase().indexOf(keyward) >= 0) {
              return anotation.boundingPoly || true;
            }
          }
        }
      }
      if (result && result.textAnnotations) {
        for (let anotation of result.textAnnotations) {
          for (let keyward of keywards) {
            if (anotation.description.toLowerCase().indexOf(keyward) >= 0) {
              return anotation.boundingPoly || true;
            }
          }
        }
      }
      if (result && result.fullTextAnnotation) {
        for (let keyward of keywards) {
          if (result.fullTextAnnotation.text.toLowerCase().indexOf(keyward) >= 0) {
            return result.fullTextAnnotation.boundingPoly || true;
          }
        }
      }
      return false;
    }
  }

  function _log(msg) {
    let log = document.getElementById("log");
    log.innerHTML += msg + "<br/>";
    log.scrollTop = log.scrollHeight;
  }
  
  

</script>
</body>
</html>