2016年6月7日 星期二

Unreal: Begin A Virtual Reality Project

since: 2016/06/07
update: 2016/11/05
reference:
1. Setting up UE4 to work with SteamVR | Unreal Engine
2. Unreal Engine 4.12 Released!


A. SteamVR is Ready

     > 確認 SteamVR 開啟後, 是處在就緒的狀態.

-----------------------------------------------------------------------------------------------

B. 建立新專案:
   
> Launch Unreal Engine 4.12.0


     1. New Project > Blueprint > Blank

     2. Mobile / Tablet > Scalable 3D or 2D > No Starter Content >
        Folder: E:\RD\projects\Unreal
        Name: HelloVR

     3.檢查是否啟用支援該 VR 設備
        a. Edit > Plugins

        b. Virtual RealitySteamVR 支援, 勾選 Enabled

     4. VR Preview
        a.  按下 Play 鍵旁的小三角形 > VR Preview

        b. 結果: (可戴上 VR 頭盔觀看)

-----------------------------------------------------------------------------------------------

C. 修正 VR 的地平線水平高度
     1. 新增 MapBlueprint 資料夾:

        > Content Brower > Add New > New Folder

        結果: 在 Content BrowerContent 底下, 可以看到 MapBlueprint 資料夾

     2. 進入 Blueprint 資料夾, 新增二個 Blueprint 類別:
         > Content Brower > Add New > Blueprint Class

     > Blueprint Node: Game Mode              Name: VR_GameMode
     > Blueprint Node: Pawn                         Name: VR_Pawn

     結果:

     3. 開啟 VR_GameMode Blueprint:
         > Details > classes:
         > Default Pawn Class: VR_Pawn

     4. 關閉 VR_GameMode Blueprint 後, 回到關卡:
         Window
> World Settings

     > World Settings > GameMode:
        GameMode Override: VR_GameMode
       
(只對目前的關卡改寫預設值)

     5. 開啟 VR_Pawn Blueprint:
         > Details > Camera:
            Base Eye Height: 0.0

   /* 以下為 4.12 版本 ################################################
     6. 在 Components 頁籤, 新增: SteamVRChaperoneCamera component


    > 並確定: camera 的 Transform Location 設為 0,0,0.

     7. 關閉 VR_Pawn Blueprint 後, 回到關卡:
         > World Outliner > Floor:
         > Transform > Location: X: 0.0 Y: 0.0 Z: 0.0

         > World Outliner > Player Start:
         > Transform > Location: X: 0.0 Y: 0.0 Z: 1.0

     8. 再執行一次: VR Preview

   以上為 4.12 版本 ################################################ */


   /* 更新: 4.13 版本 */
     6. 在 Components 頁籤, 新增: Capsule, Scene, Camera SteamVRChaperone component

     7. 點選 Capsule Components , 在右邊 Details 面板的 Shape 部分, 更改:
         Capsule Half Height: 96
         Capsule Radius: 16

     8. 點選 Camera Components , 將它拖拉到 Scene Components

     9. 點選 Scene Components , 在右邊 Details 面板的 Shape 部分, 更改:
         Transform > Location: 將 Z 值設為: -110
         , 然後編譯並儲存.

     10. 回到關卡, 點選 floor (Static Mesh), 將其 X, Y, Z 位置皆設為: 0,0,0

     11. 備註: 在 VR Preview 時, 如果覺得太接近或太遠離地板, 開啟 VR_Pawn Blueprint,
           點選 Scene Component, 調整 Z 的高度值.
-----------------------------------------------------------------------------------------------

D. VR Editor: (4.12)
    1. Edit > Editor Preferences...

    2. General > Experimental > VR:
        勾選: Enable VR Editing

    3. 結果: 多出一個啟用 VR Editor 的功能

-----------------------------------------------------------------------------------------------

E. 備註

     1. 將 GameMode 套用到整個專案
         > Level Editor > Editor > Project Settings...
         > Maps & Modes
         > Default Modes
         > Default GameMode: VR_GameMode

AudioKit for Swift

since: 2016/06/07
update: 2016/06/07
reference:
1. AudioKit - Powerful audio synthesis, processing, and analysis

A. 於專案中, 加入 AudioKit.framework 

    1. 接續上個專案: OSC for Swift
        到 AudioKit 下載 AudioKit-iOS-3.1.zip, 解壓縮後,  將 AudioKit.framework 拷貝到
        專案目錄裡(可以先建立 Frameworks 資料夾) 

    2. 在專案下新增 Frameworks 群組, 按下 Embedded Binaries 下的 "+"

    3. 瀏覽  AudioKit.framework 的檔案位置 > Open

    4. 點選 Create folder references > Finish

    5. 結果如下:

-----------------------------------------------------------------------------------------------

B. 專案加入 AudioKit 功能
    1. 修改 ViewController.swift 如下:
//
//  ViewController.swift
//  Hello_OSC
//
//  Created by LanliChen on 2016/6/7.
//  Copyright © 2016年 Lanli. All rights reserved.
//

import UIKit

//@add for AudioKit ############

import AudioKit

//class ViewController: UIViewController {
//@update for F53OSC ############
class ViewController: UIViewController, F53OSCPacketDestination {

    //@add for F53OSC ############
    var oscServer: F53OSCServer!
    var myAddressPattern = ""
    var myArguments = [AnyObject]()
   
    //@add for AudioKit ############

    var oscillator = AKOscillator()
   
    // @add for AudioKit ############
    func toggleSound() {
        if oscillator.isPlaying {
            oscillator.stop()
        } else {
            oscillator.amplitude = random(0.5, 1)
            oscillator.frequency = random(220, 880)
            oscillator.start()
        }
    }
   
    //@add for F53OSC ############
    func takeMessage(message: F53OSCMessage!) {
       
        print("Received '\(message)'")

       
        //@add for AudioKit ############
        toggleSound()
       
        //        let addressPattern = message.addressPattern
        //        let arguments = message.arguments
        myAddressPattern = message.addressPattern
        myArguments = message.arguments
        print("myAddressPattern = " + myAddressPattern)
        print(myArguments) // print(myArguments[0])
    }

   
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       
        //@add for F53OSC ############
        print("Starting OSC Server")
        oscServer = F53OSCServer.init()
        oscServer.port = 3030
        oscServer.delegate = self
        if oscServer.startListening() {
            print("Listening for messages on port \(oscServer.port)")
        } else {
            print("Error: Server was unable to start listening on port \(oscServer.port)")
        }

       
        //@add for AudioKit ############
        AudioKit.output = oscillator
        AudioKit.start()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

OSC for Swift

since: 2016/06/07
update: 2016/06/07
reference:
1. marsman12019/buzzOSC

A. 新增專案:
    1. Xcode > File > New > Project...

    2. iOS > Application > Single View Application > Next

    3. options for project:
        Product Name: Hello_OSC
        Organization Identifier: tw.blogspot.app4iot
        Language: Swift
        Device: iPhone
         > Next

    4. > 選擇專案目錄存放位置
        > 勾選: Create Git repository on Mac
        > Create

-----------------------------------------------------------------------------------------------

B. OSC Library
    1. 從 marsman12019/buzzOSC 下載 buzzOSC-master.zip,
        解壓縮後將整個 F53OSC 目錄複製到 Hello_OSC 專案目錄裡.
        (先新增一個 library 資料夾)

    2. 在專案裡, 新增二個階層群組: ResourcesF53OSC
        > F53OSC 滑鼠右鍵 > Add Files to "Hello_OSC" ...

    3. 選取專案的 F53OSC 目錄下的所有檔案目錄
        > Add

    4. Create Bridging Header

     5. add Header to Bridge to Swift
         > 點選 F53OSC 目錄下的 Hello_OSC-Bridging-Header.h 檔案
         > 新增內容:
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "F53OSC.h"


-----------------------------------------------------------------------------------------------

C. 新增 OSC 接收功能:

    1. 開啟 ViewController.swift 檔案, 修改如下:
//
//  ViewController.swift
//  Hello_OSC
//
//  Created by LanliChen on 2016/6/7.
//  Copyright © 2016年 Lanli. All rights reserved.
//


import UIKit

//class ViewController: UIViewController {
//@update for F53OSC ############

class ViewController: UIViewController, F53OSCPacketDestination {

    //@add for F53OSC ############
    var oscServer: F53OSCServer!
    var myAddressPattern = ""
    var myArguments = [AnyObject]()
   
    //@add for F53OSC ############
    func takeMessage(message: F53OSCMessage!) {
       
        print("Received '\(message)'")
       
        //        let addressPattern = message.addressPattern
        //        let arguments = message.arguments

        myAddressPattern = message.addressPattern
        myArguments = message.arguments
        print("myAddressPattern = " + myAddressPattern)
        print(myArguments) // print(myArguments[0])
    }
   
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       
        //@add for F53OSC ############
        print("Starting OSC Server")
        oscServer = F53OSCServer.init()
        oscServer.port = 3030
        oscServer.delegate = self
        if oscServer.startListening() {
            print("Listening for messages on port \(oscServer.port)")
        } else {
            print("Error: Server was unable to start listening on port \(oscServer.port)")
        }
       
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}


2. 修正錯誤:
    編譯時出現以下錯誤:
    .... iOS/Hello_OSC/library/F53OSC/GCDAsyncSocket.m:7928:75:
  
'kCFStreamNetworkServiceTypeVoIP' is deprecated: first deprecated in iOS 9.0 - use PushKit for VoIP control purposes


   修正方式:
   開啟: .... iOS/Hello_OSC/library/F53OSC/GCDAsyncSocket.m 檔案, 修改如下:

#import "GCDAsyncSocket.h"

//@add #############################################
#import <PushKit/PushKit.h>


- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat
{
    if (![self createReadAndWriteStream])
    {
        // Error occured creating streams (perhaps socket isn't open)
        return NO;
    }
   
    BOOL r1, r2;
   
    LogVerbose(@"Enabling backgrouding on socket");
   
    //r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
    //r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
    //@update ###############################################################################
    r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, PKPushTypeVoIP);
    r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, PKPushTypeVoIP);

   
    if (!r1 || !r2)
    {
        return NO;
    }
   
    if (!caveat)
    {
        if (![self openStreams])
        {
            return NO;
        }
    }
   
    return YES;
}


-----------------------------------------------------------------------------------------------

D. 編譯並執行:


2016年6月6日 星期一

VJ Combination: AirServer + Syphon + Modul8

since: 2016/06/06
update: 2016/06/06
reference:
1. AirServer - AirPlay, Mirroring and Miracast receiver for Mac OS X and Windows PC
2. Syphon

3. Modul8 VJ software

A. AirServer
    1. 於 Mac 上執行 AirServer 後, 可檢查 Preferences 設定

    2. 在 Mirroring 頁籤, 可以調整 Mirroring 畫面的傳輸品質.(品質越好, 越吃頻寬)

-----------------------------------------------------------------------------------------------

B. AirPlay
    1. 在 iPad2 上, 從底部往上滑, 點選 AirPlay:
        > xxx 的 MacBook Pro > 啟用鏡像輸出

    2. 此時在 Mac 上即可看到 iPad2 的鏡像

-----------------------------------------------------------------------------------------------

C. Syphon

    開啟 Syphon Client > 選擇來源為: iPad2 - AirServer

-----------------------------------------------------------------------------------------------

D. Modul8

    1. 開啟 Modul8 (需為 2.7 以上, 且是正式版)
        > Modules > Show > Syphon Input (layer)

    2. 在 out 中可看到經由 Syphon 輸出的畫面:

-----------------------------------------------------------------------------------------------

E. 備註:

     1. 如果 Modul8 為從 2.6 以前升級到 2.7 以上的,  
         在 Modules > Show > 找不到 Syphon Input (layer) 的話:

         方式一: 線上安裝


*************************************************************
  (建議)方式二: 拷貝 Syphon Input Modules 
         > 先到 Modul8 官網, 下載最新的 demo
         > 解壓縮掛載後, 把整個 Modul8Demo 資料夾拷貝出來,
         > 到 Modul8Demo 資料夾下的 Modules 資料夾裡
         > 將 Syphon Input (layer).m8m 檔案,
            拷貝到 Mac 上安裝  Modul8 的 Modules 目錄裡
            (一般為: 應用程式 > 某程式 > 顯示套件內容 > Contents > Modules)

2016年6月5日 星期日

Mac 無線網路診斷

since: 2016/06/05
update: 2016/06/05
reference:

A. 打開無線診斷

    1. 開啟 Wi-Fi 後, 按下鍵盤的 ALT 鍵不放 


    2. 點一下右上方的無線網路圖示(之後可以放開 ALT 鍵了)


    3. 點選 “打開無線診斷...”



-----------------------------------------------------------------------------------------------

B. 掃描
    1. 無須理會開啟的 “無線診斷” 視窗

    2. 點選左上角的 “視窗

    3. 點選 “掃描”



-----------------------------------------------------------------------------------------------

C. 立即掃描
     1. 按下右下方的 “立即掃描”


     2. 可以看到無線網路所使用的頻道
         (可視情況更改無線 AP 的頻道, 以確保無線傳輸品質)


2016年5月19日 星期四

Raspberry Pi: OSC for Python3

since: 2016/05/19
update: 2016/05/19
reference:
1. Osc4py3 documentation

A. 安裝 OSC for Python3
    $ sudo pip3 install osc4py3

-----------------------------------------------------------------------------------------------

B. OSC 接收端 (client)
     // osc_client.py

#!/usr/bin/python3

# Import needed modules from osc4py3
from osc4py3.as_eventloop import *
from osc4py3 import oscmethod as osm

print('start osc client....'+'\n')

def osc_receive_function(address, s, x, y):
    # Will receive message address, and message data flattened in s, x, y
    print("osc client receive:")
    print('address = '+address)
    print('s = ' + s)
    print('x = ' + str(x))
    print('y = ' + str(y))

# Start the system.
osc_startup()

# Make server channels to receive packets.
osc_udp_server("192.168.1.17", 2781, "aservername")

# Associate Python functions with message address patterns
osc_method("/test/*", osc_receive_function, argscheme=osm.OSCARG_ADDRESS + osm.OSCARG_DATAUNPACK)

# Periodically call osc4py3 processing method in your event loop.
finished = False
while not finished:
    # …
    osc_process()
    # …

# Properly close the system.
osc_terminate()


-----------------------------------------------------------------------------------------------

C. OSC 發送端 (server)
     // osc_server.py

#!/usr/bin/python3

# Import needed modules from osc4py3
from osc4py3.as_eventloop import *
from osc4py3 import oscbuildparse

print('start osc server....'+ '\n')
# Start the system.
osc_startup()

# Make client channels to send packets.
osc_udp_client("192.168.1.17", 2781, "aclientname")

# Build a simple message and send it.
s = 'text'
x = 672
y = 8.871
address = '/test/me'

msg = oscbuildparse.OSCMessage(address, ",sif", [s, x, y])
osc_send(msg, "aclientname")

print("osc server send:")
print('address = ' + address)
print('s = ' + s)
print('x = ' + str(x))
print('y = ' + str(y))

# Periodically call osc4py3 processing method in your event loop.
finished = False
while not finished:
    # You can send OSC messages from your event loop too…
    # …

    osc_process()
    # …
    finished = True # finish the process

# Properly close the system.
osc_terminate()

-----------------------------------------------------------------------------------------------

D. 測試:
     1. 啟動 OSC client:
         $ ./osc_client.py
         start osc client....

     2. 啟動 OSC server:
         $ ./osc_server.py
         start osc server....

         osc server send:
         address = /test/me
         s = text
         x = 672
         y = 8.871


     3. 查看 OSC client:
         start osc client....

         osc client receive:
         address = /test/me
         s = text
         x = 672
         y = 8.871000289916992

2016年5月8日 星期日

Raspberry Pi: Real-Time Face Detection With Camera Module

since: 2016/05/08
update: 2016/05/08
reference:
1. openframeworks
2. I touchs: openFrameworks: Control The Raspberry Pi Camera Module

A. 功能測試: opencvHaarFinderExample
    // 單張照片臉部辨識
    $ cd /home/pi/of_v0.9.3/examples/addons/opencvHaarFinderExample
    $ make
    $ ./bin/opencvHaarFinderExample    // 結果


-----------------------------------------------------------------------------------------------
B. 功能測試: example-texture-mode
     // Camera Module
     $ cd /home/pi/of_v0.9.3/addons/
     $ git clone https://github.com/jvcleave/ofxRPiCameraVideoGrabber
     $ cd /home/pi/of_v0.9.3/addons/ofxRPiCameraVideoGrabber/example-texture-mode
     $ make
     $ ./bin/example-texture-mode

-----------------------------------------------------------------------------------------------

C. Face Detection With Camera Module
     1. addons.make
         ofxOpenCv
         ofxRPiCameraVideoGrabber

***********************************************

     2. main.cpp
#include "ofMain.h"
#include "ofApp.h"
#include "ofGLProgrammableRenderer.h"

int main()
{
    ofSetLogLevel(OF_LOG_VERBOSE);
   
    // read config file
    vector<string> _linesOfTheFile;
    ofBuffer _buffer = ofBufferFromFile("config.txt");
    for (auto line : _buffer.getLines()){
        _linesOfTheFile.push_back(line);
    }
   
    // local variables
    int _debug = ofToBool(_linesOfTheFile[1]);
    int _camWidth = ofToInt(_linesOfTheFile[4]);
    int _camHeight = ofToInt(_linesOfTheFile[7]);
    bool _fullScreen = ofToBool(_linesOfTheFile[10]);
   
    // show debug message
    if(_debug) {
        ofLog() << "****** main()";
        ofLog() << "debug: " << _debug;
        ofLog() << "OpenGL width: " << _camWidth;
        ofLog() << "OpenGL height: " << _camHeight;
        ofLog() << "fullScreen: " << _fullScreen;
    }
   
    ofGLESWindowSettings settings;
    //settings.width = 320;
    //settings.height = 240;

    settings.width = _camWidth;
    settings.height = _camHeight;
   
    settings.setGLESVersion(2);
    ofCreateWindow(settings);
   
    ofRunApp( new ofApp());
}


***********************************************

     3. ofApp.h
#pragma once

#include "ofMain.h"
#include "TerminalListener.h"
#include "RPiVideoGrabber.h"
#include "ofxCvHaarFinder.h"

class ofApp : public ofBaseApp, public KeyListener{

    public:

        void setup();
        void update();
        void draw();
        void keyPressed(int key);

    void onCharacterReceived(KeyListenerEventData& e);
    TerminalListener consoleListener;
   
    //wrapper class for drop-in replacement of ofVideoGrabber
    RPiVideoGrabber vidGrabber;
   
    //@add for CvHaarFinder
    ofxCvHaarFinder finder;
    ofImage grayImg;
    //ofImage img;

    //@add second record
    float countROI;
    long readSeconds; // read from totalSeconds record file
    long writeSeconds; // write to totalSeconds record file
    long latestSeconds; // latestSeconds
   
    // config file
    bool debug;
    int camWidth;
    int camHeight;
    bool fullScreen;
    int frameRate;
    bool concurrentVisit;
    float minROI;
    float maxROI;
    float weightROI;
    string recordFile;
};


***********************************************

    4. ofApp.cpp
#include "ofApp.h"

//--------------------------------------------------------------

void ofApp::setup()
{
    ofSetLogLevel(OF_LOG_VERBOSE);
   
    // read config file
    vector<string> linesOfTheFile;
    ofBuffer buffer = ofBufferFromFile("config.txt");
    for (auto line : buffer.getLines()){
        linesOfTheFile.push_back(line);
    }
    //for (int i = 0; i < linesOfTheFile.size(); i++) {
    //    ofLog(OF_LOG_VERBOSE, "cur.width = %s", linesOfTheFile[i].c_str());
    //}

   
    debug = ofToBool(linesOfTheFile[1]);
    camWidth = ofToInt(linesOfTheFile[4]);
    camHeight = ofToInt(linesOfTheFile[7]);
    fullScreen = ofToBool(linesOfTheFile[10]);
    frameRate = ofToInt(linesOfTheFile[13]);
    concurrentVisit = ofToBool(linesOfTheFile[16]);
    minROI = ofToFloat(linesOfTheFile[19]);
    maxROI = ofToFloat(linesOfTheFile[22]);
    weightROI = ofToFloat(linesOfTheFile[25]);
    recordFile = linesOfTheFile[28];
   
    if(debug)
    {
        ofLog() << "****** ofApp::setup()";
        ofLog() << "debug: " << debug;
        ofLog() << "camWidth: " << camWidth;
        ofLog() << "camHeight: " << camHeight;
        ofLog() << "fullScreen: " << fullScreen;
        ofLog() << "frameRate: " << frameRate;
        ofLog() << "concurrentVisit: " << concurrentVisit;
        ofLog() << "minROI: " << minROI;
        ofLog() << "maxROI: " << maxROI;
        ofLog() << "weightROI: " << weightROI;
        ofLog() << "recordFile: " << recordFile;
    }
   
    // read totalSeconds record file
    vector<string> totalSecondsFile;
    ofBuffer sbuffer = ofBufferFromFile(recordFile);
    for (auto line : sbuffer.getLines()){
        totalSecondsFile.push_back(line);
    }
   
    readSeconds = ofToFloat(totalSecondsFile[0]);
   
    if(debug)
    {
        ofLog() << "****** read totalSeconds record file";
        ofLog() << "readSeconds: " << readSeconds;
    }
   
    //ofSetFrameRate(5);
    ofSetFrameRate(frameRate);
   
    //allows keys to be entered via terminal remotely (ssh)
    consoleListener.setup(this);
   
    //vidGrabber.setDesiredFrameRate(30);
    vidGrabber.setDesiredFrameRate(frameRate);
    vidGrabber.initGrabber(camWidth, camHeight);
   
    //@add for CvHaarFinder
    //img.load("test.jpg");

    finder.setup("haarcascade_frontalface_default.xml");
    //finder.findHaarObjects(img);
}

//--------------------------------------------------------------
void ofApp::update()
{
    ofBackground(100, 100, 100);
    vidGrabber.update();
   
    if(vidGrabber.isFrameNew())
    {
        //ofPixels & pixels = vidGrabber.getPixels();
        //finder.findHaarObjects(pixels);

        grayImg.setFromPixels(vidGrabber.getPixels());
        grayImg.setImageType(OF_IMAGE_GRAYSCALE);

       
        finder.findHaarObjects(grayImg);
    }
}


//--------------------------------------------------------------
void ofApp::draw(){

    ofSetHexColor(0xffffff);
    //ofSetColor(ofColor::white);
   
    vidGrabber.draw(0, 0);
   
    //@add for CvHaarFinder
    //img.draw(0, 0);

    ofNoFill();
   
    for(unsigned int i = 0; i < finder.blobs.size(); i++) {
       
        ofRectangle cur = finder.blobs[i].boundingRect;
       
        //if(debug){
        //    ofLog(OF_LOG_VERBOSE, "ROI width = %f", cur.width);
        //    ofLog(OF_LOG_VERBOSE, "ROI height = %f", cur.height);
        //}

       
        //if((cur.width >= 50.0 && cur.width <= 150.0) &&
        //   (cur.height >= 50.0 && cur.height <= 150.0)) {

        if((cur.width >= (minROI * camWidth) && cur.width <= (maxROI * camWidth)) &&
           (cur.height >= (minROI * camWidth) && cur.height <= (maxROI * camWidth))) {
            countROI++;
            ofSetColor(255, 0, 0); // red color
            ofDrawRectangle(cur.x, cur.y, cur.width, cur.height);
           
            if(debug) {
                ofSetColor(ofColor::yellow);
                ofDrawBitmapString("ROI = " + ofToString(cur.width) + " x " + ofToString(cur.height), camWidth/5, camHeight/1.02);
            }
            // not concurrent Visit
            if(!concurrentVisit){
                break;
            }
        }
        else {
            ofSetColor(0, 0, 255); // blue color
            ofDrawRectangle(cur.x, cur.y, cur.width, cur.height);
        }
    }
   
    // writeSeconds
    writeSeconds = readSeconds + (countROI / frameRate * weightROI); // weightROI: for calibrate detect missing
   

    if(writeSeconds > latestSeconds){
        // Write data
        if(debug) {
            // totalSeconds record file
            ofLog() << "****** write totalSeconds record file: " << writeSeconds;
        }
       
        ofBuffer secondsBuff;
        secondsBuff.set(ofToString(writeSeconds));
        bool fileWritten = ofBufferToFile(recordFile, secondsBuff);
       
        latestSeconds = writeSeconds;
    }
   
    if(debug){
        // show message
        ofSetColor(ofColor::white);
        ofDrawBitmapString("total seconds: " + ofToString(writeSeconds), camWidth/5, camHeight/1.2);
        ofDrawBitmapString(ofToString(minROI * camWidth) + " <= ROI <= " + ofToString(maxROI * camWidth), camWidth/5, camHeight/1.1);
    }
   
    ofSetColor(100, 100, 100);
}

//--------------------------------------------------------------
void ofApp::keyPressed  (int key)
{
    ofLog(OF_LOG_VERBOSE, "%c keyPressed", key);
    //if (key == 'e')
    //{
    //}
    //@add for VideoGrabber

    if(key == 'd' || key == 'D'){
        debug = !debug;
    }
}

void ofApp::onCharacterReceived(KeyListenerEventData& e)
{
    keyPressed((int)e.character);
}


***********************************************

   5. bin/data 資料夾
        a. haarcascade_frontalface_default.xml // from ofxOpenCv

        b. totalSeconds.txt // record second
            0

        c. config.txt // config file
# debug (true/false)
false

# cam width (int)
320

# cam height (int)
240

# full screen (true/false)
false

# frame rate (int)
5

# concurrent visit (true/false)

false

# minimum width ratio of ROI(region of interest) (float)
0.125

# maximum width ratio of ROI(region of interest) (float)
0.5

# weight of ROI (float)
1.0

# recordFile (string)
totalSeconds.txt