2018年8月8日 星期三

Sita 的即時翻譯功能


外遊在即,雖然我的日語尚算靈光,但老人家少不免會一時忘記句子說法;為了解決這個問題,於是花了一小時教懂了私人助理 Sita 多國語言。在有需要時可以隨時請 Sita 幫忙。

相信大家能估到背後使用了 Google API;不過,不是收費或需要信用卡資料的 Google Cloud API。重點是免費。原來 Google 有一條免費的 API 可供隨時使用。不過,在開發時遇到小問題;當要由中文轉成英文,或日文轉換成英文時,結果卻是爛字。花了好一會時間,才發現需要加入 User Agent 去解決。以下是相關 PHP 程式碼:
//----------------------------------------------------------------------------------------
//  Translation
//  zh-tw: = Colon is at position 5
$count = substr_count($incomingMessage, ":");
if ($count >= 2)  {

 $array = explode(":", $incomingMessage);
 $count = count($array);
 if ($count >= 3)  {

  $sourceLanguage = $array[0];
  $targetLanguage = $array[1];

  $position = strlen($sourceLanguage)+1+strlen($targetLanguage)+1;
  $text = mb_substr($incomingMessage, $position);

  $url = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=".$sourceLanguage."&tl=".$targetLanguage."&dt=t&q=".urlencode($text);

  $curl = curl_init();
  curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
  curl_setopt($curl, CURLOPT_URL, $url);
  curl_setopt($curl, CURLOPT_TIMEOUT, 10);
  curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  $response = curl_exec($curl);
  curl_close($curl);

  if ($response != null)  {

   $json = json_decode($response);
   $translatedText = $json[0][0][0];
   $_lineBot->sendText($replyToken, $translatedText." ($text)");
   break;

  }  else  {
   $_lineBot->sendText($replyToken, "Translation API return null...");
   break;
  }
 }  else  {
  $_lineBot->sendText($replyToken, "We need 3 translation parameters, but only $count now...");
  break;
 }
}

2018年6月23日 星期六

機器學習基礎.第三課


機器學習基礎」來到第三課,亦是最後一課。承接首兩堂的系統設定、Python 編程、數據收集,今天教會學生如何訓練模型去分辨樣貌。我們即場拍了數十張相片,有學生也有老師;把相片輸入到系統並進行訓練。成功令機器分辨出各人的樣貌。希望學生回家能導入不同的相片,訓練模型去處理更多的分類工作,繼續探索及研究。

在此,多謝東華三院張明添中學的梁老師及東華三院甲寅年總理中學的溫副校的信任與支持!我才有機會向同學分享機器學習的知識,課程才得以順利完成。

2018年6月11日 星期一

收集股票詳細交易資料


今日放工時,跟同事談起收集到的世界盃數據,他希望用相同方法收集股票數據。對我來說,自從上年五月 Yahoo 停止了 API 運作後,已經好一段時間沒有收集到數據;雖然兩三個月前重新開始,而且也能順利收集;不過,今次卻是拿 Yahoo 沒提供的詳細交易資料,於是我接下這個案件。急不及待地在晚上研究取得數據的方法,編寫自動化程式,正式開始收集數據。下一步是利用這些數據及股價升跌去尋找買入賣出機會。

2018年6月10日 星期日

自動下載抖音影片


女兒愛玩抖音,拍了一些短片。我希望能把它保存起來,於是用 Charles Proxy 研究了一下當中的通訊內容;然後寫了下面的 PHP 程式,配合 crontab 定時執行。只要女兒有公開的影片,程式便會自動下載。
<?php
//----------------------------------------------------------------------------------------
//  Douyin Video Downloader
//----------------------------------------------------------------------------------------
//  Platform: CentOS 7 + PHP 5
//  Written by Pacess HO
//  Copyright Pacess Studio, 2018.  All rights reserved.
//----------------------------------------------------------------------------------------

//  Global variables
$_downloadDirectory = "./__files__/";

$_readLiveList = true;
$_responseFile = "dorothy.json";

$_userID = "6519251381372489395";
$_url = "https://api.tiktokv.com/aweme/v1/aweme/post/?version_code=2.2.1&language=ja"+
        "&app_name=trill&vid=18DD92EA-42E1-848F-4BA4-CDFAB132F79D&app_version=2.2.1"+
        "&carrier_region=HK&is_my_cn=1&channel=App%20Store&mcc_mnc=45406"+
        "&device_id=6539510560658635543&tz_offset=28800&account_region=HK&sys_region=HK"+
        "&aid=1180&screen_width=640&openudid=92046a2f432be94bec4d1b7369d754ab57259918"+
        "&os_api=18&ac=WIFI&os_version=11.4&app_language=ja&tz_name=Asia/Hong_Kong"+
        "&device_platform=iphone&build_number=22102&device_type=iPhone8,4"+
        "&iid=6564579886684473090&idfa=CE02FD8A-E2BA-44B5-884B-C03EBAFC413B"+
        "&count=21&max_cursor=0&min_cursor=0&user_id=$_userID"+
        "&mas=035160f738fae1dd802eef2d7ae9b80526a7d06a0300db4d1378d8&as=".time();

//========================================================================================
//  Main program
$response = null;
if ($_readLiveList == true)  {

   //  Get response from server
   $curl = curl_init($_url);
   curl_setopt($curl, CURLOPT_HEADER, 0);
   curl_setopt($curl, CURLOPT_TIMEOUT, 30);
   curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
   $response = curl_exec($curl);
   curl_close($curl);

   file_put_contents($_responseFile, $response);

}  else  {

   //  Get response from file
   $response = file_get_contents($_responseFile);
}

//----------------------------------------------------------------------------------------
$json = json_decode($response, true);

if (isset($json["aweme_list"]) == false)  {
   echo("### Aweme list not found...\n");
   exit(-1);
}

$awemeArray = $json["aweme_list"];
$videoCount = count($awemeArray);
echo("$videoCount videos found...\n");
foreach ($awemeArray as $aweme)  {

   //  Get video create time, use this value for filename
   if (isset($aweme["create_time"]) == false)  {
      echo("### Create time not found...\n");
      continue;
   }
   $createTime = $aweme["create_time"];

   //  Check if video already exists
   $filename = $_userID."_".$createTime.".mov";
   $filePath = $_downloadDirectory.$filename;

   $exists = file_exists($filePath);
   if ($exists == true)  {
      echo("Skip video [$filename]...");
      continue;
   }

   //----------------------------------------------------------------------------------------
   //  Video not exists, check for video URL
   if (isset($aweme["video"]) == false)  {
      echo("### Video not found...\n");
      continue;
   }

   $videoDictionary = $aweme["video"];
   if (isset($videoDictionary["play_addr"]) == false)  {
      echo("### Video address not found...\n");
      continue;
   }

   $videoAddressArray = $videoDictionary["play_addr"]["url_list"];
   $count = count($videoAddressArray);
   $index = 0;
   if ($index >= $count || $index < 0)  {
      echo("### Invalid address index #$index...\n");
      continue;
   }

   $videoAddress = $videoAddressArray[$index];
   echo("Downloading [$videoAddress]...");

   $curl = curl_init($videoAddress);
   curl_setopt($curl, CURLOPT_HEADER, false);
   curl_setopt($curl, CURLOPT_TIMEOUT, 30);
   curl_setopt($curl, CURLOPT_BINARYTRANSFER, true);
   curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
   curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
   $response = curl_exec($curl);
   curl_close($curl);

   //  The content is share information only, need to download video with URL inside
   $count = file_put_contents($filePath, $response);
   echo("$count saved.\n");
}

?>

2018年6月9日 星期六

Donkeycar 的 Tensorflow 錯誤解決方法


既然 Raspberry Pi 3 Model B+ 無法使用 Donkeycar 的官方影像,就只好自行安裝。要在 NOOBS 成功安裝 Donkeycar 所須配件,網友 Dennis 指示以 https://github.com/wroscoe/donkey/blob/bd311a19acdc36b198882d3afbe9a023673ca007/install/make_pi_disk_img.sh 內的做法。花了數小時安裝,在啟動 Donkeycar 時,調用 Tensorflow 出現錯誤。嘗試了不同方法,最後發現轉用最新 Tensorflow 1.8.0 能解決問題。以下是我的安裝步驟:
##----------------------------------------------------------------------------------------
##  Donkey installation script for NOOBS.
##  Original version by wroscoe
##  Modified version by Pacess HO, 2018.
##----------------------------------------------------------------------------------------

##  Manually make sure the camera and I2C are enabled
sudo raspi-config

##  Standard updates (5 min)
sudo apt update -y
sudo apt upgrade -y
sudo rpi-update -y

##  Helpful libraries (2 min)
sudo apt install build-essential python3-dev python3-distlib python3-setuptools python3-pip python3-wheel -y
sudo apt install libzmq-dev -y
sudo apt install xsel xclip -y

##  Install numpy which is needed for OpenCV
pip3 install pandas
pip3 install h5py==2.8.0rc1

##  Install numpy and pandas (3 min)
sudo apt install libxml2-dev python3-lxml -y
sudo apt install libxslt-dev -y

##  Create a python virtualenv (2 min)
sudo apt install virtualenv -y
virtualenv donkey --system-site-packages --python python3
echo '##  Start donkeycar envoronment' >> ~/.bashrc
echo 'source ~/donkey/bin/activate' >> ~/.bashrc
source ~/.bashrc

##  Install redis-server (1 min)
sudo apt install redis-server

##  Install OpenCV (1 hour)
sudo apt-get install build-essential git cmake pkg-config -y
sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev -y
sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev -y
sudo apt-get install libxvidcore-dev libx264-dev -y
sudo apt-get install libatlas-base-dev gfortran -y

##  NOTE: this gets the dev version. Use tags to get specific version
git clone https://github.com/opencv/opencv.git --depth 1
git clone https://github.com/opencv/opencv_contrib.git --depth 1

cd ~/opencv
mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE \
  -D CMAKE_INSTALL_PREFIX=/usr/local \
  -D INSTALL_C_EXAMPLES=OFF \
  -D INSTALL_PYTHON_EXAMPLES=OFF \
  -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
  -D BUILD_EXAMPLES=OFF ..
make -j4
sudo make install
sudo ldconfig

##  Install tensorflow (5 min)
tf_file=tensorflow-1.8.0-cp35-none-linux_armv7l.whl
wget https://github.com/lhelontra/tensorflow-on-arm/releases/download/v1.8.0/${tf_file}
pip install ${tf_file}

##  Install donkey (1 min)
git clone https://github.com/wroscoe/donkey.git donkeycar
pip install -e donkeycar/[pi]

2018年6月8日 星期五

Raspberry Pi 多連線設定


3 月 23 日購買了一塊 Raspberry Pi 3 Model B+。經過兩個月的手續,在 5 月 21 日取得香港的入口簽証。前晚收到過數通知,到昨天收到由新加坡寄來的板子。

我拿了在 Raspberry Pi 2 開發的 Donkeycar SD 卡放到 Pi 3,竟然啟動不了。紅燈閃爍,畫面停在彩虹魔方。


我重灌 Donkeycar 影像,同樣不行。於是,只好安裝 NOOBS 到 SD 卡,今次卻成功了。證明了 Donkeycar 官方網站的 donkey_v22.img 影像檔是不對應 Pi 3 Model B+(Pi 3 Model B 是沒問題)。由於我希望 Raspberry Pi 能自動進行 WiFi 連線,而應用的場景有三個:
1. 公司
2. 家中
3. 比賽

三個場景所提供的 WiFi 都不一樣,要讓 Raspberry Pi 能有多個連線設定,只要修改 /etc/wpa_supplicant/wpa_supplicant.conf,把它加入數組連線設定就可以:

2018年6月7日 星期四

為 WDA 加入新指令

機緣巧合之下,找到了一個有趣的程式。它利用 Facebook 的 WebDriverAgent 及 Python 程序來控制手機內的《抖音》應用程式,利用臉部識別去找出顏值高於 80 的美女,並進行關注。吸引到我的地方不是美女,而是控制 iOS 的方法,亦即是 WDA。

過往也曾試過分析 iPhone 的遊戲畫面,開發計算出最好步數的指導器;但當時卻沒能把結果傳回 iPhone。一直希望能得到解決。現在 WDA 有望能達到這樣的效果。我嘗試用 WDA 去玩一個遊戲,可是當中需要快速連擊,而 WDA 本身沒有支援,於是動手修改一下 ~/anaconda3/lib/python3.6/site-packages/wda/__init__.py:
    ##  2018.06.07 Pacess
    def sequenceTap(self, list):
        return self.http.post('/wda/sequenceTap', dict(taps=list))
    ##  2018.06.07 End

在 FBElementCommands.m 的 + (NSArray *)routes 加入:
[[FBRoute POST:@"/wda/sequenceTap/"] respondWithTarget:self action:@selector(handleSequenceTap:)],

及加入新的函數:
//  Sequence-tap by Pacess
+ (id<FBResponsePayload>)handleSequenceTap:(FBRouteRequest *)request  {
   CGPoint tapPoint = CGPointZero;
   NSArray *tapArray = (NSArray *)request.arguments[@"taps"];
   NSInteger count = [tapArray count];
   for (int i=0; i<count; i+=2)  {

      CGFloat x = [[tapArray objectAtIndex:i] doubleValue];
      CGFloat y = [[tapArray objectAtIndex:i+1] doubleValue];
      tapPoint = CGPointMake(x, y);
      XCUICoordinate *tapCoordinate = [self.class gestureCoordinateWithCoordinate:tapPoint application:request.session.application shouldApplyOrientationWorkaround:isSDKVersionLessThan(@"11.0")];
      [tapCoordinate tap];
   }
   return FBResponseWithOK();
}

2018年6月6日 星期三

世界盃賠率走勢


為了提高學習 Machine Learning 的興趣,我提議同事收集世界盃球隊資料,尋找用得著的機器學習算法,去估算最有機會勝出的隊伍,然後根據結果進行投注。如果能選中勝出隊伍,大家又有點小收穫,相信會有動力;若然不想輸,就得想盡方法去增加勝算,技術也因此得到提升。不過,說說當然是容易,收集甚麼數據、如何收集、怎樣處理,到目前還是沒有頭緒...。然而,我提出了一個與機器學習無關,反而用了逆向思維的方法卻得到大家的認同。現在,賠率起了變化,也差不多是時候入市了。希望能帶來一點收穫!

2018年6月2日 星期六

Donkey car 一號


今天出席了香港自動駕駛模型協會的聚會,看看大家的 Donkey car 之餘,也了解一下實際運行時遇到的問題,及不同的改裝。

我的車子還沒完成,所以沒有帶來比試。目前大家的 Donkey car 都是按照官方版本製作,只有 3D 打印的部份用了不同顏色,他們都想進入下一階段,製作有個人風格的車子。我也是不想一式一樣而自行改裝,難度是有的,但結果將會是好玩的。其中一樣討論過的事情,是希望成功用積木砌成 Donkey car,跟 mbot 比試。原本貪拖頭車的空間較大,想優先把它開發成 Donkey car 二號,但需要花很多功夫去改裝;以及車長的關係,轉彎會不夠靈活。於是繼續裝嵌 Donkey car 一號。同時加入了超聲波模組及鏡頭模組的固定支架。目前,只欠 Raspberry Pi 3 Model B+ 來到,及加入自訂的 GPIO PWM 控作,希望能在一個月內完成!

2018年6月1日 星期五

Donkey car 二號


原本買了一套樂拼遙控跑車作為 Donkey car 的車架。在拼砌到一半後,發現車子有點小,無法容得下所有電子零件;於是購買另一台卡車。


卡車早幾到已運到,今晚趕工把它完成。我自行改裝一下,加入了伺服馬達,把原本手動的轉向改為馬達控制;同時也放入鏡頭及 Raspberry Pi 運殻。一切看似順利,零件也容得下,外觀也很趣緻。不過...


卻發現模型的馬達只負責升降台,而沒有前進動力... (T_T);可能零件多了,車頭也較為貼地,容易卡住。我是用來當遙控車,得自行大改才能達成...。反正今晚夜了,明日再想。