把Bilibili的验证码识别算法用php-cpp写了个php扩展以及做了个API接口

花了点时间写了个php页面来调用这个接口,里面主要逻辑是去爬bilibili的图片一张,然后拿去识别一下,现阶段识别率还没有去优化所以基本还没法看,以后有空也许可以去改进一下,有兴趣的请看看:http://myqsmy.com/bili.php

首先我的开发环境是虚拟机的ubuntu12.04,首先需要升级g++到4.8.x,在ubuntu下可以:

sudo add-apt-repository ppa:ubuntu-toolchain-r/test

sudo apt-get update; sudo apt-get install gcc-4.8 g++-4.8

sudo update-alternatives --remove-all gcc 

sudo update-alternatives --remove-all g++

sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 20

sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 20

sudo update-alternatives --config gcc

sudo update-alternatives --config g++

gcc --version

php-cpp需要php.h头文件,在ubuntu下安装:

sudo apt-get install php5-dev

然后去php-cpp的github上下载源码编译,我下载的1.4版本,然后把php-cpp的源码make&&make install来编译并且安装之。

然后安装php-cpp的官网的一个例子编译了一个php扩展来试了一下,一会儿就写了个hello world,发现php-cpp用来写扩展的确很简单,比较意外的是目前我这里php-cpp官网本身是被墙了的。

现在开始想办法把Bilibili的字符识别算法(依赖于opencv2.4的一个captcha.hpp和对应的captcha.cpp的加起来1k行的代码)放到php扩展里面,Bilibili的字符识别算法是一个SsqRecog的类,这个类初始化的时候很耗时间,因此把这个类申明一个全局变量,php-fpm启动的时候初始化一次,后面每次调用就不用耗时间,这样比较好。总之,先写个测试用的,算法初始化用到的xml的训练数据、log以及测试用的样本统一放到/etc/php5/conf.d里面,写出来是这样的感觉:


#include "iostream"
#include "fstream"
#include "captcha.hpp"
#include <sstream>
#include <cstdlib>
#include <phpcpp.h>

using namespace cv;
using namespace std;

SsqRecog sr("/etc/php5/conf.d/sr.log", "/etc/php5/conf.d/cap", false);

void helloWorld(Php::Parameters &params) {
cout << "recog started:" << std::endl;

char buf[100];
Captcha cap; //验证码结构体
static int i = 0;
sprintf(buf, "/etc/php5/conf.d/img/__%04d.png", i++);
cap.fn = buf;
cap.mode = false; //调试模式
sr.recog(cap);
cout << cap.issue << endl;

std::string name = params[0];
std::cout << "Hello " << name << "!" << std::endl;
}

extern "C" {

/**
* Function that is called by PHP right after the PHP process
* has started, and that returns an address of an internal PHP
* strucure with all the details and features of your extension
*
* @return void* a pointer to an address that is understood by PHP
*/
PHPCPP_EXPORT void *get_module() {
// static(!) Php::Extension object that should stay in memory
// for the entire duration of the process (that's why it's static)
static Php::Extension extension("yourextension", "1.0");
extension.add("helloWorld", helloWorld);
// @todo add your own functions, classes, namespaces to the extension

// return the extension
return extension;
}
}

然后是想办法make了,我不怎么会写Makefile,而且之前的Bilibili的字符识别算法各种依赖都是在eclipse里面做的,这里摸索一番直接用g++命令行来link好了:

首先生成”captcha.cpp”的目标文件:

g++ -Wall -c -O2 -std=c++11 -fpic -o "captcha.o" "captcha.cpp"

首先生成”main.cpp”的目标文件:

g++ -Wall -c -O2 -std=c++11 -fpic -o main.o main.cpp

然后是link成动态链接库(注意把opencv的库依赖加上):

g++ -shared -o yourextension.so main.o captcha.o -lphpcpp -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_ml -lopencv_highgui

然后把生成的库安装一下:

cp -f yourextension.ini /etc/php5/conf.d
cp -f yourextension.so /usr/lib/php5/20090626

现在来写个test.php测试一下扩展:

<?php
echo helloWorld('1:'); 
echo helloWorld("2:");
echo helloWorld('3'); 
echo helloWorld('4'); 
echo helloWorld('5'); 
echo helloWorld('6'); 

echo "over!";

每次调用helloworld的时候,扩展会去/etc/php5/conf.d/img下读一张测试用的样本来识别,那么下面用php命令行跑一下(php命令行每次执行一个php文件都会去load一遍拓展,这一点和php-fpm不一样,我的这个扩展Release版本的话load时间测了一下是3s)

root@ubuntu:/home/gouchaoer/share/workshop/java__test/EmptyExtension/EmptyExtension# php test.php

这里是php的输出结果:

recog started:
2499M
Hello 1:!
recog started:
??88Q
Hello 2:!
recog started:

Hello 3!
recog started:
L2L?8
Hello 4!
recog started:
3GVPJ
Hello 5!
recog started:
XVR4J
Hello 6!

6张里面成功识别了3张,当然了现在不去管识别率,主要目的是测试扩展功能成功。

现在得想办法把拓展安装到生产环境里,我用的是阿里云的1G内存的单核的centos6.5,centos的epel源并没有gcc4.8.x,我一时也没有找到别的centos的源,所以去gcc官网下载gcc4.8.2自己编译安装的,当然了vps单核cpu速度很慢花了我不少时间。
然后和前面一样安装php-dev,centos里面该包的准确名字是php-devel.x86_64,yum install发现版本不对,我把php版本升级到了PHP 5.4.41版本来着,现在要重新编一个php肯定不划算,在stackoverflow上搜到一个比较暴力的办法,试了一下可行,虽然不知道为什么但是能跑就ok了……
update:上面那个stackoverflow的方法不行的,最后加载php扩展的时候会有这样的错误:

PHP Warning:  PHP Startup: Unable to load dynamic library '/usr/lib64/php/modules/yourextension.so' - /usr/lib/libphpcpp.so.1.4: undefined symbol: _ZN3Php9StreamBuf4syncEv in Unknown on line 0
libgcc_s.so.1 must be installed for pthread_cancel to work
Aborted

于是继续找php-devel,然后在remi源里找到了:http://pkgs.org/centos-6/remi-x86_64/
用remi来安装:yum –enablerepo=remi install php-devel
然后去make php-cpp就OK了……
总之make&&make install之后现在php-cpp已经安装完毕了。

把之前写好的源代码上传,make之后还有opencv依赖,于是继续安装opencv,然后又遇到个问题就是centos6.5(7.x倒是有)版本的package没有opencv2.4.x版本,于是又得自己下载opencv源码自己编译了……没办法把opencv的源码上传vps,安装了cmake开始了比较慢的编译opencv的过程。个人比较讨厌在vps上编译东西,因为以编译cpu就100%占用,上面的http服务基本就不可用了。开发环境ubuntu12.04和生产环境centos6.5不一致的确挺麻烦的,以后我还是统一把系统都换成centos7.x好了。

总之,编译然后安装好了opencv,这样所有环境都弄好了,然后上传扩展源码,和ubuntu12.04一样编译好扩展。安装的时候和ubuntu位置不一样的是,yourextension.ini在centos6.5的扩展位置在/etc/php.d,然后yourextension.so放在/usr/lib64/php/modules,安装好了之后ldconfig一下读入新的动态链接库配置。
现在命令行里用php测试一下,可以看到验证码识别功能是成功的,插件成功运行。

[root@iZ94fgh1ehiZ EmptyExtension]# php test.php
Testing captcha in yourextension.so
recog started:
recog started:
recog started:
2499M
Hello debug!
recog started:
??88Q
Hello debug!
recog started:

Hello debug!
recog started:
L2L?8
Hello debug!
recog started:
3GVPJ
Hello debug!
recog started:
XVR4J
Hello debug!
recog started:
XVR4J
Hello debug!
recog started:
2499M
Hello debug!

现在php-fpm并没有加载新的扩展,我重启php-fpm,然后试了一下试着通过http访问,结果也是成功的,原来php-fpm启动是瞬间的,加入扩展后加载时间也是瞬时启动的,然后第一次访问test.php(里面有对扩展函数的调用)的时候很慢。也就是说php-fpm并没在启动的时候加载所有扩展,第一次调用函数的时候才加载扩展。之前的扩展输出我们是用c++的std::out来输出到命令行(只有开发的时候这么做),也就是说这里什么也不会输出,回去把扩展std::out输出改成返回字符串到php。然后重启php-fpm之后,然后测试了一下http访问,和命令行一样得到了结果。

最后,讨论一下php线程安全的问题,我试过在php-cpp里面开新的线程,结果就是php执行到这个函数就崩溃了。另外由于同一时间可能有多个php调用那个接口函数,接口函数本身是可以重入的,主要在于我的识别算法class的那个方法并没有去改变class本身的属性,里面调用了opencv的Mat数据结构和一些函数,OpenCV的Mat和函数基本上都是thread-safe的,所以个人觉得这个接口是可重入thread-safe的。

把Bilibili的验证码识别算法用php-cpp写了个php扩展以及做了个API接口》有1个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注