2012-01
20

OpenCV的人脸检测功能在一般场合还是不错的。而ubuntu正好提供了python-opencv这个包,用它可以方便地实现人脸检测的代码。

写代码之前应该先安装python-opencv:

$ sudo apt-get install python-opencv

具体原理就不多说了,可以参考一下这篇文章。直接上源代码。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# face_detect.py

# Face Detection using OpenCV. Based on sample code from:
# http://python.pastebin.com/m76db1d6b

# Usage: python face_detect.py <image_file>

import sys, os
from opencv.cv import *
from opencv.highgui import *
from PIL import Image, ImageDraw
from math import sqrt

def detectObjects(image):
    """Converts an image to grayscale and prints the locations of any faces found"""
    grayscale = cvCreateImage(cvSize(image.width, image.height), 8, 1)
    cvCvtColor(image, grayscale, CV_BGR2GRAY)

    storage = cvCreateMemStorage(0)
    cvClearMemStorage(storage)
    cvEqualizeHist(grayscale, grayscale)

    cascade = cvLoadHaarClassifierCascade(
        '/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml',
        cvSize(1,1))
    faces = cvHaarDetectObjects(grayscale, cascade, storage, 1.1, 2,
        CV_HAAR_DO_CANNY_PRUNING, cvSize(20,20))

    result = []
    for f in faces:
        result.append((f.x, f.y, f.x+f.width, f.y+f.height))

    return result

def grayscale(r, g, b):
    return int(r * .3 + g * .59 + b * .11)

def process(infile, outfile):

    image = cvLoadImage(infile);
    if image:
        faces = detectObjects(image)

    im = Image.open(infile)

    if faces:
        draw = ImageDraw.Draw(im)
        for f in faces:
            draw.rectangle(f, outline=(255, 0, 255))

        im.save(outfile, "JPEG", quality=100)
    else:
        print "Error: cannot detect faces on %s" % infile

if __name__ == "__main__":
    process('input.jpg', 'output.jpg')

检测结果:

face_detect_output.jpg

2011-07
13

最近发现用jQuery实现的动画在chrome下会有暴走的问题。例如下面这个页面, 打开后红色方块每隔三秒向右移动20像素。此时开一个新的Tab,把这个页面放在后台Tab上等待30秒, 然后切换回该页面,就会看到红方块连续向右移动了200像素。

<!doctype html>
<html>
<head>
<script language="javascript" src="jquery-1.6.1.min.js"></script>
<style>
#box {
  position: absolute; background: #f00;
  width: 20px; height: 20px; top: 100px; left: 20px;
}
</style>
<script>
$(document).ready(function() {
  var left = 1;
  var move = function() {
    left++;
    $("#box").animate({ left: 20 * left }, 200);
    setTimeout(move, 3000);
  };
  setTimeout(move, 3000);
});
</script>
</head>
<body>
  <div id="box"></div>
</body>
</html>

该现象在Chrome 12上可以复现,而Firefox 3.6和IE8上无此问题。可能是Chrome为了提高性能, 后台Tab不会执行动画效果,而切换回Tab的瞬间,积压的动画效果一下子执行,导致动画暴走。

解决方法就是在执行animate之前先stop()一下:

    $("#box").stop().animate({ left: 20 * left }, 200);

这样,虽然切换Tab的瞬间还是能看到一些残像,但比之前要好多了。


2011-07
06

django的错误页面是非常赞的,而且它还有个功能,就是将 settings.py 中的 DEBUG 设置为 False 时, 500错误会自动发送到 ADMINS 中设置的邮件地址(文档)。 这样即使在正式环境中关闭了DEBUG,也能通过邮件监视服务器错误。

不过这里面有个陷阱:万一邮件设置有误怎么办?django默认使用SMTP协议发送邮件,默认服务器地址为 localhost:25。 如果与服务器环境不一致,就要改变django的邮件设置(文档)。因此完整的设置如下:

DEBUG = False

ADMINS = (
    ('admin', 'admin@yoursite.com'),
)

EMAIL_HOST = 'smtp.yoursite.com'
EMAIL_PORT = 25
EMAIL_HOST_USER = 'your_account'
EMAIL_HOST_PASSWORD = 's3cret'

2011-07
05

上回说到了用经纬度范围实现附近地点搜索。 一些小型应用中这样做没问题,但在大型应用中它有个显著的缺点:速度慢。慢的原因有两个, 第一是范围比较的索引利用率并不高,第二是SQL语句极其不稳定(不同的当前位置会产生完全不同的SQL查询),很难缓存。

可以考虑使用geohash算法。

geohash是一种地址编码,它能把二维的经纬度编码成一维的字符串。比如,北海公园的编码是wx4g0ec1。

geohash-intro-01.png

geohash有以下几个特点:

首先,geohash用一个字符串表示经度和纬度两个坐标。某些情况下无法在两列上同时应用索引 (例如MySQL 4之前的版本,Google App Engine的数据层等),利用geohash,只需在一列上应用索引即可。

其次,geohash表示的并不是一个点,而是一个矩形区域。比如编码wx4g0ec19,它表示的是一个矩形区域。 使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。

第三,编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。 这个特性可以用于附近地点搜索。首先根据用户当前坐标计算geohash(例如wx4g0ec1)然后取其前缀进行查询 (SELECT * FROM place WHERE geohash LIKE 'wx4g0e%'),即可查询附近的所有地点。

geohash-intro-02.png
阅读全文 »
2011-06
27

RabbitMQ大家应该不陌生,著名的消息队列嘛。可惜我最近才听说它的大名,了解之后不禁惊呼,世界上居然还有这种东西! 立刻觉得手里有了锤子,就看什么都是钉子了,主网站不愿意干的操作统统扔给RabbitMQ去做吧 :D

言归正传,先介绍一下这篇文章的应用场景吧。我们知道大型网站的性能非常重要,然而有时不得不做一些相当耗时的操作。 比如SNS网站的“新鲜事儿”系统,我发帖之后,会给所有关注我的人推送一条通知。乍一看没什么难的,发帖之后找出关注我的人, 然后生成相应的消息记录就行了。但问题是,100个人关注我,就要执行100条INSERT查询,更要命的是,Web服务器是同步的, 这100条查询执行完成之前,用户是看不到结果的。

怎么办呢,这时就轮到消息队列上场了。发帖之后只需给队列发送一条消息, 告诉队列“我发帖子了”,然后把发帖的结果返回给用户。 这时另一个叫做worker的进程会取出这条消息并执行那100条INSERT查询。这样,推送通知的操作在后台异步执行, 用户就能立即看到发帖结果。更精彩的是,可以运行多个worker实现分布式,多繁重的任务都不在话下了。

好了,来看看今天的主角:

  • django:web框架,其实只能算作配角了;
  • RabbitMQ:消息队列系统,负责存储消息;
  • celery:worker进程,同时提供在webapp中创建任务的功能。
django-celery-rabbitmq-intro-1.png
阅读全文 »
2011-06
17

附近地点搜索,顾名思义,就是搜索用户附近有哪些地点。随着GPS和带有GPS功能的移动设备的普及, 附近地点搜索也变得炙手可热。不过在网上却很少有这方面的讨论。本文的方法并不算最好, 但足以应付一般的应用了。

本文中,数据库采用MySQL,语言采用python。理论上别的数据库和语言也没问题, 但我们要在经纬度上设置两个索引,所以如果你的数据库不支持索引,或者不支持在一个查询中使用两个索引, 那就只能想别的办法了。

阅读全文 »
2011-05
31

有时需要在JSON中使用浮点数,比如价格、坐标等信息。但python中的浮点数相当不准确, 例如下面的代码:

#!/usr/bin/env python

import json as json

data = [ 0.333, 0.999, 0.1 ]
print json.dumps(data)

输出结果如下:

$ python floatjson.py
[0.33300000000000002, 0.999, 0.10000000000000001]

能不能指定浮点数的输出格式,比如精确到小数点后两位呢?有个简单的方法,虽然比较dirty:

#!/usr/bin/env python

import json
json.encoder.FLOAT_REPR = lambda x: format(x, '.3f')

data = [ 0.333, 0.999, 0.1 ]
print json.dumps(data)

这样输出结果为:

$ python floatjson.py 
[0.333, 0.999, 0.100]

2011-05
25

用pdb单步调试django其实很方便,至少比调试mod_perl方便多了。只需打开views.py,在你想调试的view函数开头添加:

def myview(request):
  import pdb; pdb.set_trace()
  # other ...

然后启动服务器:

$ python manager.py runserver

用浏览器访问要调试的视图,就能在控制台上调试了。


2011-05
24

从python 2.6开始,用户可以设置自己的site-packages目录,该目录位于:

~/.local/lib/python2.6/site-packages/

这样,即使没有root权限,也可以随意安装python软件包了。方法如下:

$ mkdir -p ~/.local/lib/python2.6/site-packages/
$ easy_install --prefix=~/.local -U django-sentry

2011-04
27

今天在MacBookPro上安装wordpress时,安装程序一直报错说连不上数据库。mysql客户端可以正常使用,可以确定不是服务器的问题。写了个php脚本单独执行mysql_connect(),发现错误信息居然是“No such file or directory"!这里应该没涉及到文件啊?

在网上搜了一下,找到了这篇文章:mysql_connect and No such file or directory。原来,我的apache/php是mac系统自带的,而mysql是通过MacPorts安装的,它的本地socket设置与默认的不一样,导致php无法找到mysql的socket文件。解决方法上面那篇文章也给了,这里简单翻译一下:

  1. 首先确定是mysql_connect()和mysql_pconnect()的问题,故障现象就是函数返回空,而mysql_error()返回"No such file or directory"。
  2. 写个phpinfo页面,找到mysql.default_socket、mysqli.default_socket、pdo_mysql.default_socket。
  3. 启动mysql,执行命令 STATUS; 记下UNIX socket的值。
  4. 如果2和3的值不一样,则打开php.ini(可以从phpinfo页面中找到php.ini的位置,默认是/private/etc/php.ini),将2中提到的三个配置项的值改成3的值。
  5. 重启apache。