Michel Anders - Написание скриптов для Blender 2.49
Конечно, никакой из методов не является абсолютно надёжным, и несомненно, вопросы Captcha не лишены недостатков, они не будут устойчивыми при доступных больших компьютерных мощностях, но они все еще остаются весьма эффективными. Хотя в настоящее время считается, что способы с простым размытием и окраской — задачи решенные, компьютерам все еще требуется серьёзное время на разделение индивидуальных символов в слове, когда они слегка перекрывают друг друга, в то время как для людей это не проблема.
Учитывая эти аргументы, это может быть отличным применением рендеринга текста в 3D, так как, предположительно, трехмерное представление слов при подходящих условиях освещения (то есть, резкие тени) даже труднее для интерпретации, чем двумерный текст. Наша задача тогда заключается в разработке сервера, который будет отвечать на запросы, чтобы сделать трехмерное изображение какого-нибудь текста.
Мы разработаем наш сервер как веб-сервер, который будет реагировать на запросы, адресованные ему как URL'ы в форме http:<hostname>:<port>/captcha?text=<sometext>, и который возвращает PNG-изображение - 3D-представление этого текста. Таким образом, будет легко внедрить этот сервер в архитектуру, в которой некоторое программное обеспечение, например блог, может легко использовать эту функциональность, просто подключаясь к нашему серверу через HTTP. Пример сгенерированного вопроса показан на иллюстрации:
Разработка сервера CAPTCHAПри использовании модулей, доступных в полном дистрибутиве Питона, задача создания сервера HTTP становится не такой уж пугающей, как может показаться. Наш сервер Captcha будет основан на классах, предоставленных модулем Питона BaseHTTPServer, так что мы начинаем с импорта этого модуля вместе с несколькими дополнительными модулями-утилитами:
import BaseHTTPServer
import re
import os
import shutil
Модуль BaseHTTPServer определяет два класса, которые вместе включают полную реализацию сервера HTTP. Класс BaseHTTPServer реализует основной сервер, который будет слушать поступающие HTTP-запросы на некотором сетевом порту, и мы используем этот класс, как есть.
При получении корректного HTTP-запроса BaseHTTPServer пошлет этот запрос обработчику запросов. Наша реализация такого обработчика запросов, основанная на BaseHTTPRequestHandler, довольно скудна, так как ожидается, что всё, что он будет делать - запрашивать поля GET и HEAD в форме captcha?text=abcd. Следовательно, всё мы должны сделать - переписать методы do_GET() и do_HEAD() базового класса.
От запроса HEAD ожидается возвращение только заголовков запрошенного объекта, а не содержимого, чтобы сохранять время, за которое содержимое не изменится со времени последнего запроса (что-то, что может быть определено проверкой заголовка Last-Modified). Мы игнорируем такую аккуратность; мы возвращаем заголовки именно тогда, когда мы получаем запрос HEAD, но мы, тем не менее, будем генерировать полностью новое изображение. Это в некоторой степени расточительно, но зато код будет простым. Если важна производительность, можно разработать другую реализацию.
Наша реализация начинается с определения метода do_GET(), который просто вызывает метод do_HEAD(), который будет генерировать вопрос Captcha и возвращать заголовки клиенту. do_GET(), впоследствии, копирует содержание файлового объекта, возвращённого методом do_HEAD() в выходной файл, такой как объект обработчика запроса (выделено), который в свою очередь возвращает это содержимое клиенту (например, браузеру):
class CaptchaRequestHandler(
BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
f=self.do_HEAD()
shutil.copyfileobj(f,self.wfile)
f.close()
Метод do_HEAD() сначала определяет, получили ли мы правильный запрос (то есть, URI в форме captcha?text=abcd), вызывая метод gettext() (выделено, определяется позже в коде). Если URI некорректен, метод gettext(), возвращает None и тогда do_HEAD() возвращает клиенту ошибку File not found (Файл не найден), вызывая метод send_error() базового класса:
def do_HEAD(self):
text=self.gettext()
if text==None:
self.send_error(404, "File not found")
return None
Если был запрошен корректный URI, фактическое изображение генерируется методом captcha(), который возвращает имя файла сгенерированного изображения. Если этот метод терпит неудачу по любой причине, клиенту возвращается Internal server error (Внутренняя ошибка сервера):
try:
filename = self.captcha(text)
except:
self.send_error(500, "Internal server error")
return None
Если все прошло хорошо, мы открываем файл изображения, отсылаем клиенту ответ 200 (показывающий успешную операцию), и возвращаем заголовок Content-type, устанавливающий, что мы возвращаем png-изображение. Затем мы используем функцию fstat() с номером handle открытого файла в качестве аргумента, чтобы извлечь длину сгенерированного изображения и вернуть её как заголовок Content-Length (выделено), сопроводив временем модификации и пустой строкой, означающей конец заголовков перед возвратом открытого файлового объекта f:
f = open(filename,'rb')
self.send_response(200)
self.send_header("Content-type", 'image/png')
fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs[6]))
self.send_header("Last-Modified",
self.date_time_string(fs.st_mtime))
self.end_headers()
return f
Метод gettext() проверяет, что запрос, передаваемый нашему обработчику запросов в переменной пути - правильный URI, сверяя его с регулярным выражением. Функция match() из модуля Питона re возвращает MatchObject (объект сопоставления), если регулярное выражение соответствует параметру, и None, если нет. Если есть соответствие, мы возвращаем содержание первой группы объекта сопоставления (символы, которые соответствуют выражению между круглыми скобками в регулярном выражении, в нашем случае значение текстового аргумента), в противном случае мы возвращаем None:
def gettext(self):
match = re.match(r'^.*/captcha?text=(.*)$',
self.path)
if match != None:
return match.group(1)
return None
Теперь мы добрались до задачи, специфичной для Блендера - сгенерировать рендеренный в 3D текст, который будет возвращён в виде png изображения. Метод captcha() принимает текст для рендера как аргумент, и возвращает имя файла сгенерированного изображения. Мы допускаем, что освещение и камера в .blend файле, в котором мы запускаем captcha.py, настроены правильно, чтобы удобочитаемо отображать наш текст. Следовательно, метод captcha() просто настраивает правильным образом объект Text3d и рендерит его.
Первая задача состоит в том, чтобы определить текущую сцену и проверить, присутствует ли объект с именем Text, который можно использовать заново (выделено). Заметьте, что вполне допустимо иметь другие объекты на сцене, чтобы ещё более затемнить отображение:
def captcha(self,text):
import Blender
scn = Blender.Scene.GetCurrent()
text_ob = None
for ob in scn.objects:
if ob.name == 'Text' :
text_ob = ob.getData()
break
Если не нашлось никакого ранее используемого объекта Text3d, создаём новый :
if text_ob == None:
text_ob = Blender.Text3d.New('Text')
ob=scn.objects.new(text_ob)
ob.setName('Text')
Следующий шаг - установить текст объекта Text3d в значение аргумента, переданного в метод captcha(), и сделать его трёхмерным, настроив глубину выдавливания. Мы также изменяем ширину символов и сокращаем расстояние между ними, чтобы ухудшить разделение. Добавление небольшого скоса (bevel) смягчит контуры символов, что может добавить трудностей для робота, различающего символы, если настроено искусное освещение (выделено). Мы могли бы решить использовать другой шрифт для нашего текста, который ещё труднее для чтения ботом, и здесь как раз место для установки такого шрифта (смотри следующий информационный блок).
Чего-то не хватает
Документация API Блендера имеет небольшой пропуск: как будто не существует способа настроить другой шрифт для объекта Text3d. Тем не менее, есть недокументированный метод setFont(), который принимает объект Font в качестве аргумента. Код, выполняющий изменение шрифта должен выглядеть похожим на это: