mongonta555のアイコン画像
mongonta555 2021年05月08日作成 (2021年05月08日更新)
製作品 製作品 閲覧数 1949
mongonta555 2021年05月08日作成 (2021年05月08日更新) 製作品 製作品 閲覧数 1949

obnizとMaixBit+ステッピングモータで360°監視カメラを作る

obnizとMaixBit+ステッピングモータで360°監視カメラを作る

はじめに

obniz IoT 2021に向けて、obnizを使うとステッピングモーターをドライバなしで駆動できる点を活かして360°の監視カメラを作ってみました。

デモ動画と結果画面

ここに動画が表示されます

MaixBitと画角45°のOV2640を利用してこのように、アイテムを配置し360°撮影してみます。

キャプションを入力できます

結果画面

キャプションを入力できます

使用した機器

配線

360°曲がるので、高屈曲性ケーブルとデュポンコネクタを用意して自作しました。
協和ハーモネット シールドスリムロボットケーブル(Amazon)

接続

obnizとMaixBitの接続

obnizとMaixBitはUART通信を行うため下記のように配線します。MaixBitは消費電力が大きくobnizのピンからは電力供給できなかったのでVCC5V(+)とGND(-)に接続しています。

obniz MaixBit
VCC(5V) + 5V
GND - GND
UART TX(10) RX(IO25)
UART RX(11) TX(IO24)

obnizとステッピングモーターの接続

下記のように接続します。

obniz ステッピングモーター
0 a(赤)
1 aa(黄)
2 b(青)
3 bb(橙)

プログラム

今回制作過程をTwitterでつぶやいていたところハセグルさんがUnitV向けのパーツライブラリを作成していたので、それを参考にさせていただきました。

パーツライブラリ(Grove_UnitVAICamera.js)

カメラとのUART通信を行うパーツライブラリです。
リポジトリに公開しています。

"use strict"; class Grove_UnitVAICamera { // Constants // Header of a receive packet static get _HOP() { return [0xFF, 0xD8, 0xEA, 0x01]; } // "Take a picture" instruction static get _TAP() { return [0xFF, 0xD8, 0xEA, 0x00]; } constructor() { this.keys = ["vcc", "cam_tx", "cam_rx", "gnd", "baud", "grove"]; this.requiredKeys = ["cam_tx", "cam_rx"]; this.ioKeys = this.keys; this.displayName = "Cam"; this.displayIoNames = { cam_tx: "camTx", cam_rx: "camRx" }; } static info() { return { name: "Grove_UnitVAICamera", }; } wired(obniz) { this.obniz = obniz; if (this.params.grove) { this.uart = this.params.grove.getUart(this.params.baud, "3v"); } else { this.obniz.setVccGnd(this.params.vcc, this.params.gnd, "5v"); this.uart = obniz.getFreeUart(); this.uart.start({ tx: this.params.cam_tx, rx: this.params.cam_rx, baud: this.params.baud, bits: this.params.bits, parity: this.params.parity, stop: this.params.stop, }); } } arrayToBase64(array) { //return Buffer.from(array).toString("base64"); // this line just work in obniz.js let uint8Array = new Uint8Array(array.length); for(let i = 0; i < uint8Array.length; i++) { uint8Array[i] = array[i]; } return btoa(String.fromCharCode(...new Uint8Array(uint8Array))); } async _drainUntil(uart, search, recv) { this.log("_drainUntil" + search); if (!recv) { recv = []; } this.log("recv:" + recv + ":" ); while (true) { const readed = uart.readBytes(); recv = recv.concat(readed); this.log("recv:" + recv + ":" ); const tail = this._seekTail(search, recv); if (tail >= 0) { recv.splice(0, tail); return recv; } await this.obniz.wait(10); } } _seekTail(search, src) { let f = 0; for (let i = 0; i < src.length; i++) { if (src[i] === search[f]) { f++; if (f === search.length) { this.log("find"); return i + 1; } } else { f = 0; } } return -1; } log(value) { // HTMLの<div id="log">log</div>にログを表示します。 // var target = document.getElementById("log"); // var text = target.innerHTML + "<BR>" + value; // target.innerHTML = text; } async takeContinuousWait() { this.log("takeContinuousWait") const uart = this.uart; let recv = await this._drainUntil(uart, Grove_UnitVAICamera._HOP); let databytes; while (true) { console.log("uartRecv"); const readed = uart.readBytes(); recv = recv.concat(readed); if (recv.length >= 6) { databytes = (recv[0] << 16) | (recv[1] << 8) | recv[2]; // size of JPEG data break; } await this.obniz.wait(10); } console.log("image: " + databytes + " Bytes"); recv.splice(0, 6); // remove tail of packet header from the received data while (true) { const readed = uart.readBytes(); recv = recv.concat(readed); if (recv.length >= databytes) { break; } await this.obniz.wait(10); } recv = recv.splice(0, databytes); // remove tail console.log(recv); return recv; } async takeOneTimeWait() { const uart = this.uart; uart.send(Grove_UnitVAICamera._TAP); // send "Take a picture" instruction this.log("Request") let recv = await this._drainUntil(uart, Grove_UnitVAICamera._HOP); this.log("Request Recv") let databytes; while (true) { const readed = uart.readBytes(); recv = recv.concat(readed); if (recv.length >= 6) { databytes = (recv[0] << 16) | (recv[1] << 8) | recv[2]; // size of JPEG data break; } await this.obniz.wait(10); } //console.log("image: " + databytes + " Bytes"); recv.splice(0, 6); // remove tail of packet header from the received data while (true) { const readed = uart.readBytes(); recv = recv.concat(readed); if (recv.length >= databytes) { break; } await this.obniz.wait(10); } recv = recv.splice(0, databytes); // remove tail return recv; } } if (typeof module === 'object') { module.exports = Grove_UnitVAICamera; }

MaixBit側のPythonスクリプト

メモリが足らないのでファームウェアは最小を使います(ただし_threadは必要)
microSDカードのルートにboot.pyという名前のファイルを作成して書き込むか、MaixPyIDEからフラッシュメモリ上に転送しておきます。

import sensor from fpioa_manager import fm from machine import UART import _thread import time,sys BOARD_NAME = "MAIXBIT" # M5STICKV or UNITV if BOARD_NAME == "M5STICKV": import lcd from board import board_info from Maix import GPIO elif BOARD_NAME == "UNITV": from modules import ws2812 class_ws2812 = ws2812(8,100) elif BOARD_NAME == "MAIXBIT": print("MAIXBIT") ##### User settings section Begin ##### # Baud rate(should be matched with obniz.) BAUD_RATE = 115200 # Shooting mode # When SHOOTING_MODE_ONE_TIME is True, Wait for "take a picture" instruction from obniz. SHOOTING_MODE_ONE_TIME = True # JPEG Compression Quality(0-100) JPEG_COMP_QUALITY = 50 # Camera Resolution(sensor.VGA, QVGA, QQVGA ...) CAMERA_RESOLUTION = sensor.QVGA # Use LED indicator USE_LED = False ##### User settings section End ##### # Send Flag SEND_MODE = False # RGB LED Settings BRIGHTNESS = 0x10 LED_INTERVAL = 100 # msec # Initialize of UART fm.register(24, fm.fpioa.UART1_TX, force=True) fm.register(25, fm.fpioa.UART1_RX, force=True) #uart_Port = UART(UART.UART1, BAUD_RATE, 8, UART.PARITY_ODD, 1, timeout=1000, read_buf_len= 4096) uart_Port = UART(UART.UART1, BAUD_RATE, 8, None, 1, timeout=1000, read_buf_len= 4096) uart_Port.init() # camera sensor.reset() sensor.set_framesize(CAMERA_RESOLUTION) sensor.set_pixformat(sensor.RGB565) if BOARD_NAME == "M5STICKV": lcd.init(type=3) # type=3 is required for maixpy 0.6.0 and above. lcd.rotation(2) fm.register(board_info.LED_W, fm.fpioa.GPIO3) led_w = GPIO(GPIO.GPIO3, GPIO.OUT) led_w.value(1) #RGBW LEDs are Active Low fm.register(board_info.LED_R, fm.fpioa.GPIO4) led_r = GPIO(GPIO.GPIO4, GPIO.OUT) led_r.value(1) #RGBW LEDs are Active Low fm.register(board_info.LED_G, fm.fpioa.GPIO5) led_g = GPIO(GPIO.GPIO5, GPIO.OUT) led_g.value(1) #RGBW LEDs are Active Low fm.register(board_info.LED_B, fm.fpioa.GPIO6) led_b = GPIO(GPIO.GPIO6, GPIO.OUT) led_b.value(1) #RGBW LEDs are Active Low def BlinkLED(r, g, b): # M5StickV can't control brightness if r > 0: led_r.value(0) if g > 0: led_g.value(0) if b > 0: led_b.value(0) time.sleep_ms(LED_INTERVAL) led_r.value(1) led_g.value(1) led_b.value(1) time.sleep_ms(LED_INTERVAL) elif BOARD_NAME == "UNITV": sensor.set_vflip(1) sensor.set_hmirror(1) class_ws2812 = ws2812(8, 1) a = class_ws2812.set_led(0, (100, 0, 100)) a = class_ws2812.display() time.sleep(1) a = class_ws2812.set_led(0, (0, 0, 0)) a = class_ws2812.display() # Blink RGB LED def BlinkLED(r,g,b): if USE_LED == True: class_ws2812.set_led(0,(r,g,b)) class_ws2812.display() time.sleep_ms(LED_INTERVAL) class_ws2812.set_led(0,(0,0,0)) class_ws2812.display() time.sleep_ms(LED_INTERVAL) elif BOARD_NAME == "MAIXBIT": def BlinkLED(r,g,b): time.sleep_ms(100) # sensor.skip_frames(1, 300) sensor.set_brightness(1) sensor.set_auto_whitebal(1) sensor.set_auto_exposure(1) sensor.run(1) def take(shooting_mode): global SEND_MODE if USE_LED == True: # Turn on the red LED during the process. BlinkLED(BRIGHTNESS,0,0) img = sensor.snapshot() if BOARD_NAME == "M5STICKV": lcd.display(img) if (SEND_MODE == True or SHOOTING_MODE_ONE_TIME == False): if BOARD_NAME == "M5STICKV": img.draw_string(320, 240 , "Send", color=lcd.RED) lcd.display(img) # Lowering the quality value increases the frame rate. #img = sensor.snapshot() img.compress(quality=JPEG_COMP_QUALITY) sendImage(img) if USE_LED == True: BlinkLED(0,0,BRIGHTNESS) def sendImage(p_buf): global SEND_MODE # Packet header buf_size1 = (p_buf.size()& 0xFF0000)>>16 buf_size2 = (p_buf.size()& 0x00FF00)>>8 buf_size3 = (p_buf.size()& 0x0000FF)>>0 data_packet = bytearray([0xFF,0xD8,0xEA,0x01,buf_size1,buf_size2,buf_size3,0x00,0x00,0x00]) #data_packet = bytearray([0x02,0xD8,0xEA,0x01,buf_size1,buf_size2,buf_size3,0x00,0x00,0x00]) uart_Port.write(data_packet) # Transmit image data. uart_Port.write(p_buf) print("Data Sent") #time.sleep(0.5) if SHOOTING_MODE_ONE_TIME == True: SEND_MODE = False def recvData(uart_Port): global SEND_MODE while True: print("recvdata") while uart_Port.any() == False: #print("data wait") time.sleep_ms(100) recv_data = uart_Port.read() #recv_str = recv_data.decode('utf-8') print(recv_data) if (recv_data == bytearray([0x00, 0xFF, 0xD8, 0xEA, 0x00])) or \ (recv_data == bytearray([0xFF, 0xD8, 0xEA, 0x00])): SEND_MODE = True time.sleep_ms(100) if SHOOTING_MODE_ONE_TIME == True: # SINGLE SHOT _thread.start_new_thread(recvData, (uart_Port,)) try: while True: #print("Take Picture") take(SHOOTING_MODE_ONE_TIME) time.sleep_ms(500) except Exception as e: uart_Port.deinit() del _thread sys.print_exception(e) sys.exit()

obniz側のブラウザプログラム

obnizクラウドで公開しています。(まだ公開の仕方がよくわかっていないので説明文書がありません。)
下記の2ステップを繰り返した後、元に戻ります。(初期状態では45°毎に8回)
・写真を撮る
・ステッピングモーターの回転

<html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" /> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@3.x/obniz.js" crossorigin="anonymous" ></script> <script src="https://obniz.io/users/2489/repo/Grove_UnitVAICamera.js"></script> <script src="bower_components/console-log-div/console-log-div.js"></script> </head> <body> <div id="obniz-debug"></div> <script> console.log("Start") var obniz = new Obniz("OBNIZ_ID_HERE"); // called on online obniz.onconnect = async function() { // パーツライブラリの登録 Obniz.PartsRegistrate(Grove_UnitVAICamera); obniz.display.clear(); obniz.display.print("360 Camera"); var cam = obniz.wired("Grove_UnitVAICamera", { gnd:8, vcc:9, cam_rx:10, cam_tx:11, baud:115200, parity:"off", stop:1 }); // var cam = obniz.wired("Grove_UnitVAICamera", { grove: obniz.grove0, baud:115200 }); // Groveの場合(未検証) var motor = obniz.wired("StepperMotor", {a:0, aa:1, b:2, bb:3}); motor.rotationStepCount = 200; const image_area = [ "image0", "image1", "image2", "image3", "image4", "image5", "image6", "image7" ]; const rotation = [ -45, -45, -45, -45, -45, -45, -45, -45 ]; //if the "Take Picture" clicked $("#take_picture").on("click", async function() { //show input text on obniz display obniz.display.clear(); obniz.display.print("Take Picture"); all_rotation = 0; for (let i = 0; i < image_area.length; i++) { console.log(rotation[i]); await motor.rotateWait(rotation[i]); const jpegData = await cam.takeOneTimeWait(); console.log("recv"); // <img id="image?">に表示 document.getElementById(image_area[i]).src = "data:image/jpeg;base64," + cam.arrayToBase64(jpegData); all_rotation = all_rotation + rotation[i]; }; await obniz.wait(500); console.log("home") await motor.rotateWait(-1 * all_rotation); }); // called while online. obniz.onloop = async function() { }; }; // called on offline obniz.onclose = async function() { }; </script> <button class="btn btn-primary" id="take_picture">Take Picture</button> <hr> <img id="image0"><img id="image1"><img id="image2"><img id="image3"><img id="image4"><img id="image5"><img id="image6"><img id="image7"> <hr> <div id="log"></div> </body> </html>

終わりに

K210というAIチップ搭載のMaixBitをカメラとして使うので、人の検出をしたら撮影開始等の機能も埋め込みたかったのですが、今回はメモリ不足のようでうまく動かないため組み込めませんでした。また、時間ができたらリベンジしてみます。
 
 obnizとJavaScriptの経験が浅いので、間違っている実装もあると思いますがその点はご了承ください。

ログインしてコメントを投稿する