BrightEyes日記
長嶋 洋一
2012年12月17日(月)
遂に届いた。 ここ の11月15日、さらに ここ で書いていた、 これ である。とりあえず、任意のムービーをLEDパターンで表示するためのファイル変換が ここ にあるらしい。
174個ものLEDを制御するには、単純にArduinoでは出来ないので、専用ハードを搭載していて、 その情報はまだ簡単には出てこない模様である。 とりあえず、 ここ の「README.md」によれば、おいおい
が公開される、とあり、実際には前者のためのツールとして「ビデオをLEDパターンに変換するコンバータ」と、 「SDカード上のデータを再生するツール」について記載されているという。
- Software to convert you're awesome videos to playback on the glasses
- Software to control the glasses
さっそく、 ここ を見てみると、以下のように解説があった。
また、 ここ を見てみると、以下のように解説があった(Macについてのみ)。readme.txt Place the input video file in the /data/ folder, named "input.mov". Input format is ideally 627x220, 30 fps, MUST be .mov. We also recommend using the Animation/Millions of Colors preset when exporting your .mov files. Anything outside that size will be scaled, anything of a different FPS will be played at 30 FPS (unless SDplayer.ino, the player firmware for Bright Eyes, is modified). The converter program itself is controlled via the keyboard: - c converts the input file, closing the program once done - p plays / pauses playback of the input file for review - r resets playback of the movie to the start (this does not affect conversion) - s shows / hides an overlay of the Bright Eyes LEDs for guidance - t toggles text information display on or off Output will be into the /data/ folder, with files named movieNNN.dat, sequentially numbered allowing you to convert lots of files and then transfer them to SD card for playback. When conversion finishes, the Processing console (black area at the bottom of the screen) will display the name of the most recently converted file.ムービーの画素はかなり変則的であるが、 ここ の「bright-eyes-coords.xml」を見ると、LEDに対応する座標は以下のようになっているようである。readme.txt ************************************************************************************** How to write your own firmware for the Bright Eyes board: ************************************************************************************** Just use the Arduino IDE! Instructions for transferring the code to the board (as we don't use USB uploading like a normal Arduino) are given below. First though, a few notes on what you CAN'T do with the Bright Eyes board. The Bright Eyes bootloader uses EEPROM on the ATMEGA to store the size and last-modified date of the firmware file in order to avoid loading the same file at every boot. Using the same areas of EEPROM in your Arduino code will cause problems. The no-go areas of EEPROM are the last 8 bytes (as we store two DWORDS) - these are EEPROM addresses 0x3F8..0x3FF inclusive. If none of the above makes much sense to you, or you aren't sure if you are using EEPROM, don't worry - it's very difficult to do without realising so the chances are EXTREMELY good that you are fine! (And you won't break your Bright Eyes even if there is an issue, strange things will just happen..). ************************************************************************************** How to transfer firmware you have written in the Arduino IDE to the Bright Eyes board: ************************************************************************************** 1. Enable verbose compilation output ------------------------------------ With the Arduino IDE open, select Arduino -> Preferences from the Menu bar (Apple+,). Ensure the checkbox labelled "Show verbose output during: compilation" is checked. 2. Compile (Verify) your code ----------------------------- Click the Check (Verify) button as opposed to the usual Arrow (Upload) button. This will cause the code to be compiled, but not uploaded, and a lot of text will appear at the bottom of the Arduino IDE window (in the console area). 3. Copy the output directory location ------------------------------------- When compilation is complete, there will be a penultimate line in the console output which shows the location of the compiled file. For example, it might say something like: /var/folders/g7/btk0n8zs669b453qkgy94g980000gn/T/build2705930656204672901.tmp/sketch_nov12a.cpp.hex We need to copy just the folder, not the full file location, so select from the start of this line to the \ or / before the end and copy that text (Apple+C). The clipboard will then contain something like: /var/folders/g7/btk0n8zs669b453qkgy94g980000gn/T/build2705930656204672901.tmp/ 4. Find the compiled .hex file and copy to your SD card ------------------------------------------------------- We now need to find the compiled file: Run Terminal - you can find this in Applications -> Utilities in Finder, or by typing "Terminal" into Spotlight (Apple+Space). Once terminal is open (another black screen!), type "open" then a space and then paste in the location we copied from earlier. For example, this might read: open /var/folders/g7/btk0n8zs669b453qkgy94g980000gn/T/build2705930656204672901.tmp/ Once you have done this, press Enter and a new Finder window should open. You can close Terminal now if you like. This folder will contain a large number of files (all of which are related in some way to compiling your code). You are looking for the only file in this directory which has the extension .hex. It will be named after your sketch name, with .cpp.hex appended to the end. You will notice that this is the same as the ending of the penultimate line of compilation (the bit we didn't copy). You can now copy this file to your SD card. Place it in the root of the card (that is, not in any folders, just on the card itself). 5. Rename the .hex file to firmware.hex --------------------------------------- On the SD card, rename your copied file to firmware.hex. This will make sure that the Bright Eyes board knows to load it when you next start. 6. Flash the board! ------------------- With the Bright Eyes turned off, insert the SD card and then switch the board on. The red status LED should flash three times and then blink repeatedly during code flashing. Once it is done, leave the board for a while to reboot and your code should be running and ready to play with! If for whatever reason, this doesn't work, try turning off the Bright Eyes board, reseating the SD card and turning the board on again. It is also important to note that the Bright Eyes board stores the file size and last-modified date of the most recently flashed file. It uses this information to avoid flashing the same file at every boot, so if you are trying to flash the same file over itself (although you should NEVER need to do this), try recompiling the source to create a .hex file with a different last-modified date.何か適当な動画をQuickTimePlayerPro7で指定された画素サイズに変換して、表示させるファイルを作るのは簡単そうだが、 なんせ これ は10個あるのに対して、こちらのBrightEyesは1個しかない。 たった1枚のSDカードを潰してしまって止まるのもナンなので、まずはとりあえず、 実験用のマイクロSDカードを業者に発注してみることにした。 送られて来たBrightEyesに付いていたマイクロSDカードは128MBだったが、 そんな小さな容量のものはかなり昔に製造中止であり、商社1社だけノーブランド品で「残り52個」と出ていたのは512MBだった。 2GBとかを越えるとフォーマットが違って来るので、とりあえずこれを注文した。bright-eyes-coords address="1" leftX="63" rightX="68" topY="17" bottomY="29" address="2" leftX="80" rightX="85" topY="17" bottomY="29" address="3" leftX="97" rightX="102" topY="17" bottomY="29" address="4" leftX="114" rightX="119" topY="17" bottomY="29" address="5" leftX="131" rightX="136" topY="17" bottomY="29" address="6" leftX="148" rightX="153" topY="17" bottomY="29" address="7" leftX="165" rightX="170" topY="17" bottomY="29" address="8" leftX="182" rightX="187" topY="17" bottomY="29" address="9" leftX="199" rightX="204" topY="17" bottomY="29" address="10" leftX="29" rightX="34" topY="46" bottomY="58" address="11" leftX="46" rightX="51" topY="46" bottomY="58" address="12" leftX="63" rightX="68" topY="46" bottomY="58" address="13" leftX="80" rightX="85" topY="46" bottomY="58" address="14" leftX="97" rightX="102" topY="46" bottomY="58" address="15" leftX="114" rightX="119" topY="46" bottomY="58" address="16" leftX="131" rightX="136" topY="46" bottomY="58" address="17" leftX="148" rightX="153" topY="46" bottomY="58" address="18" leftX="165" rightX="170" topY="46" bottomY="58" address="19" leftX="182" rightX="187" topY="46" bottomY="58" address="20" leftX="199" rightX="204" topY="46" bottomY="58" address="21" leftX="216" rightX="221" topY="46" bottomY="58" address="22" leftX="12" rightX="17" topY="75" bottomY="87" address="23" leftX="29" rightX="34" topY="75" bottomY="87" address="24" leftX="46" rightX="51" topY="75" bottomY="87" address="25" leftX="63" rightX="68" topY="75" bottomY="87" address="26" leftX="80" rightX="85" topY="75" bottomY="87" address="27" leftX="97" rightX="102" topY="75" bottomY="87" address="28" leftX="114" rightX="119" topY="75" bottomY="87" address="29" leftX="131" rightX="136" topY="75" bottomY="87" address="30" leftX="148" rightX="153" topY="75" bottomY="87" address="31" leftX="165" rightX="170" topY="75" bottomY="87" address="32" leftX="182" rightX="187" topY="75" bottomY="87" address="33" leftX="199" rightX="204" topY="75" bottomY="87" address="34" leftX="216" rightX="221" topY="75" bottomY="87" address="35" leftX="233" rightX="238" topY="75" bottomY="87" address="36" leftX="250" rightX="255" topY="75" bottomY="87" address="37" leftX="12" rightX="17" topY="104" bottomY="116" address="38" leftX="29" rightX="34" topY="104" bottomY="116" address="39" leftX="46" rightX="51" topY="104" bottomY="116" address="40" leftX="63" rightX="68" topY="104" bottomY="116" address="41" leftX="80" rightX="85" topY="104" bottomY="116" address="42" leftX="97" rightX="102" topY="104" bottomY="116" address="43" leftX="114" rightX="119" topY="104" bottomY="116" address="44" leftX="131" rightX="136" topY="104" bottomY="116" address="45" leftX="148" rightX="153" topY="104" bottomY="116" address="46" leftX="165" rightX="170" topY="104" bottomY="116" address="47" leftX="182" rightX="187" topY="104" bottomY="116" address="48" leftX="199" rightX="204" topY="104" bottomY="116" address="49" leftX="216" rightX="221" topY="104" bottomY="116" address="50" leftX="233" rightX="238" topY="104" bottomY="116" address="51" leftX="250" rightX="255" topY="104" bottomY="116" address="52" leftX="12" rightX="17" topY="133" bottomY="145" address="53" leftX="29" rightX="34" topY="133" bottomY="145" address="54" leftX="46" rightX="51" topY="133" bottomY="145" address="55" leftX="63" rightX="68" topY="133" bottomY="145" address="56" leftX="80" rightX="85" topY="133" bottomY="145" address="57" leftX="97" rightX="102" topY="133" bottomY="145" address="58" leftX="114" rightX="119" topY="133" bottomY="145" address="59" leftX="131" rightX="136" topY="133" bottomY="145" address="60" leftX="148" rightX="153" topY="133" bottomY="145" address="61" leftX="165" rightX="170" topY="133" bottomY="145" address="62" leftX="182" rightX="187" topY="133" bottomY="145" address="63" leftX="199" rightX="204" topY="133" bottomY="145" address="64" leftX="216" rightX="221" topY="133" bottomY="145" address="65" leftX="233" rightX="238" topY="133" bottomY="145" address="66" leftX="250" rightX="255" topY="133" bottomY="145" address="67" leftX="29" rightX="34" topY="162" bottomY="174" address="68" leftX="46" rightX="51" topY="162" bottomY="174" address="69" leftX="63" rightX="68" topY="162" bottomY="174" address="70" leftX="80" rightX="85" topY="162" bottomY="174" address="71" leftX="97" rightX="102" topY="162" bottomY="174" address="72" leftX="114" rightX="119" topY="162" bottomY="174" address="73" leftX="131" rightX="136" topY="162" bottomY="174" address="74" leftX="148" rightX="153" topY="162" bottomY="174" address="75" leftX="165" rightX="170" topY="162" bottomY="174" address="76" leftX="182" rightX="187" topY="162" bottomY="174" address="77" leftX="199" rightX="204" topY="162" bottomY="174" address="78" leftX="216" rightX="221" topY="162" bottomY="174" address="79" leftX="63" rightX="68" topY="191" bottomY="203" address="80" leftX="80" rightX="85" topY="191" bottomY="203" address="81" leftX="97" rightX="102" topY="191" bottomY="203" address="82" leftX="114" rightX="119" topY="191" bottomY="203" address="83" leftX="131" rightX="136" topY="191" bottomY="203" address="84" leftX="148" rightX="153" topY="191" bottomY="203" address="85" leftX="165" rightX="170" topY="191" bottomY="203" address="86" leftX="182" rightX="187" topY="191" bottomY="203" address="87" leftX="199" rightX="204" topY="191" bottomY="203" address="88" leftX="423" rightX="428" topY="17" bottomY="29" address="89" leftX="440" rightX="445" topY="17" bottomY="29" address="90" leftX="457" rightX="462" topY="17" bottomY="29" address="91" leftX="474" rightX="479" topY="17" bottomY="29" address="92" leftX="491" rightX="496" topY="17" bottomY="29" address="93" leftX="508" rightX="513" topY="17" bottomY="29" address="94" leftX="525" rightX="530" topY="17" bottomY="29" address="95" leftX="542" rightX="547" topY="17" bottomY="29" address="96" leftX="559" rightX="564" topY="17" bottomY="29" address="97" leftX="406" rightX="411" topY="46" bottomY="58" address="98" leftX="423" rightX="428" topY="46" bottomY="58" address="99" leftX="440" rightX="445" topY="46" bottomY="58" address="100" leftX="457" rightX="462" topY="46" bottomY="58" address="101" leftX="474" rightX="479" topY="46" bottomY="58" address="102" leftX="491" rightX="496" topY="46" bottomY="58" address="103" leftX="508" rightX="513" topY="46" bottomY="58" address="104" leftX="525" rightX="530" topY="46" bottomY="58" address="105" leftX="542" rightX="547" topY="46" bottomY="58" address="106" leftX="559" rightX="564" topY="46" bottomY="58" address="107" leftX="576" rightX="581" topY="46" bottomY="58" address="108" leftX="593" rightX="598" topY="46" bottomY="58" address="109" leftX="372" rightX="377" topY="75" bottomY="87" address="110" leftX="389" rightX="394" topY="75" bottomY="87" address="111" leftX="406" rightX="411" topY="75" bottomY="87" address="112" leftX="423" rightX="428" topY="75" bottomY="87" address="113" leftX="440" rightX="445" topY="75" bottomY="87" address="114" leftX="457" rightX="462" topY="75" bottomY="87" address="115" leftX="474" rightX="479" topY="75" bottomY="87" address="116" leftX="491" rightX="496" topY="75" bottomY="87" address="117" leftX="508" rightX="513" topY="75" bottomY="87" address="118" leftX="525" rightX="530" topY="75" bottomY="87" address="119" leftX="542" rightX="547" topY="75" bottomY="87" address="120" leftX="559" rightX="564" topY="75" bottomY="87" address="121" leftX="576" rightX="581" topY="75" bottomY="87" address="122" leftX="593" rightX="598" topY="75" bottomY="87" address="123" leftX="610" rightX="615" topY="75" bottomY="87" address="124" leftX="372" rightX="377" topY="104" bottomY="116" address="125" leftX="389" rightX="394" topY="104" bottomY="116" address="126" leftX="406" rightX="411" topY="104" bottomY="116" address="127" leftX="423" rightX="428" topY="104" bottomY="116" address="128" leftX="440" rightX="445" topY="104" bottomY="116" address="129" leftX="457" rightX="462" topY="104" bottomY="116" address="130" leftX="474" rightX="479" topY="104" bottomY="116" address="131" leftX="491" rightX="496" topY="104" bottomY="116" address="132" leftX="508" rightX="513" topY="104" bottomY="116" address="133" leftX="525" rightX="530" topY="104" bottomY="116" address="134" leftX="542" rightX="547" topY="104" bottomY="116" address="135" leftX="559" rightX="564" topY="104" bottomY="116" address="136" leftX="576" rightX="581" topY="104" bottomY="116" address="137" leftX="593" rightX="598" topY="104" bottomY="116" address="138" leftX="610" rightX="615" topY="104" bottomY="116" address="139" leftX="372" rightX="377" topY="133" bottomY="145" address="140" leftX="389" rightX="394" topY="133" bottomY="145" address="141" leftX="406" rightX="411" topY="133" bottomY="145" address="142" leftX="423" rightX="428" topY="133" bottomY="145" address="143" leftX="440" rightX="445" topY="133" bottomY="145" address="144" leftX="457" rightX="462" topY="133" bottomY="145" address="145" leftX="474" rightX="479" topY="133" bottomY="145" address="146" leftX="491" rightX="496" topY="133" bottomY="145" address="147" leftX="508" rightX="513" topY="133" bottomY="145" address="148" leftX="525" rightX="530" topY="133" bottomY="145" address="149" leftX="542" rightX="547" topY="133" bottomY="145" address="150" leftX="559" rightX="564" topY="133" bottomY="145" address="151" leftX="576" rightX="581" topY="133" bottomY="145" address="152" leftX="593" rightX="598" topY="133" bottomY="145" address="153" leftX="610" rightX="615" topY="133" bottomY="145" address="154" leftX="406" rightX="411" topY="162" bottomY="174" address="155" leftX="423" rightX="428" topY="162" bottomY="174" address="156" leftX="440" rightX="445" topY="162" bottomY="174" address="157" leftX="457" rightX="462" topY="162" bottomY="174" address="158" leftX="474" rightX="479" topY="162" bottomY="174" address="159" leftX="491" rightX="496" topY="162" bottomY="174" address="160" leftX="508" rightX="513" topY="162" bottomY="174" address="161" leftX="525" rightX="530" topY="162" bottomY="174" address="162" leftX="542" rightX="547" topY="162" bottomY="174" address="163" leftX="559" rightX="564" topY="162" bottomY="174" address="164" leftX="576" rightX="581" topY="162" bottomY="174" address="165" leftX="593" rightX="598" topY="162" bottomY="174" address="166" leftX="423" rightX="428" topY="191" bottomY="203" address="167" leftX="440" rightX="445" topY="191" bottomY="203" address="168" leftX="457" rightX="462" topY="191" bottomY="203" address="169" leftX="474" rightX="479" topY="191" bottomY="203" address="170" leftX="491" rightX="496" topY="191" bottomY="203" address="171" leftX="508" rightX="513" topY="191" bottomY="203" address="172" leftX="525" rightX="530" topY="191" bottomY="203" address="173" leftX="542" rightX="547" topY="191" bottomY="203" address="174" leftX="559" rightX="564" topY="191" bottomY="203" outputSize width="627" height="220"そして、Processingでのファイル変換というのをやってみる事にした。 あまり細かいものや、ミュージックビデオとか具象物や風景では、このBrightEyesの解像度では奇麗になる気がしない。 手元には過去に実験用に購入した多数の抽象的なループmovie素材があるので、 そこから これ を選んでみた。 そしてQuickTimePlayerPro7で、このムービーを
と指定して変換したのが input.mov である。 そしてProcessing2.0を呼び出して、スケッチフォルダ内に以下の「sketch_121217a.pde」を置いて、 さらにその中に「data」フォルダを置いて、input.movを置いた。
- 画面サイズを 627x220 に
- フレームレートを30 fps に
- 圧縮を Animation に
- 色は Millions に
- 書き出しは"input.mov"というファイル名
エラーが出たのでさらにこの中にinput.movと並べて置いたのは、 ここ にあった「bright-eyes-coords.xml」である。 これで以下のように、BrightEyesのLED画素の配置されたウインドウ内で変換されたムービーをpreviewできた。// storage class for each LED node private class node{ public int leftX; public int rightX; public int topY; public int bottomY; node(int leftX, int rightX, int topY, int bottomY){ this.leftX = leftX; this.rightX = rightX; this.topY = topY; this.bottomY = bottomY; } }; // movie import processing.video.*; Movie movie; boolean playing = false; boolean converting = false; float movFrameRate = 30; int numFrames = 0; // mapping node[] nodes; int mapWidth = 0; int mapHeight = 0; final int numLeds = 174; final int headerLen = 16; float dstW; float dstH; float srcW; float srcH; // output file saving StringBuilder outputFileName = new StringBuilder("movie000.dat"); final int minMovieIndex = 0; final int maxMovieIndex = 999; final String header = "TWSU-BRIGHT-EYES"; int outputFileSize = 0; byte[] outputArray; int k; // GUI boolean showLeds = false; boolean showText = true; void setup() { // load the input movie and set it up movie = new Movie(this, dataPath("input.mov")); movie.loop(); movie.play(); movie.jump(0); movie.pause(); // create output array based on number of frames and header numFrames = getMovLength(); outputFileSize = headerLen + (numFrames*numLeds); outputArray = new byte[outputFileSize]; // load mapping XML XML inputXML = loadXML("bright-eyes-coords.xml"); XML[] coordsXML = inputXML.getChildren("node"); XML sizeXML = inputXML.getChild("outputSize"); // extract map width and height mapWidth = sizeXML.getInt("width"); mapHeight = sizeXML.getInt("height"); // set app size to match map size(mapWidth, mapHeight); // check we have a correct map if(coordsXML.length != numLeds){ print("Input XML length is "); print(coordsXML.length); print(" should be "); println(numLeds); exit(); } else { // if we do, create a node array nodes = new node[coordsXML.length]; // populate the node array from the XML for (int i = 0; i < coordsXML.length; i++) { int address = coordsXML[i].getInt("address"); int leftX = coordsXML[i].getInt("leftX"); int rightX = coordsXML[i].getInt("rightX"); int topY = coordsXML[i].getInt("topY"); int bottomY = coordsXML[i].getInt("bottomY"); nodes[address-1] = new node(leftX, rightX, topY, bottomY); } } } void draw() { // draw routine // draw the movie //image(movie, 0, 0, mapWidth, mapHeight); image(movie,0,0); // draw the LEDs, if appropriate if(showLeds){ drawLeds(); } // create the text string, if appropriate if(showText){ String status = ""; if(converting){ status = "Converting... " + (int)(100.0*movie.time()/movie.duration()) + "% complete"; } else if(playing){ status = "Playing... " + (int)(100.0*movie.time()/movie.duration()) + "%"; } else { status = "Press:\nc to convert\np to play / pause\nr to reset movie to the start\ns to overlay LEDs\nt to toggle text on / off"; } // draw the text string noStroke(); fill(255, 0, 0); text(status, 10, 20); } } void drawLeds(){ // LED draw routine // set rectMode to match the way we store the LED co-ordinates rectMode(CORNERS); // set stroke and fill (yellow, 75/255 opacity) noStroke(); fill(255, 255, 0, 75); // draw each rectangle, one by one for (int i = 0; i < nodes.length; i++) { rect(nodes[i].leftX, nodes[i].topY, nodes[i].rightX, nodes[i].bottomY); } } void movieEvent(Movie m) { // Called every time a new frame is available to read // read the frame m.read(); if(converting){ // convert the frame if we need to convertFrame(); // if we are at the end, finish conversion if(getMovFrame()==getMovLength()-1){ endConversion(); } } } void keyPressed(){ switch(key){ case 'c': // convert the movie if(!converting){ playing = false; beginConversion(); } break; case 'r': // reset the movie to the beginning if(!converting){ movie.jump(0); movie.play(); movie.stop(); playing = false; break; } case 's': // toggle showing the leds showLeds = !showLeds; break; case 'p': // play / pause if(!converting){ playing = !playing; if(playing){ movie.play(); } else { movie.pause(); } } break; case 't': // toggle text visibility showText = !showText; break; } } void convertFrame(){ // a few variables for the pixel mapping float dstX; float dstY; float srcX; float srcY; float accum; float pixelCount; // one LED at a time for(int j=0; jそして、変換開始の「c」コマンドを押すと、このプレビューウインドウが消えて、以下のように「begin conversion」、 そして「end conversion」、「movie000.dat」と簡潔で淡白なメッセージが出て(^_^;)、 無事にLEDが読み込む、つまりマイクロSDカードに書き込むべきデータに変換完了できた。 一度に1つしか出来ない、というのがこれで判明した。