测试地址:http://face.kkk5.com

最近微软推出的年龄识别软件可谓是火爆了朋友圈,听说好像是通过识别脸上皱纹来判断年龄的,而我,通过抓取知乎100万用户也小小火了一把,于是我想继续发掘PHP的潜能,看看有没有更多的可能性,做一个有趣的东西出来。别人识别年龄、美颜、变老,我要不贴贴胡子试试?!于是,PHP的贴胡子程序就开始了。。。

要给脸上贴胡子,首先当然得把脸找出来,于是我找到了opencv,一个开源的人脸识别类库,还有PHP扩展,听说国内那些android上面的人脸识别,都是用这个搞的。。。

先来试试识别人脸,用的是 haarcascade_frontalface_alt.xml 配置文件

Array ( [0] => Array ( [x] => 115 [y] => 190 [w] => 278 [h] => 278 ) )

不错,人脸一下子就识别出来了,但是这样也太不雅观了,把这些数字变成线条试试:

接下来看看鼻子,用的是 haarcascade_mcs_nose.xml 配置文件

不得不说小猪的脸长得是够标致的,一下就识别出鼻子来了,不信我们来试试范爷的脸:

看范爷这张比较标致的脸,都能找出3个鼻子来,更别说我这种能找出5个鼻子的脸了~_~

再来看看嘴巴,用的是 haarcascade_mcs_mouth.xml 配置文件

    

好吧我承认 opencv 真的弱爆了,小猪居然有8个嘴巴,范爷有18个,他两是妖精么?!

不得不说,opencv 如果单单这么用,真的是完全没法用,所以接下来才是重点,我要把真正的鼻子和嘴巴找出来,然后在他们中间贴上胡子,为了完成这艰巨的任务,我采用了排除法。。。^_^

首先鼻子的中线应该离得脸的中线最近,于是我们要先算出脸和鼻子的中线来,这样看貌似又很难看得出数值,于是我把调试模式再次升级。。。

嘿嘿,是不是有种黑客帝国的感觉^_^

经过计算排除,我们最终得到了范爷准确的脸和鼻子

接下来是嘴巴,范爷有18个嘴巴而小猪有8个

    

这毛毛多得嘴巴啊,看着都吓人,好吧说说我的算法把,排除嘴巴的算法是这样的:

1、脸蛋上边、左上角、上边的嘴巴排除掉

2、超出脸蛋下边的嘴巴排除掉

3、超出脸蛋右边的嘴巴排除掉

4、最左边和最右边已经超过鼻子的中间竖线了的排除

5、最上边已经超过鼻子的中间横线了的排除掉

6、鼻子上边的嘴巴排除

7、下边线已经在脸以下了的排除掉

经过这几部算法以后,我们准确的得到了小猪和范爷的脸蛋、鼻子、嘴巴

    

哈哈是不是干净多了,接下来是重点了,贴胡子。。。

贴胡子的思路是这样的,找到鼻子和嘴巴的中间线,计算出胡子的中间线从而推导胡子的Y轴坐标,以鼻子的X轴坐标作为胡子的X轴坐标,当然如果你歪鼻子或者歪嘴巴的话,那还是不要贴了吧^_^,好吧下面是算法:

1、获得胡子的宽高

2、以嘴巴的1.8倍宽为胡子的宽度,为什么是1.8倍?因为我试了100个胡子,觉得1.8倍是最合适的呀^_^

3、缩放胡子图片大小

4、计算胡子坐标:

X轴坐标 = 鼻子的X轴坐标 - 胡子宽度 / 2;

鼻子到嘴巴的中间坐标 = 鼻子的中间坐标 + ( 嘴巴的中间坐标 - 鼻子的中间坐标 ) / 2

Y轴坐标 = 鼻子到嘴巴的中间坐标 - 缩放后的胡子高度 / 2;

5、贴上胡子,大功告成!!!

    

接下来我们把烦人的调试信息去掉。。。

    

是不是很酷哈,当然聪明的你可能早已发现,他两的胡子不一样?!对了,这才是重点,为了逗你们开心,我做了100套胡子,想不想看看这些胡子的表现如何?

不得不说,一个男人拥有了胡子,更加性感迷人有木有,如果可以每天和换衣服一样换胡子,岂不是美哉?

好吧如果你以为这就完了,那你就错了,再好的技术也少不了包装,于是,我给这个程序穿上了一件华丽的衣衫。。。

好吧,软件到这里就设计完了,有兴趣的朋友可以上去玩玩,看看能匹配出什么样的胡子来哈,忘了说现在胡子是随机的,后面我们改进算法,给不同脸型的人不同的胡子匹配,觉得好玩的帮忙转发微信朋友圈。。。^_^

测试地址:http://face.kkk5.com


很久没有用 Wireshark 查看包工具了,今天一安装打开就一闪而过,在命令行下打开就报下面的错误:

2015-08-13 20:28:32.489 defaults[11089:151665]

The domain/default pair of (kCFPreferencesAnyApplication, AppleAquaColorVariant) does not exist

2015-08-13 20:28:32.497 defaults[11090:151670]

The domain/default pair of (kCFPreferencesAnyApplication, AppleHighlightColor) does not exist

(process:11072): Gtk-WARNING **: Locale not supported by C library.

Using the fallback 'C' locale.

(wireshark-bin:11072): Gtk-WARNING **: cannot open display:

上面的错误之后最后一句是重点,其他都是普通警告,最后一句是说Wireshark不能运行在当前的display,解决办法很简单

vim /Applications/Wireshark.app/Contents/Resources/bin/wireshark

在最前面添加下面这行即可:

export DISPLAY=:0.0


要判断客户端是否使用代理服务器,可以从客户端所发送的环境变量信息来判断。

具体来说,就是看HTTP_VIA字段,如果这个字段设置了,说明客户端使用了代理服务器。

匿名级别可以参考下表来判断。

给出一个应用例子,可以挂上代理试试效果: http://ip.epooll.com/

一、没有使用代理服务器的情况:

REMOTE_ADDR = 您的 IP

HTTP_VIA = 没数值或不显示

HTTP_X_FORWARDED_FOR = 没数值或不显示

二、使用透明代理服务器的情况:Transparent Proxies

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 代理服务器 IP (补充:这个字段由代理服务器填充,有时会填充网关信息等)

HTTP_X_FORWARDED_FOR = 您的真实 IP

这类代理服务器还是将您的信息转发给您的访问对象,无法达到隐藏真实身份的目的。

三、使用普通匿名代理服务器的情况:Anonymous Proxies

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 代理服务器 IP (补充:这个字段由代理服务器填充,有时会填充网关信息等)

HTTP_X_FORWARDED_FOR = 代理服务器 IP

隐藏了您的真实IP,但是向访问对象透露了您是使用代理服务器访问他们的。

四、使用欺骗性代理服务器的情况:Distorting Proxies

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 代理服务器 IP (补充:这个字段由代理服务器填充,有时会填充网关信息等)

HTTP_X_FORWARDED_FOR = 随机的 IP

告诉了访问对象您使用了代理服务器,但编造了一个虚假的随机IP代替您的真实IP欺骗它。

五、使用高匿名代理服务器的情况:High Anonymity Proxies

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 没数值或不显示

HTTP_X_FORWARDED_FOR = 没数值或不显示

完全用代理服务器的信息替代了您的所有信息,就象您就是完全使用那台代理服务器直接访问对象。

除此之外,可以通过proxy judges总 结其他一些可供参考的判定信息,一遍于在实践中加以利用。

最后写一个php例子,仅供大家参考:

if(!empty($_SERVER['HTTP_VIA']))    //使用了代理
{
    if(!isset($_SERVER['HTTP_X_FORWARDED_FOR']))
    {
        //Anonymous Proxies    普通匿名代理服务器
        //代理IP地址为 $_SERVER['REMOTE_ADDR']
    }
    else
    {
        //Transparent Proxies 透明代理服务器
        //代理IP地址为 $_SERVER['REMOTE_ADDR']
        //真实ip地址为 $_SERVER['HTTP_X_FORWARDED_FOR']
    }
}
else    //没有代理或者是高匿名代理
{
    //真实ip地址为 $_SERVER['REMOTE_ADDR']
}

如果不动态指定超时时间,file_get_contents 默认超时时间是受 default_socket_timeout 这个 php.ini 属性影响,也就是60秒,所有 php-fpm 就会被堵塞死。

一、增加超时限制

注意:set_time_limit 只是设置你的PHP程序的超时时间,而不是 file_get_contents 函数读取 URL 的超时时间。

我一开始以为 set_time_limit 也能影响到 file_get_contents,后来经测试是无效的。真正的修改 file_get_contents 延时可以用 resource $context 的 timeout 参数:

$opts = array(
    'http'=>array(
        'method'=>'GET',
        'timeout'=>60,
    )
);
$context = stream_context_create($opts);
$html = file_get_contents('http://www.example.com', false, $context);
fpassthru($fp);  // 不知道是谁哈?

二、多次尝试

$cnt = 0;
while($cnt < 3 && ($str=@file_get_contents('http...'))===FALSE)
{
    $cnt++;
}

三、POST请求

以上方法对付超时已经OK了。接下来演示一下用 file_get_contents 实现 Post,如下:

function Post($url, $post = null)
{
    if (is_array($post)) 
    {
        ksort($post);
    }
    $context['http'] = array (
        'method'  => 'POST',
        'timeout' => 60,
        'content' => http_build_query($post, '', '&'),
    );
    
    return file_get_contents($url, false, stream_context_create($context));
}

$data = array (
    'name' => 'test',
    'email' => 'test@gmail.com',
    'submit' => 'submit',
 );

echo Post('http://www.example.com', $data);

四、提交Http头

$context['http'] = array (
    'method' => 'GET',
    'header' => "Accept-language: en\r\n" .
          "Cookie: foo=bar\r\n",
);

// 注意:Http头每个字段以 \r\n 结尾。

一次完整的http请求,一般包含三个步骤:

  1. 通过DNS把域名解析成IP
  2. 通过IP地址连接到目标主机
  3. 获取目标主机数据(1、给目标主机输出http请求头,以\r\n\r\n结尾;2、获取目标主机传过来的数据)

php 的 curl 对以上三个步骤都有设置超时时间的方法

1、保存DNS信息时间

CURLOPT_DNS_CACHE_TIMEOUT 设置在内存中保存DNS信息的时间,默认为120秒。

2、连接超时时间

CURLOPT_CONNECTTIMEOUT 以秒为单位。如果设置为0,则无限等待。

CURLOPT_CONNECTTIMEOUT_MS 以毫秒为单位。如果设置为0,则无限等待。

3、执行超时时间( DNS解析+连接+提交请求数据+获取请求数据? 或是 提交请求数据+获取请求数据 ?)

CURLOPT_TIMEOUT 设置cURL允许执行的最长秒数。

CURLOPT_TIMEOUT_MS 设置cURL允许执行的最长毫秒数。 

注意:

1、毫秒级设置在cURL 7.16.2中被加入。从PHP 5.2.3开始可用的。如果需要进行毫秒超时,需要增加:

curl_easy_setopt(curl, CURLOPT_NOSIGNAL,1L);

//或者

curl_setopt ( $ch, CURLOPT_NOSIGNAL,true);

2、cURL的超时既是socket的超时,因为底层就是socket,所以cURL的默认时间可以在 php.ini 里面设置

default_socket_timeout = 60

3、提交请求数据包括header和body,header和body之间用 \r\n\r\n 隔开,获得请求数据一样。这是http的规定。