http://habrahabr.ru/post/265123/
Да да, в этой статье будет описана попытка научить компьютер детектировать adult изображения.
В качестве инструментов используется python, opencv и scikit-learn.
На выборке из 2500 примеров удалось получить точность около 90%.
Под катом вы найдёте описание подхода c примерами кода.
Описание
Для начала необходимо получить обучающую выборку — набор изображений, которые относятся к порно и набор любых других изображений. Я получил её следующим образом — обычные картинки взял из выдачи google и yandex фото, а так же с flickr, воспользовавшись его api поиска изображений со свободными лицензиями. Порно под свободной лицензией мне найти не удалось поэтому пришлось воспользовать thepiratebay-ем и произведениями неизвестных авторов с неизвестной лицензией. Вначале я собрал разметку из 1000 изображений (по 500 каждой категории), потом увеличил до 2500.
Разметка есть — теперь можно приступать к обучению. Первым шагом — необходимо преобразовать изображения в наборы признаков. Один из вариантов — взять непосредственно значения яркости в каждом пикселе изображения. Но в этом случае обучиться по этим признаком будет тяжело — их количество огромно а полезная информация в каждом из них не велика. Поэтому на практике часто используют другие подходы. На изображении пытаются найти ключевые точки — такие точки, которые представляют собой наибольшей интерес. Это могут быть углы, резкие смены цвета, линии или что-то ещё. После того как точки обнаружены — в их окрестности считаются дескрипторы. Дескриптор — это компактное представление этой точки (а точнее того, что содержится в небольшом радиусе вокруг неё). Основные требования к ключевым точкам и дескрипторам — устойчивость к масштабированию и повороту изображений. Сейчас существуют различные алгоритмы определения ключевых точек и дескрипторов. Мы воспользуемся алгоритмом SIFT, но можно взять и какой-то другой — SIFT является запатенованным и использовать его в США для коммерческих целей может быть затруднительно.
На следующем шаге среди дескрипторов, извлеченных из всех изображений в нашей выборке, необходимо найти похожие. Для этого воспользуемся одним из алгоритмов кластеризации, к примеру K-Means. Он позволяет объеденить огромную кучу дескрипторов (в среднем — около 200 c каждого изображения, а всего 2500 изображений — итого 500 000 дескрипторов) в заданное количество групп с похожими дескрипторами (например, 1000). После выполнения этой операции для любого дескриптора мы можем сказать — в какую именно группу он попадёт.
Для каждой из картинок мы получаем массив из 1000 чисел. На первом месте — количество встретившихся на картинке дескрипторов, принадлежащих первой группе, на втором — второй, и т. д. Этот массив мы и будем использовать в качестве признаков. В качестве классификатора будем использовать наивный байесовский классификатор — он довольно хорошо работает с большим числом признаков, большая часть из которых нулевые. Его частенько используют для классификации текстов вместе с моделью bag of words. В нашем же случае подход такой же как и с текстами — только вместо слов — дескрипторы. Но применять байесовский классификатор непосредственно к частотам встречаемости — не самая лучшая идея. Одни дескрипторы могут встречаться на всех картинках, в то время как другие могут встречаться намного реже, но зато быть намного полезнее для решаемой задачи. Для того, чтобы правильней оценить важность признака можно воспользовать мерой tf-idf. В ней вес некоторого слова (для нашей задачи — дескриптора) пропорционален количеству употребления этого слова в документе (для нашей задачи — изображении), и обратно пропорционален частоте употребления слова в других документах коллекции.
После обучения модели и проверки точности на тестовой выборке (например, взяв 80% данных для обучения и 20% для теста) получаем точность около 70%. Попробуем улучшить. Дескрипторы SIFT по умолчанию не используют цветовую информацию. Но есть способы всё же извлечь цветовую информацию для SIFT дескрипторов. Один из них — OpponentSIFT. Его суть в следующем. Строится четыре версии картинки — черно-белая и три слоя в opponent цветах (oponnent цвета — один из варинтов представления цвета, аналог rgb и hsv но более близкий человеческом зрению). Ключевые точки по прежнему ищутся в черно-белом изображении. После этого для каждой из точек извлекается три дескриптора — по одному на канал в opponent цветах. Затем эти три дескриптора соединяют в один длинный.
Воспользовавшись OpponentSIFT получившаяся точность будет около 80%.
Пробуем улучшить дальше. Один классификатор хорошо, а серия — лучше, поэтому пробуем бустинг (а точнее — AdaBoost). Бустинг — подход к построению серии однотипных классификаторов, в которых каждый следующий учится на ошибках предыдущего. Применив бустинг получим результат около 85% процентов.
Среди примеров ошибочно определенных изображений много таких, которые можно было бы отсеять по цветовой гамме. Несмотря на то, что мы использовали цвет в дескрипторах — про общую цветовую гамму наш детектор ничего не знает. Поэтому следующим шагом будем пытаться построить другую модель, используя в качестве признаков не дескрипторы, а цветовую гистограмму. Преобразуем изображение в hsv и воспользуемся каналом hue для подсчёта гистограммы (количества встречаемости каждого из 256 цветовых оттенков).
В итоге, мы получим массив из 256 чисел (количеств встречаемости, по одному числу на цевт — аналогично 1000 группам дескрипторов).
Проделываем с ним те-же самые операции — tfidf, байесовский классификатор, бустинг. Качество такого цветового классификатора будет примерно 80%.
Теперь у нас есть два детектора — один по ключевым точкам, с точностью 85% и второй по цветовой гистограмме — с точностью 80%. Склеим из них один, но крутой! Алгоритм следующий. Если оба детектора выдали одинаковое предсказание — верим им и сразу выдаём ответ. Если они выдали разные предсказания — верим тому из них, кто больше уверен в своём предсказании (тому, кто выдал большую вероятность).
Такой общий детектор будет иметь точность около 90%!
На этом пока остановимся, но вообще 90% не предел и можно пытаться улучшать и дальше. Например, добавить классификаторов, которые будут выискивать на картинах конкретные элементы.
Реализация
Для реализации детектора нам понадобится python, opencv и scikit-learn. Я разрабатывался под ubuntu и использовал python2.7, scikit-learn0.16.1 и opencv2.4. opencv пришлось собрать самому т. к. в версии из репозитория отсутствовали sift дескрипторы.
И так — начнём с открытия файла и вычисления дескрипторов:
img = cv2.imread(fileName)
if img.shape[1] > 1000:
cf = 1000.0 / img.shape[1]
newSize = (int(cf * img.shape[0]), int(cf * img.shape[1]), img.shape[2])
img.resize(newSize)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
s = cv2.SIFT(nfeatures = 400)
d = cv2.DescriptorExtractor_create("OpponentSIFT")
kp = s.detect(gray, None)
kp, des = d.compute(img, kp)
Тут мы открываем файл, ресайзим его (на файлах с большим разрешением дескрипторы считаются долго и едят много оперативки), трансформируем в черно-белое и вычисляем дескрипторы (сами точки находим по черно-белому, а значения дескрипторов вычисляем уже по цветному.
Дальше считаем цветовую гистограмму для второго классификатора:
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
dist = cv2.calcHist([hsv],[0],None,[256],[0,256])
Переводим картинку в hsv и по каналу hue считаем гистограмму.
Для дальнейшего процесса нам понадобится создать все необходимые классификаторы:
kmeans = MiniBatchKMeans(n_clusters = CLUSTERS_NUMBER, random_state = CLUSTER_SEED, verbose = True)
tfidf = TfidfTransformer()
tfidf1 = TfidfTransformer()
clf = AdaBoostClassifier(MultinomialNB(alpha = BAYES_ALPHA), n_estimators = ADA_BOOST_ESTIMATORS)
clf1 = AdaBoostClassifier(MultinomialNB(alpha = BAYES_ALPHA), n_estimators = ADA_BOOST_ESTIMATORS)
kmeans — для кластеризации, TfidfTransformer, MultinomialBN, AdaBoostClassifier — для классификации.
Теперь можно загружать обучающую выборку и приступать к обучению:
positiveSamples = loadSamples(positiveFiles)
negativeSamples = loadSamples(negativeFiles)
totalDescriptors = []
addDescriptors(totalDescriptors, positiveSamples)
addDescriptors(totalDescriptors, negativeSamples)
kmeans.fit(totalDescriptors)
clusters = kmeans.predict(totalDescriptors)
totalSamplesNumber = len(negativeSamples) + len(positiveSamples)
counts = lil_matrix((totalSamplesNumber, CLUSTERS_NUMBER))
counts1 = lil_matrix((totalSamplesNumber, 256))
calculteCounts(positiveSamples, counts, counts1, clusters)
calculteCounts(negativeSamples, counts, counts1, clusters)
counts = csr_matrix(counts)
counts1 = csr_matrix(counts1)
_tfidf = tfidf.fit_transform(counts)
_tfidf1 = tfidf1.fit_transform(counts1)
classes = [True] * len(positiveSamples) + [False] * len(negativeSamples)
clf.fit(_tfidf, classes)
clf1.fit(_tfidf1, classes)
Вначале загружаются положительные и отрицательные примеры. Функция addDescriptors просто извлекает из них дексрипторы в список totalDescriptors. totalDescriptors — это просто куча всех дескрипторов с картинок из всей коллекции — они нужны нам для кластеризации (kmeans.fit).
kmeans.predict возвращает нам список кластеров — какому дескриптору какой кластер соответствует.
Дальше создаём две матрицы, и считаем в них частоту встречаемости. В первую — сколько раз встретились дескрипторы из каждого класстера для каждой картинки. Во вторую — сколько раз встретился каждый цвет для каждой картинки.
Дальше преобразовываем наши матрицы используя tfidf (tfidf.fit_transform) и обучаем бустнутый байесовский классификатор (clf.fit). Для разных детекторов (по дескрипторам и по цвету) — обучаем разные классификаторы (clf и clf1).
На этом обучение окончено. Теперь можно сохранить нашу модель в файл чтобы потом применить её для предсказаний. Для этого воспользуемся стандартным python модулем pickle:
data = pickle.dumps((CLUSTERS_NUMBER, kmeans, tfidf, tfidf1, clf, clf1), -1)
data = zlib.compress(data)
open(fileName, 'w').write(data)
Процесс предсказания похож на обучение, только теперь мы используем один набор изображений (тестовый), а не два (положительные и отрицательные примеры). Кроме этого — мы выбираем какому из классификаторов верить.
samples = loadSamples(files)
totalDescriptors = []
addDescriptors(totalDescriptors, samples)
clusters = kmeans.predict(totalDescriptors)
counts = lil_matrix((len(samples), CLUSTERS_NUMBER))
counts1 = lil_matrix((len(samples), 256))
calculteCounts(samples, counts, counts1, clusters)
counts = csr_matrix(counts)
counts1 = csr_matrix(counts1)
_tfidf = tfidf.transform(counts)
_tfidf1 = tfidf1.transform(counts1)
weights = clf.predict_log_proba(_tfidf)
weights1 = clf1.predict_log_proba(_tfidf1)
predictions = []
for i in xrange(0, len(weights)):
w = weights[i][0] - weights[i][1]
w1 = weights1[i][0] - weights1[i][1]
pred = w < 0
pred1 = w1 < 0
if pred != pred1:
pred = w + w1 < 0
predictions.append(pred)
На этом всё. Полные исходники вы можете посмотреть на
гитхабе. Разметку выложить не могу, т. к. не уверен что это не нарушит лицензии. Возможно выложу обученную модель, если кому-то пригодится.