This page looks plain and unstyled because you're using a non-standard compliant browser. To see it in its best form, please visit upgrade to a browser that supports web standards. It's free and painless.

Fillano's Learning Notes 會員登入 會員註冊

原文:用php做圖片臉部偵測

這是自己在ithelp網站上分享的文章,想一想如果需要在圖片上針對臉部特徵做一些處理的話,可以做參考,所以在這裡提一下,不另外寫文章了。

之前想要熟悉一下PHP Extension的開發,剛好看到LightCloud,他是基於另外一個專案Tokyo Tyrant,Tokyo Tyrant的底層則是一個高速Key-Value pair資料庫,叫做Tokyo Cabinet。TokyoTyrant已經有不少專案支援,包括純php以及extension,但是Tokyo Cabinet似乎還沒有,所以就拿他來開刀。

結果在這裡:http://www.fillano.idv.tw/tokyocabinet3-0.1.src.tar.gz

其實我之前是根據TokyoCabinet 1.4.X的TCADB這個介面來開發,不過最近都在使用Ubuntu,他安裝的套件是1.3.X版本的,之前許多功能不能使用...所以就先把他改成與1.3.X相容,也稍微調整一下程式碼再釋出。

這個Extension主要是提供幾個東西:TokyoCabinet的Hash及B+ Tree格式的資料庫檔案及Hash的記憶體資料庫的支援,可以開啟(如果apache有讀寫權限,那檔案不存在時會產生檔案)、寫入、查詢、讀出等功能。另外雖然可以支援記憶體資料庫,但是用php extension的方式使用不太有意義,我當初寫這個extension除了練習之外,還順便拿來做檔案資料庫來測試使用。

要安裝,必須先安裝php的developer功能,解開檔案後,在目錄中執行phpize,然後./configure -> make -> make install等。mytest目錄中有兩個php檔案可以用來做測試。當然,在安裝前要先裝好tokyocabinet。

Tokyo Cabinet的key與value都是binary safe的,而且不限長度,所以拿來存放大檔案來做streaming也許是個不錯的用法。

因為是習作,所以歡迎指教囉,有些地方其實不太確定就硬上了,心有點虛......


2010-1-22 17:22 補充

除了透過memcached的協定來存取TokyoTyrant,PECL上面已經有extension支援TokyoTyrant,作者還在持續更新中:
http://pecl.php.net/package/tokyo_tyrant
開發的主站在github:
http://github.com/mkoppanen/php-tokyo_tyrant
作者的blog有不少好物:
http://valokuva.org/

為了測試自己寫的PHP Tokyo Cabinet Extension是否在多人運作下不會有問題,興起了利用Web Worker來做測試的念頭。

(按:我自己寫的extension只是包裝他的tcadb函式庫,這樣就可以做local access他的資料庫檔案或是記憶體資料庫。其實透過Tokyo Tyrant才是最好方法,Plurk的LightCloud也是基於Tokyo Tyrant的。Tokyo Tyrant已經有初期的PECL Connector可以支援,同時也有純PHP實現的Connector。)

 (閱讀全文)

PHP 5.3.0開始,有一個重要的特性正式加到PHP裡面,就是匿名函數。(請參考:Anonymous functions。手冊還有提到,匿名函數目前是用 Closure內部類別實作,但是不要管他,因為實作方式有可能修改。)

在提到匿名函數之前,先看看PHP的callback應用。callback是php非常好用的功能,可以利用他來用自訂的方式處理資料,例如array_map,就可以用一個callback函數一次處理陣列中所有元素,不用iteration,也不用loop。下面是用callback函數處理一個整數陣列,傳回每個元素的平方:

 (閱讀全文)

Plurk沒有提供api,但是有提供使用IM來收發訊息的功能,透過簡單的語法,就可以透過IM做出plurk機器人。

我自己是使用Google Talk來收發Plurk,Google Talk使用從XMPP標準延伸出來的協定,所以要利用XMPP來使用Google Talk不必花太多功夫。稍微找了一下,就在google code上面找到一個雖然還在開發中,但是已經可以使用的專案,叫做XMPPHP

要讓他可以跑,還需要打開PHP的openssl模組,因為Google Talk會用到TLS以及SASL。另外,就算功能正常使用,我還是碰到許多無法fclose資源的訊息...沒辦法,還在開發中的東西吧...

接下來,只要修改一下他的範例(sendmessage_example.php):

 (閱讀全文)

之前用Quercus嘗試將CodeIgniter放到Google App Engines上執行,仿照Google提供的留言板例子,把程式放到CodeIgniter上跑。但是後來發現,在留言板上輸入中文會出現問題。

我的資料是放在一個java bean裡面(拿google的範例,所以叫做Greeting),資料存取透過jdo,存入時使用servlet,取出則用php。結果存入中文資料沒問題,取出中文資料就...一堆亂碼。

比較一下取出的資料,發現似乎字串長度是對的,但是編碼不對,每個中文字元長度似乎都被裁成一個位元組,而不是UTF-8的三個位元組。在java bean裡面動了一下手腳,把getter回傳的資料型態改成byte array,然後用getBytes("UTF-8")回傳,結果就是正確的了。(另外一個方法是用&#number;這樣的格式來處理每個字元)

看起來Quercus處理java與php資料型別轉換的方法(unmarshal)似乎怪怪的...要不然就是他內建的Env.createString()方法有點問題...另外,如果不使用GoogleQuercusServlet也會有一些問題,我把script_encoding改成iso-8859-1就正常顯示php程式中的中文???但是我用binary editor確認過,檔案編碼是UTF-8耶???真奇怪。我還特別修改unicode.semantics、unicode.script_encoding、unicode.runtime_encoding、unicode.output_encoding等選項耶,但是沒啥用處。

Quercus的功能感覺還不夠穩定...

其實如前篇,並沒有什麼進度,不過反正一步一步測試。

目前是先把Quercus on the Google App Engine的例子porting到CodeIgniter然後放到google app engine跑跑看。urlrewrite的部份透過UrlRewrite Filter來做。

datastore還是用google app engine官網的例子與Quercus on the Google App Engine的php程式整合出來的,只是把MVC分開,用CodeIgniter來跑。

有興趣可以玩玩看:http://fillanocode.appspot.com/

要改資料庫方法跟ActiveRecord方法,可能需要做一些抉擇。google提供的datastore其實是一個ORM的東西,跟直接操作關聯式資料庫的方法頗有差距,與其porting上去(但是背後要重組query),也許不如透過JDO去操作...,不然要實作到可以直接透過sql操作,要花不少功夫。

怕標題太聳動,所以先聲明一下,我只是想讓CodeIgniter根目錄下的index.php會動。(怎麼在Google App Engine跑php,請參考前篇用Google App Engine跑php ,至於為何挑CodeIgniter?因為他架構比較簡單,似乎比較容易改...)

Google App Engine有一個限制,就是無法寫入檔案系統,這對於許多framework及php有很大殺傷力。尤其是cache機制,需要可以動態產生、更新cache,這樣在Google App Engine跑php就有很多問題。例如想要跑CodeIgniter,就會出現一堆錯誤訊息,主要來自無法寫入檔案。

Quercus有一個很方便的地方,就是可以自己寫QuercusModule,QuercusModule的公有方法就會成為php函數。使用自訂的函數,配合Google App Engine的datastore服務,就有可能把datastore當作檔案系統來使用。

 (閱讀全文)

Google App Engine開始測試java的支援了。由於java透過他的scripting可以支援php,所以可以用這個方式間接在Google App Engine跑php。之前在請問一下有人在玩google app engine嗎? 討論串討論了一下,自己又找資料做了一些測試,果然可以,但是有少許語法似乎會有問題。

主要的參考資料可以看下面幾個連結:

詳細的配置使用方法可以參考上面的連結,但是web.xml不要用第二個連結裡面的,他改過QuercusServlet,使用GoogleQuercusServlet,但是以發行的quercus.jar裡面沒有這個東西,所以配置會有問題。只要把google的guestbook範例跟第二個連結中的php範例結合起來理論上應該就ok......不過我直接用那個php範例會出現錯誤......稍微除錯一下php,發現php裡面的

foreach ($greetings as $g) {
  ......
這一行出現錯誤,所以改了一下陣列的使用方式,就可以跑了:
for ($i=0; $i<count($greetings); $i++) {
  $g = $greetings[$i];
  ......

需要注意的是,目前google appengine datastore的使用與資料庫大大不同,他是一個object data store,目前java可以透過他提供的jdo/jpa方式來存取,存入的entity class必須用java來實作,用php做不出來。參考上面第二個連結可以看到短期可能出現的一些解法(透過H2模擬mysql),長期恐怕還是需要等google推出原生的支援。


2009-5-15 16:52 補充

上了resin的官網仔細看了一下,resin-4.0.0裡面可以找到resin.jar,這裡面就有GoogleQuercusServlet(如同blog上說的)。另外測試了一下,建議使用resin.jar而不是quercus.jar + resin-util.jar + script-10.jar + jetty-util-6.1.0.jar這個solution。這樣使用foreach也不會出現錯誤訊息。

龐大的switch case常常讓程式不好維護,需要增加新功能時就得去改switch case所在的程式,讓維護比較麻煩。之前看到可以用command pattern來改進這個狀況,所以就用來試一試。

大致的構想如下:

class CommandExecuter {
  var $command=array();
  function addCommand($command,&$obj) {
    $this->command[$obj->cmdName] =& $obj;
  }
  function getAllCommand() {
    return array_keys($this->command);
  }
  function render($command) {
    $this->command[$command]->render();
  }
  function deal($command) {
    $this->command[$command]->deal();
  }
}

上面是主要的程式,用來執行所有表單產生以及回傳搜尋結果。

class Command {
  var $cmdName="";
  function Command ($str) {
    $this->cmdName = $str;
  }
  function render() {
    產生表單
    echo "<input type='hidden' name='COMMAND' value='".$this->cmdName."'>";
    ......
    echo "</form>";
  }
  function deal() {
    處理表單資料,產生搜尋結果並回傳
  }
}

以上是實際產生表單以及處理搜尋結果的命令類別。

class CommandAssembler {
  function getCommandExecuter(){
    $tmp = new CommandExecuter();
    $tmp->addCommand("search1",new Command("search1"));
    return $tmp;
  }
}

用這個類別組出完整的設計。

使用方法(產生搜尋表單):

$cmd = CommandAssembler::getCommandExecuter();
$keys = $cmd->getAllCommand();
foreach ($keys as $k) {
  $cmd->render($k);
}

使用方法(產生搜尋結果):

$cmd = CommandAssembler::getCommandExecuter();
$cmd->deal($_POST['COMMAND']);

這是大略的想法。

方法及程式發表在[酷!學園 -> PHP程式設計]論壇中,這個網址:http://www.ez2.us/~ricky/RobotAway/則有範例與程式下載,也可以線上檢視原始碼。使用起來十分簡單。

他的原理是用一些javascript的混淆技術,包括隨機變數名稱、隨機註解、程式內容編碼等方法,讓機器人難以即時破解,來達到防止灌水的目的。(這些也是病毒上常見的變形、混淆技術)

最大的好處是,對於一般的網頁用戶來說,不會感覺到這個程式的存在,不像一般的captcha需要額外輸入文數字來驗證。對於程式設計師來說,只要用到三個方法,配合網頁程式,就可以做出來,非常簡單。

在上述的連結,Ricky兄有提供使用的範例。不過因為使用簡單,所以也不太需要繁雜的說明。另外,程式是寫成php5的類別,對於php4的用戶來說,只要拿掉private關鍵字,把constructor函數從__construct改名為RobotAway就可以了。

使用步驟大致如下:

  1. 在php程式中引用"rw.inc.php"
  2. $ra = new RobotAway("SITEKEY","INPUTNAME");
  3. 在form中加入名為"INPUTNAME"的隱藏欄位,記得要把id也設為"INPUTNAME"
  4. 用$ra->GenerateJS()方法產生混淆過的javascript函數
  5. 用$ra->CheckFunction()方法產生呼叫上述函數的javascript
  6. 在submit之前攔截submit,插入步驟5的php來產生呼叫步驟4的javascript函數,動態賦值給INPUTNAME隱藏欄位,然後才submit
  7. 在伺服器端,利用$ra->Verify()方法來檢查$_POST['INPUTNAME'],(在之前要用步驟2的同樣參數生成$ra),就知道傳進來的值是否正確。

應用一些方法,還可以讓這個程式更難捉摸。就是在要輸入表單的地方,用隨機產生的SITEKEY與INPUTNAME來產生$ra物件,然後把SITEKEY與INPUTNAME存入session,要做Verify時,再從session中取出SITEKEY與INPUTNAME來產生$ra物件,因為每次欄位名稱與sitekey都不一樣,會更增加機器人灌水的難度。

昨天檢視了一下以前寫的程式,想要改良一下程式的效率,並且讓程式清晰易懂。這個時候發現一個問題,就是依照條件建構SQL的過程太複雜,需要許多if/else判斷,使得程式容易不小心出錯。

仔細觀察了一下,問題發生的主要原因在於兩個地方:

  1. SQL子句有固定的順序,但是程式邏輯不一定符合這個順序
  2. 查詢的條件必須集中在Where子句中,但是形成這些條件的過程不一定是集中的

結果為了依照SQL的規則來產生SQL語句,就會讓程式流程變得很複雜。

想到的方法是,將sql語法的邏輯與產生sql的邏輯分開,寫一個類別,任意將各種條件傳入,等到需要用到SQL語句時,再依照SQL語法來將各種條件組合成SQL語句。依照這個想法,寫了一個簡單的SQL Builder類別:

class QueryBuilder {
	var $table;
	var $columns;
	var $active;
	var $order;
	var $where;
	var $limit;
	function QueryBuilder ($table, $columns) {
		$this->__construct ($table, $columns);
	}
	function __construct ($table, $columns) {
		$this->table = $table;
		if (strpos($columns,':')===false) {
			$this->columns = array($columns);
		} else {
			$this->columns = explode(':',$columns);
		}
		$this->where = array();
		$this->order = "";
		$this->limit = "";
	}
	function setActive ($active) {
		$this->where[] = " $active=1";
	}
	function setOrder ($order, $sort="ASC") {
		$this->order = sprintf(" ORDER BY %s %s", $order, $sort);
	}
	function setWhereInt ($col, $val) {
		$this->where[] = sprintf(" %s=%u", $col, intval($val));
	}
	function setWhereStr ($col, $val) {
		$this->where[] = sprintf("%s='%s'", $col, $val);
	}
	function setSearch ($col, $val) {
		$this->where[] = sprintf("%s LIKE '%%s%'", $col, $val);
	}
	function setSearchLt ($col, $val) {
		$this->where[] = sprintf("%s < %u", $col, intval($val));
	}
	function setSearchGt ($col, $val) {
		$this->where[] = sprintf("%s > %u", $col, intval($val));
	}
	function setLimit ($offset,$rowcount) {
		$this->limit = " LIMIT $offset, $rowcount";
	}
	function compose () {
		$ret = sprintf("SELECT %s FROM %s", implode(',',$this->columns), $this->table);
		if ($this->where) {
			$ret .= sprintf(" WHERE %s", implode(' AND ', $this->where));
		}
		$ret .= $this->order;
		$ret .= $this->limit;
		return $ret;
	}
}

這個類別還很粗糙,但是剛好滿足我的需求,也讓程式流程看起來比較清楚了。

檔案上傳進度資訊是PHP5.2才新增的功能。但是最近在逛phpclass.org時,發現有人做了PHP4的patch。patch及說明的出處:http://www.phpclasses.org/blog/post/61-File-upload-progress-meter-for-PHP-4-at-last.html

作者是早期PHP的核心開發人員,跟據他的說明,其實在php4就已經有人做了patch,但是不知為何到了PHP5.2,PHP團隊才把這個功能正式加進來。PHP5已經問世好幾年了,但是市面上大部分的網站,其實都還在用PHP4,主要是因為移植的複雜度以及潛在問題,所以許多人裹足不前。所以為了廣大的PHP4用戶,他做了Patch,使用這個patch就可以支援PHP5.2的檔案上傳進度資訊功能。

看到這個資訊很興奮,不過他patch的rfc1867.c是屬於PHP的核心,所以非得重新編譯不可。另外,PHP4已經不再更新了,末代版本是4.4.7,作者patch的版本是4.3.11以及4.4.4,在PHP官方網站上已經找不到原始碼的套件,所以只好用4.4.7來試試看。

以下是大致的過程中以及我碰到的一些問題:

  1. 一開始我是手動一行一行改rfc1867.c以及rfc1867.h,發現編譯可以過,但是結果上傳功能出問題.....。所以用patch指令來做,但是會有三處無法patch的地方,這些就再手動patch吧。結果.....恩,上傳功能沒問題,但是不知道上傳進度資訊可不可以用。
  2. 上網找了一下,結果還是到PECL下載了uploadprogress套件,網址:http://pecl.php.net/package/uploadprogress。套件中幾乎沒有說明,不過看起來是需要有PHP5.2才能裝。沒關係,先phpize,然後configure/make/make install。(我在我的系統裡面另外編譯了一套apache-2.0.61來測試,同時另外編譯一套php-4.4.7,但是使用phpize的時候,他會把include directory指到系統中預設的php目錄,所以我另外手動改了Makefile,才能順利編譯安裝)
  3. 安裝完畢以後,跑phpinfo(),ok,出現uploadprogress套件訊息,看起來這個延伸套件有作用。另外用get_defined_functions()函數,可以發現有一個叫做uploadprogress_get_info()的函數。
  4. 還是不知道怎麼用比較好,所以到網上找一找。後來在這個網站上找到說明跟展示:http://blog.liip.ch/archive/2006/09/10/upload-progress-meter-extension-for-php-5-2.html。更新過可demo的東西要參考另一個blog:http://blog.joshuaeichorn.com/archives/2005/05/01/ajax-file-upload-progress/。他的demo檔案放在一個web svn系統上:http://websvn.bluga.net/wsvn/HTML_AJAX/trunk/?rev=0&sc=0,把檔案下載下來,測試了一下......成功!!!

如果大家想要測試的話,以下是我patch過的rfc1867.c以及rfc1867.h:下載(注意:只能給php-4.4.7用,我是用它的source來patch的喔)

另外是幾個測試的圖片:

  1. 這是phpinfo()的畫面:
    upload progress
  2. 這是get_defined_functions()的畫面:
    upload progress
  3. 這是上傳進度的畫面:
    upload progress
  4. 這是上傳結束的畫面:
    upload progress

(我把畫面中的網址拿掉,因為我測試完就把測試用的apache關掉了。其實效果應該跟demo網站是一樣的。)

前一陣子找到的資料,使用VeriWord類別來做文字圖片驗證(Captcha)可以讓圖片驗證具有較高的安全性。所以就來試試看了。

VeriWord(官方網站在phpclasses上)看起來已經很久沒有人維護了(最後的日期是2004年,有一點久了).....手冊也找不到,不過使用起來很簡單。主要的功能包括:

  1. WordArt Creation:隨機選取提供的truetype字型來製作captcha,並且隨機改變每個字的水平/垂直位置、角度,並提供多種方法讓文字變形(預設有Wavy(波浪扭曲)、Bubbly(隨機產生的小泡泡干擾文字顯示)、BreakType(把文字的部份pixel拿掉),也可以自己撰寫filter來處理文字變形
  2. Noise Generation:根據提供的一些圖片隨機選取背景,或是自己產生隨機背景雜訊
  3. Word Generation:根據提供的字典檔案隨機選字、或是自己產生隨機的字母組合
  4. output:支援多種輸出圖檔格式(要看使用的GD Library版本)、可隨意指定長寬
  5. 簡單的驗證程式叫做VeriFicator
  6. 可以輸出成Flash格式,支援動畫flash背景(我看了一下原始碼,flash功能還沒寫完的樣子,有一些參數理論上要可以設定的,但是他寫死在程式裡)
  7. 使用設定檔來設定

大概介紹到這裡,先來試試吧。

先來看看怎麼安裝設定:

  1. 全部的東西都壓在一起,先把他解到一個目錄下
  2. veriword設定檔內容與你解壓縮出來的環境有一些不一樣,所以要做幾件事情:
    1. 建立fonts目錄,把你要隨機抽換的ttf檔案放進去
    2. 建立words目錄,然後把字典檔words放到目錄下(或是更改設定,把設定中的"words/words.txt"改成"words.txt"
    3. 建立noises目錄,用來放設計好的Background Noise圖檔(如果設定成RandomNoise就不會用到)
    4. 視環境調整veriword.ini
  3. 用瀏覽器開啟sample.php看看是否正常執行
  4. 根據自己的需求,繼承或改寫相關的類別

大概就這樣吧,用起來很簡單的,程式碼也不多,很容易了解,要整合到不同的程式裡面不會花很大功夫。

以下是sample.php跑出來的畫面:

這一張的captcha比較清楚,套用他的RandomNoise,有時候字會看不清楚....如果不是搭配BackgroundNoise的話,我想使用的字型最好挑選過,效果比較好。

這個程式已經沒人在維護,官方的manual連結也找不到了,但是我有google到還有別的網站有,檔名叫做manualveriword.pdf,參考一下。

xoops2定義了XoopsObject這個核心類別,用陣列來儲存一系列的值,並用XoopsObjectHandler類別來與資料表做映射。XoopsObject定義好了初始化、存取值的方法,讓我們很方便地使用。更方便的是,由於在XoopsObject類別內,只使用了TextSanitizer物件來做字串檢查,而XoopsObjectHandler類別只是一個抽象類別,並沒有定義好實作,所以只要把XoopsObject(放在/kernel/opject.php檔案內)以及TextSanitizer(放在/class/module.textsanitizer.php檔案內)從Xoops2中取出,就可以當作自己開發的核心。

使用XoopsObject如何達到快速開發呢?因為XoopsObject裡面定義好了資料型別與處理方法,利用他定義好的型別,可以設計好一些類別,輸入XoopsObject即可產生相對應的網頁。舉例來說,製作表單是一件相當煩瑣的工作,可以設計一個表單類別,輸入XoopsObject後,可以針對相對應的xoops資料型別產生表單。

另外使用XoopsObject搭配Smarty(或其他樣板引擎)來做MVC模式開發,就可以看到快速開發的效果。由XoopsObject以及XoopsObjectHandler負責Model,Smarty負責View,程式開發只要專注在Controller上就可以。

由於XoopsObjectHandler其實並沒有實作他定義的方法,所以在應用的時候還需要自己撰寫他的create、get、insert、delete方法,如果要偷懶,更好的方法是寫一個類別產生器,類似PDO。這樣在開發上,只要設計出data schemas,就可以自動產生箱對應的物件。(也許有一天可以跟phpMyAdmin結合)目前Xoops有設計好的database類別可用,但是只有支援mysql。如果要抽換資料庫,或是改成用pear的db套件等,類別產生器都需要修改,這是比較麻煩的地方。

另外一個問題是,XoopsObject並沒有比較好的處理關聯性的方法,如果資料關係複雜,處理起來會花費比較大的功夫。如何讓XoopsObject能自動處理類別之間的關係(1:n、n:m等等),而不用花費controller的功夫,是類別產生器需要下的另一個功夫。

如果XoopsObjectHandler的方法是一致的(例如,僅限於create、get、insert、delete),還有另一個好處,就是容易做權限控管。可以利用類似Static Proxy的方式,控制這些方法的使用。

會有這些想法,其實是自己懶的緣故(已經熟悉xoops2,但是懶得學cake),另外一個原因是希望不會因為使用一個framework讓程式大幅增加,並留給自己一些Mashup的空間。