今回は外部ライブラリとリンクする方法、
WEBカメラのようなハードウェアにアクセスするのはPHPの標準機能では不可能なことで、
OpenCVのインストール
まずは公式WikiのLinux向けインストールガイド
specファイルを記述する
今回制作するcvcaptureモジュールのspecファイルの構成はリスト1のようになっています。
<?xml version="1.0" encoding="UTF-8"?>
<extension name="cvcapture" version="0.1.0">
<summary>Capture frames from webcams.</summary>
<!-- モジュール情報(説明は省略) -->
<!-- 依存関係(リスト2-1、2-2) -->
<!-- 定数定義(リスト3) -->
<!-- 関数定義(リスト4) -->
</extension>
依存関係を定義する
依存関係の定義はリスト2-1にあるように、
<deps language="c" platform="all">
<!-- pkg-configを使って“opencv”の設定を取得する -->
<with name="opencv" mode="pkg-config">
<!-- ヘッダ名(ヘッダのインクルードパスもpkg-configで設定される) -->
<header name="cv.h"/>
<header name="highgui.h"/>
</with>
</deps>
<deps language="c" platform="all">
<!-- configureの引数--with-cvcaptureで指定されたパス(省略時は
“/usr /usr/local”)から“include/opencv/cv.h”を探す -->
<with testfile="include/opencv/cv.h">
<!-- ヘッダ名(includeからの相対パス) -->
<header name="opencv/cv.h"/>
<header name="opencv/highgui.h"/>
<!-- ライブラリ名(libなどの接頭辞や.soなどの拡張子は不要) -->
<lib name="cv"/>
<lib name="highgui"/>
</with>
</deps>
定数を定義する
続いて、
<constants>
<!-- CV_CAP_*はopencv/highgui.hで定義されているマクロ -->
<constant type="int" name="CV_CAP_ANY" value="CV_CAP_ANY">autodetect</constant>
<-- (省略) -->
<constant type="int" name="CV_CAP_QT" value="CV_CAP_QT">QuickTime</constant>
</constants>
関数を定義する
最後に、
筆者はCodeGen_
値を返す関数のプロトタイプでは関数名の前にvoidではなく戻り値の型を書きます。また、
キャプチャする画像の大きさはカメラによって変わるので、
<function name="cv_camera_capture">
<proto>bool cv_camera_capture(string filename[, int index[, mixed &size]])</proto>
<summary>Capture from camera.</summary>
<description>(省略)</description>
<test>
<code><?data
if (cv_camera_capture("test_camera.jpg", CV_CAP_ANY, $size)) {
print_r($size);
}
?></code>
<result mode="format"><?data
Array
(
[0] => %d
[1] => %d
)
?></result>
</test>
</function>
<function name="cv_file_capture">
<proto>bool cv_file_capture(string dst_filename, string src_filename[, mixed &size])</proto>
<summary>Capture from video file.</summary>
<description>(省略)</description>
<test>
<skipif><?data
if (!file_exists("sample.3g2")) {
die("skip sample video file does not exist");
}
?></skipif>
<code><?data
if (cv_file_capture("test_file.jpg", "sample.3g2", $size)) {
print_r($size);
}
?></code>
<result mode="format"><?data
Array
(
[0] => %d
[1] => %d
)
?></result>
</test>
</function>
ソースコードを生成する
これでspecファイルが用意できたので、
$ pecl-gen --dir=cvcapture-0.1.0 cvcapture-0.1.0.xml Creating 'cvcapture' extension in './cvcapture-0.1.0' Your extension has been created in directory ./cvcapture. See ./cvcapture-0.1.0/README and/or ./cvcapture-0.1.0/INSTALL for further instructions.
ソースコード解説
前回は生成されたソースコードの説明を一切しなかったので、
バージョンチェック
まずはconfig.
AC_MSG_CHECKING(PHP version)
AC_TRY_COMPILE([#include <php_version.h>], [
#if PHP_VERSION_ID < 40000
#error this extension requires at least PHP version 4.0.0
#endif
],
[AC_MSG_RESULT(ok)],
[AC_MSG_ERROR([need at least PHP 4.0.0])])
一見するとリスト5-1でちゃんとバージョンをチェックできているように思えますが、
AC_MSG_CHECKING(PHP version)
AC_TRY_COMPILE([#include <php_version.h>], [
#if PHP_MAJOR_VERSION < 4 || (PHP_MAJOR_VERSION == 4 && PHP_MINOR_VERSION < 4)
#error this extension requires at least PHP version 4.4.0
#endif
],
[AC_MSG_RESULT(ok)],
[AC_MSG_ERROR([need at least PHP 4.4.0])])
引数情報
次に、
PHP_FUNCTION(cv_file_capture); /* 関数のプロトタイプ宣言 */
#if (PHP_MAJOR_VERSION >= 5)
ZEND_BEGIN_ARG_INFO_EX(cv_camera_capture_arg_info, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 1)
ZEND_ARG_INFO(0, filename)
ZEND_ARG_INFO(0, index)
ZEND_ARG_INFO(1, size)
ZEND_END_ARG_INFO()
#else /* PHP 4.x */
#define cv_camera_capture_arg_info NULL /* third_arg_force_refに変更する */
#endif
これらの情報はcvcapture.
/* {{{ cvcapture_functions[] */
function_entry cvcapture_functions[] = {
PHP_FE(cv_camera_capture , cv_camera_capture_arg_info)
PHP_FE(cv_file_capture , cv_file_capture_arg_info)
{ NULL, NULL, NULL } /* 必ずヌル終端する */
};
/* }}} */
モジュール情報
モジュールに関する情報もメインのソースコードであるcvcapture.
/* {{{ cvcapture_module_entry
*/
zend_module_entry cvcapture_module_entry = {
STANDARD_MODULE_HEADER, /* おまじない */
"cvcapture", /* モジュール名 */
cvcapture_functions, /* 関数リスト */
PHP_MINIT(cvcapture), /* Replace with NULL if there is nothing to do at php startup */
PHP_MSHUTDOWN(cvcapture), /* Replace with NULL if there is nothing to do at php shutdown */
PHP_RINIT(cvcapture), /* Replace with NULL if there is nothing to do at request start */
PHP_RSHUTDOWN(cvcapture), /* Replace with NULL if there is nothing to do at request end */
PHP_MINFO(cvcapture),
"0.1.0", /* バージョン文字列 */
STANDARD_MODULE_PROPERTIES /* おまじない */
};
/* }}} */
このうちPHP_{MINIT,MSHUTDOWN,RINIT,RSHUTDOWN,MINFO}は、
/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(cvcapture)
{
REGISTER_LONG_CONSTANT("CV_CAP_ANY", CV_CAP_ANY, CONST_PERSISTENT | CONST_CS);
/* (省略) */
REGISTER_LONG_CONSTANT("CV_CAP_QT", CV_CAP_QT, CONST_PERSISTENT | CONST_CS);
/* add your stuff here */
return SUCCESS;
}
/* }}} */
関数を実装する
ここからはcvcapture.
/* {{{ proto bool cv_camera_capture(string filename[, int index[, mixed &size]])
Capture from camera. */
PHP_FUNCTION(cv_camera_capture)
{
/* 変数の宣言と初期化 */
const char * filename = NULL;
int filename_len = 0;
long index = 0;
zval * size = NULL;
/* 引数をパース */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|lz", &filename, &filename_len, &index, &size) == FAILURE) {
return;
}
/* 実装されていない旨のエラーを出力 */
php_error(E_WARNING, "cv_camera_capture: not yet implemented"); RETURN_FALSE;
/* 戻り値をfalseにしてreturnする(C言語レベルの戻り値はvoidで、PHPレベルの
戻り値であるzval *return_valueの型をboolに、値を0にしている) */
RETURN_FALSE;
}
/* }}} cv_camera_capture */
変数の宣言を見ると、
このコードに実装を書き加えてcv_
変数宣言の追加
最初に、
char *fullpath; /* 保存先のフルパス */
CvCapture *capture; /* キャプチャ構造体 */
IplImage *image; /* イメージ構造体 */
セーフモードとopen_basedirのチェック
引数のパースの後に、
if (strlen(filename) != filename_len ||
(fullpath = expand_filepath(filename, NULL TSRMLS_CC)) == NULL)
{
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Wrong filename given");
RETURN_FALSE;
}
if (php_check_open_basedir(fullpath TSRMLS_CC) ||
(PG(safe_mode) && !php_checkuid(fullpath, NULL, CHECKUID_CHECK_FILE_AND_DIR)))
{
efree(fullpath);
RETURN_FALSE;
}
PHPの変数から来る文字列はバイナリデータも含めて必ずヌル終端されている
フルパスを取得する関数expand_
php_
画像のキャプチャ
ここがcv_
/* キャプチャ構造体を作成 */
capture = cvCreateCameraCapture((int)index);
if (capture == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot create camera capture");
efree(fullpath);
RETURN_FALSE;
}
/* フレームを取得 */
image = cvQueryFrame(capture);
if (image == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot retrieve image");
cvReleaseCapture(&capture);
efree(fullpath);
RETURN_FALSE;
}
/* 画像を保存 */
if (!cvSaveImage(filename, image)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot save image to '%s'", filename);
cvReleaseCapture(&capture);
efree(fullpath);
RETURN_FALSE;
}
仕上げ
最後に、
if (size != NULL) {
CvSize imgsize = cvGetSize(image); /* 画像サイズを取得 */
zval_dtor(size); /* 変数の中身を開放してから */
array_init(size); /* 配列として初期化し直して */
add_next_index_long(size, (long)imgsize.width); /* 幅を追加 */
add_next_index_long(size, (long)imgsize.height); /* 高さを追加 */
}
cvReleaseCapture(&capture); /* imageも同時に開放される */
efree(fullpath);
RETURN_TRUE; /* 戻り値をtrueにしてreturn */
これでcv_
インストール
実装が終わったら、
$ phpize Configuring for: PHP Api Version: 20041225 Zend Module Api No: 20060613 Zend Extension Api No: 220060519 $ ./configure --enable-cvcapture (省略) $ make (省略) Build complete. Don't forget to run 'make test'. $ make test (省略) ===================================================================== Running selected tests. PASS cv_camera_capture() function [tests/cv_camera_capture.phpt] PASS cv_file_capture() function [tests/cv_file_capture.phpt] ===================================================================== Number of tests : 2 2 Tests skipped : 0 ( 0.0%) -------- Tests warned : 0 ( 0.0%) ( 0.0%) Tests failed : 0 ( 0.0%) ( 0.0%) Tests passed : 2 (100.0%) (100.0%) --------------------------------------------------------------------- Time taken : 2 seconds =====================================================================
テストが通ることが確認できたら、
$ sudo make install Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20060613/
実際に使ってみる
無事にインストールできたところで、
OpenCVがサポートしていない拡張子が指定された場合や、
<?php
extension_loaded('cvcapture') || dl('cvcapture.so');
for ($i = 1; $i <= 10; $i++) {
cv_camera_capture(sprintf('capture%02d.jpg', $i));
sleep(1);
}
図1はサンプルスクリプトの実行結果です。ちゃんと撮影できていますね。じっとしてくれない犬をMacBook内蔵のiSightカメラで撮影するために10回リピートしたという噂も聞かれますが、

おわりに
今回は依存するライブラリの指定方法、
ところで、
サンプルファイルのダウンロード
付録は後日掲載