2016年5月29日 星期日

為滑板車加裝運動相機


家中的小蟻運動相機很少用,想到了把它安裝到女兒的滑板車,拍攝風馳電掣的影片。為了提升速度感,相機放到盡量的低,於是設計出扶桿的部件及相機籃部件。


兩個部件用螺絲緊扣,方便隨意調校角度。而接口位參考了潛水相機殻的做法,效果不錯。打印好後位置剛剛好,不需要再作加工。


從小蟻的應用程式中預覽到拍攝出來的畫面,還是想把它放得再低,提升賽車感覺。於是設計出一條臂,放在相機及柱環中間。出來的外觀也不錯。不過,到真際試車時,發現相機會因為關節的槓桿情況而慢慢下垂,特別是在凹凸不平的路上奔馳時。於是,只好把手臂拿掉,換回原來的設計...。

2016年5月25日 星期三

焊接 AMIGO Arm 母板


懶惰了一段時間,是時候繼續完成 AMIGO Arm。之前預見很多困難需要處理,而且是花時間花錢的,所以一直拿不起勁。例如:伺服馬達單純用 5V USB 充電器來供電時,電力要夠才能驅動馬達,不過電壓還是太弱。得用上鋰電池或電源供應器。鋰電池玩不久便要充電,而且是 7.4V,但馬達是 4.8V-6V,差 1.4V 會令馬達壽命縮短...,對於開發進行其間十分不便。因此,只能用上電源供應器。可是這個裝置被收埋一角,拿出來又是一番功夫,還得找地方好好安置而不會被家中三個女王因不小心而把家園摧毀。


簡單地焊接好母板後,打算修改一下輸出埠便做測試。誰知,忘記了 PWM 輸出功能 analogWrite() 只支援 3, 5, 6, 9, 10 及 11 埠...。我隨手揀選的 2, 4, 6, 8 就像買六合彩一樣,只選中了一個...。既然如此,還是實測一下,看看 2, 4, 8 有沒有機會支援 analogWrite()。結果是 2, 4, 6 有反應,但要達到要求的話,還是只有 6 被支援。哎...得修改母板了...。

2016年5月23日 星期一

苦瓜苗的 48 小時


上週日,媽媽給我苦瓜苗嘗試栽種。一週時間已過,有三棵苦瓜苗已經長得有四厘米高。有一天,我發現其中一棵長得特別快,不到四小時已經長高了,而且是肉眼察覺得到的高;於我我設下縮時攝影,看看 48 小時會長成甚麼樣。

2016年5月22日 星期日

為滑板車加裝電池外殻


早前替大女兒的滑板車組裝底燈,當時已經買定二女的份兒;可是一直都找不到能加裝在滑板車底部及帶開關的電池,所以一直沒有動工。後來在淘寶找到了但出於性價比及運費的考慮,最終都沒有購買。既然此路不通,那就改用性價比高的小米電源。


小米電源較原本用的一芯電大出一倍多,得想方法安全地放置電池;畢竟小朋友跑跑跳跳,萬一撞到電池便會發生危險。所以在大女兒的滑板車是用容量較少的電池,就算爆也不會有太大的威力。我只想到把電池掛在扶桿上。構思好外形後,在 Inventor 中把它畫出來。當中要考慮穿進去及掛上時的空間是否足夠,也要預計打印時沒支撐能否順利進行。得出來的設計是三文治式的電池殼。為了加強保護,殻的厚度設定為 4 毫米。


打印完成後立即進行安裝,並找二女實地進行試車。發現電池有被拋出的情況。這是由於電池殼預留的空間太多,同時也發現 USB 線沒有能扣上的地。修改後再次打印,問題得到解決。

2016年5月17日 星期二

把 OpenGL 畫面轉換為 UIImage


在開發拍照畫面時,又遇到另一個問題。鏡頭圖案數據會綁定到 OpenGL 的介面來顯示,當點擊「拍照」按鈕後,程式會把本來從 AVCaptureVideoDataOutput 取得相片的數據,轉為經 AVCaptureStillImageOutput 取得;再進行 CIFilter 加工,得出結果圖案。可是拍出來的照片在加工後變了樣。起初以為是數據來源的問題,於是修改了程式,把兩個來源的數據都儲存起來。打開來看,發現除了解像度的分別外,沒有其他。後來又想到會不會是 OpenGL 的繪畫問題?於是嘗試從 OpenGL 中抓下畫面:
   CGFloat width = _previewView.frame.size.width;
   CGFloat height = _previewView.frame.size.height;
   NSInteger length = 4*width*height;

   GLubyte *buffer = (GLubyte *)malloc(length);
   glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

   GLubyte *buffer2 = (GLubyte *)malloc(length);
   for (int y=0; y<height; y++)  {
      for (int x=0; x<height*4; x++)  {
         int offset = ((4*width)*y)+x;
         int offset2 = (height-1-y)*width*4+x;
         buffer2[offset2] = buffer[offset];
      }
   }

   int bitsPerComponent = 8;
   int bitsPerPixel = 32;
   int bytesPerRow = 4*width;
   CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
   CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
   CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;

   CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer2, length, NULL);
   CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);

   UIImage *image = [UIImage imageWithCGImage:imageRef];
這個方法無法解決問題。看來在 OpenGL 用的喧染方法 (GL_ONE, GL_ONE_MINUS_SRC_ALPHA) 是正常。這樣的話,剩下的可能性會是 CIImage 及 UIImage 的問題...。

2016年5月16日 星期一

創業家的心態


今天在 Facebook 看到這條影片的分享。Steve Jobs 說的很有道理。

2016年5月15日 星期日

解決 CIImage 無法儲存的問題

拼圖畫面已經完成,接著便是拍照畫面的開發工作。當中遇到一個問題,就是從 AVCaptureVideoDataOutput 得來的 CIImage 在轉換成 UIImage 後無法儲存,十分奇怪。圖案明明能顯示到 UIImageView,圖案尺寸正常,理應能順利經 [UIImageJPEGRepresentation(image, 0.9f) writeToFile:filePath atomically:YES] 來儲存。可是實情卻不。找了很久都沒有相關解決方案,於是自行用以下方法處理:
   UIImage *originalImage = [UIImage imageWithCIImage:_originalImage];
   CGSize size = originalImage.size;
   CGFloat scaleFactor = [[UIScreen mainScreen] scale];

   UIGraphicsBeginImageContextWithOptions(size, NO, scaleFactor);
   [originalImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
   UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();

   BOOL result = [UIImageJPEGRepresentation(image, 0.9f) writeToFile:@"image.jpg" atomically:YES];

2016年5月14日 星期六

陳僖儀 Wallpaper(二)


四堂的《Android Studio》課程已經完結,學費也袋袋平安;今日不用上堂,卻想起在最後一堂中,一名女同學向我的提問:「阿 Sir,我地好想知你電腦 Wallpaper 的女仔係邊個?」。我答:「陳僖儀」。同學立即說:「哦,真係佢!不過兩年前已經走咗...」;我回答說:「嗯,已經三年了」。她說:「噢...原來已經三年了...」。從這樣的對答,我知道「陳僖儀」還留在有不少中學生的心中。

2016年5月13日 星期五

次元空間

晚上在學習 Machine Learning: Regression 時,經常看到圖表,慢慢地跌進空間的思考領域。


我在想:如果二次元空間是平面,那麼一次元空間是線,還是點?應該是線吧?那麼點是零次元空間?從這個網頁得到答案。我的想法是正確。那麼三次元空間再上一層的四次元空間,軸心是時間,還是引力?應該是時間吧?如果時間是四次元空間的軸心,那麼時間是有可能逆行,為何所有人的時間都是順行?難道就像星體般因引力影響?所以在引力大的情況下時間會行得更快。意味著引力是第五次元的軸心?但感覺上好像有不妥。如果四次元空間的軸心是引力,把三維的物件拉伸出時間軸,同樣也有可能。大家在同一引力下,時間也是一樣地前進。如果是這樣想的話,時間只是引力的副產品,只不過是一個概念而已。若能改變引力,便能回到過去,或是飛越未來。似乎這個想法較為像真。


再想下去,要是把三次元空間當成零次元空間看,並按照零至三次元的發展,就可以引伸出我們第處的四次元,以及抽象的五次元及六次元空間。可惜我被四次元空間限制住,無法想到之後的次元為何物。

想到頭都大了。看來我想得太多。還是繼續進修。

2016年5月12日 星期四

立刻開工


經過反覆打印失敗後,終於印成了細碼「立刻」二字,希望 Mark 哥喜歡。在 5 月 13-14 號,他的「立刻開工」會在香港會議展覽中心 1D 展廳的 D19 展出。

使用了 3D 打印機都有兩年時間,到目前為止,我認為有很多地方改善才能普及。就像我今次的一連串打印,遇到:由平面文未建立模型的困難、打印模型軌跡的運算矛盾、軸心錯轉問題、打印物料卡著、成品角位收縮問題、打印時拉出的細絲問題、打印時跳點的洩漏問題...等。都需要花時間及心機去處理,甚至要用上較為專業的知識去解決。對於普羅大眾想像平面打印機般簡單打印,還存在很大的距離。可能我的 ArrayZ 不是大品牌,只是手作的產品。但我聽過 MakerBot 用家也遇到問題,只是問題少一半,同時也有遙距技術支援。希望這些問題能得到解決,我很想簡簡單單、安安心心地打印。

2016年5月11日 星期三

拼圖畫面


這幾天忙於把從相片清單畫面中選擇好的相片傳到去拼圖畫面。比我想像中花得多了時間。特別是要取得相片原大圖,現時需要透過 PhotoKit 來進行異步讀取。變相相片會在畫面顯示後才會出現,得特別處理。還好,事情只是煩,而不是難。現在總算完成了功能部份,得趕快製作其他。

2016年5月10日 星期二

Illustrator + Blender = 3D Printed Text


《TIPS》負責人 Mark 哥看到我在 Facebook 貼上上面的相片後,吩咐我用立體打印機為他的「立刻開工」服務製作「立刻」二字。之前《Not-Bag》得到他們的義務栽培,我當然要報答一下。


在 Illustrator 畫好文字後,傳送到我擅長的 Inventor。雖然是同廠出品的軟件,但 Inventor 竟然無法把 Illustrator 圖案導入成草稿並作立體加工。花了數小時也找不到解決方法,唯有尋找其他出路。找到了 Blender 能幫得上忙。起初試極都無法做到 Extrude 動作,幸好再多次嘗試後終於煉成,並能輸出到 KISSlicer。可惜,我的 ArrayZ 出現問題,打印的軌跡全都錯了,連以往能成功打印的 .gcode 也出現相同情況。向 ArrayZ 作者 Chris Lau 請教後,認為是是驅動電路發生固障,需要更換後再作測試...。

2016年5月9日 星期一

植物的一天縮時攝影


這幾天拍了不同角度的縮時攝影。今次選了近鏡,看看植物的莖部會否像葉子般有大幅的動作。

2016年5月8日 星期日

寫 App 的,這麼容易賺 HK$30,000 至 $50,000

一位認識而又十年沒交流的朋友突然 WhatsApp 我:「Pacess,有朋友問我寫 Apps 工作是否要對 IT 好有認識或者懂得編程,坊間那些教寫 Apps 的課程是否能幫到他入行?」。我想了想,然後簡略回覆:「要熟悉一種編程語言。外面的 App 課程是廢的。」。然而,他繼續追問:「那麼,為何外面又會有人說到好像很易入行?寫個 Apps 又收 HK$30,000 至 $50,000?不是這麼容易嘛?」。這個「容易」觸動到我的神經,決定慢慢跟他說明一下我的經驗。

我答:「有幾個原因啦。首先,以前懂寫 App 的人不多,所以識寫的人好值錢,人工因而拉高了。曾有報導說新手入職已經 HK$15,000。而商戶有 App 又自我感覺良好,願意付較高的費用寫 App。亦導致同事每半年至一年加薪一次,每次幾千。亦由於需求也很大,導致很多人入行。現在的 App 不是面子工程,需要有實際用途,變相有很多 App 是長期營運,而不是以前為單一活動而開發,所以那些公司也有自行組隊的需要,不再找我們 App Developer。加劇了供少需多的問題。公司只好退而求其次,無甚麼經驗的人都照聘用,希望訓練出有用的人來幫忙事業的成長。」。

「流動應用程式發展日新月異,每年一個 WWDC、一個 Google I/O 推出的新改變已經追到索氣。特別是 Android 機款超多,客人又想包羅大部份機,測試及買機都有成本。而且現時基本上所有 App 都要連接服務器來更新資料。服務器有成本、寄存有成本、也需要有人手維護。還有服務器那邊也有程式要寫,甚至要有後台介面讓客戶把資料推到應用程式內。」。

「編寫一個應用程式入門價為 HK$30,000 至 $50,000,代表沒甚麼功能的那種。現在的應用程式離不開 iOS + Android + 服務器端,開發費用起手 HK$100,000。外人看似很好賺,但實際價公只是成本價,甚至蝕本。上面看到人手也要三個,每人月薪 HK$20,000 的話,一個月得花 HK$60,000 在工資上,還未計算燈油火爉及其他開支。多競爭又是一個問題。有能力的人,自己接工作好過打工啦。」。

「說回你的朋友。我亦遇見過甚麼 IT 技能都不懂的新人。在外面學那些 App 雞精班,讀了幾個課程,但我見那人只是按老師的範例來做,根本不懂得寫 App,亦不明白箇中道理。所以我認為學習不是問題,但要自行溫習、進修及理解。否則請了他後寫不出甚麼,炒硬。」。

2016年5月6日 星期五

讀取相簿名稱


上次試過讀取相機菲林最後的相片,今次則是讀取相簿名稱。

原本很懶,只想快速從源代碼中找到讀取相簿名稱的方法。花了一段時間,仍然無法成功。只好乖乖地細看文件說明。一會兒,能成功讀取相簿名稱及其相片。有時是急不來。錯,是由於 FetchResult 有三種內容,錯配便會出錯。得理解後才知道甚麼時候該做甚麼。 首先,我想在應用程式內顯示「所有相片」及其他預設相簿:
   PHFetchOptions *allPhotosOptions = [[PHFetchOptions alloc] init];
   allPhotosOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
   PHFetchResult *allPhotos = [PHAsset fetchAssetsWithOptions:allPhotosOptions];
   
   PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
   
   _sectionFetchResultArray = @[allPhotos, smartAlbums];
之後,我用 UITableView 來顯示,所以在構成 UITableViewCell 時要讀取相簿名稱:
   
   //  Setup album name
   if (indexPath.section == 0)  {
      string = NSLocalizedString(@"SELECTPHOTOVIEW_ALLPHOTOS", nil);
   }  else  {

      PHFetchResult *assetCollectionResult = _sectionFetchResultArray[indexPath.section];
      PHCollection *collection = assetCollectionResult[indexPath.row];
      string = collection.localizedTitle;
   }
   label = (UILabel *)[cell viewWithTag:SELECTPHOTOVIEW_TAG_ALBUMNAME];
   [label setText:string];
及相片:
   //  Setup thumbnail image
   imageView = (UIImageView *)[cell viewWithTag:SELECTPHOTOVIEW_TAG_THUMBNAIL];
   width = imageView.frame.size.width;
   height = imageView.frame.size.height;

   PHFetchResult *assetResult = _sectionFetchResultArray[indexPath.section];
   if (indexPath.section == 0)  {

      //  All photos
      count = [assetResult count];
      if (count == 0)  {

         //  If no photo was found, use thumbnail
         image = [BZImage imageNamed:SELECTPHOTOVIEW_FILE_THUMBNAIL];
         [imageView setImage:image];
      }  else  {

         //  Have photos, get the latest one
         PHAsset *asset = [assetResult lastObject];
         [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:CGSizeMake(width, height) contentMode:PHImageContentModeAspectFit options:nil resultHandler:^(UIImage *result, NSDictionary *info)  {
            [imageView setImage:result];
         }];
      }
   }  else  {

      //  Smart albums
      PHAssetCollection *collection = [assetResult objectAtIndex:indexPath.row];
      PHFetchResult *result = [PHAsset fetchAssetsInAssetCollection:collection options:nil];

      count = [result count];
      if (count == 0)  {

         //  If no photo was found, use thumbnail
         image = [BZImage imageNamed:SELECTPHOTOVIEW_FILE_THUMBNAIL];
         [imageView setImage:image];
      }  else  {
         
         //  Have photos, get the latest one
         PHAsset *asset = [result lastObject];
         [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:CGSizeMake(width, height) contentMode:PHImageContentModeAspectFit options:nil resultHandler:^(UIImage *result, NSDictionary *info)  {
            [imageView setImage:result];
         }];
      }
   }

2016年5月5日 星期四

WiFi Lamp


終於都到了在《Maker Hive》展出《WiFi Lamp》的日子。華輝替我印製的 Foamboard,顏色跟設計時有所出入,綠色變成黃色,細節位也印得不夠清𥇦,估計是因為忘記把 RGB 色域轉為 CMYK 色域。今晚有很多說英語的機會,原因是有很多外籍人士參觀;連看上去是唐人的都大部份以英語溝通。不過,場內的 Maker 多是手工藝系的 Maker,機乎沒有科技系,所以整晚時間有點浪費...。我還是喜歡跟科技有關的展出。

以下是部份相片:

2016年5月4日 星期三

Mac + Cronjob + Python + OpenCV + Webcam = 縮時攝影


上周六到石峽尾教授「開發 Android 應用程式」後順道去了深水埗黃金商場,以 HK$40 購買了一支 USB 鏡頭。打算連接 Raspberry Pi 2 為我的植物進行成長過程的縮時攝影。家中有一支外型一樣,功能差不多的鏡頭,它能順利在 Raspberry Pi 2 上使用;可是新的那支卻無法成功連接。買這支鏡的原因是使用 OpenCV 作實時影像分析的解像度不用很高,640x480 已經綽綽有餘。既然如此,何不買平價的呢?

既然 Raspberry Pi 2 無法使用這支鏡頭,連到 Mac 上看看。雖然有點慢,但成功了。意味著我不能拿它去換一支新的...。這樣,只好把本來由 Raspberry Pi 2 的工作調給 Mac mini 去做。原本想簡簡單單以 Python 擷取鏡頭畫面,但找到十個方案,十個都要用 OpenCV,只好在 Mac mini 上安裝 OpenCV 好了。查過 Mac OS X 本身也支援 crontab,所以用它來設定每兩分鐘拍攝一張 640x480 解像度的相片。以下是擷取鏡頭畫面的 Python 程序:
##------------------------------------------------------------------------------
##  Capture a still picture from webcam
##------------------------------------------------------------------------------
##  Language: Python 2.7 + OpenCV
##  Platform: Mac OS X 10.9
##  Written by Pacess HO
##  Copyright 2016 Pacess Studio.  All rights reserved.
##------------------------------------------------------------------------------

import cv2
from time import localtime, strftime

##------------------------------------------------------------------------------
##  Create webcam object
capture = cv2.VideoCapture()
capture.set(cv2.cv.CV_CAP_PROP_GAIN, 1)
capture.open(0)

##------------------------------------------------------------------------------
##  Get a frame from webcam
retval, image = capture.retrieve()
capture.release()

##------------------------------------------------------------------------------
##  Save frame to a JPEG file, filename is timestamp
filename = strftime("%Y%m%d%H%M%S.jpg", localtime())
cv2.imwrite(filename, image)

2016年5月3日 星期二

準備 Android 第四課


本周六便是《開發 Android 應用程式》的第四課,亦是最後一課。轉眼已經一個月時間。今次這個工作真的蝕大本,沒想過每週都要備課,夾夾埋埋花了的時間,是原來的一倍多,變相是半價了 >_<。不過,同學們有心想學,我每周為她們準備教學內容也沒所謂。教學相長,對我自己來說也有得著。經過這四堂,我在 Android 的開發上又增添了不少經驗值,也不錯。希望同學們學到的東西不要還給我!

上堂的內容最為艱深,但大家都學到了設定藍芽芯片、也加插了 Arduino 編程教學、用 Android 經藍芽 2.0 連接 Arduino Uno 讀取溫度數據、學過拍攝相片。最後一堂將會講解儲存及輸出內容,課堂內容已經準備妥當。當天應該還有半堂時間,同學們最想學寫簡單遊戲,不過只有四小時,可以寫些甚麼呢?