Детектор кое-каких картинок на python

от автора

Да да, в этой статье будет описана попытка научить компьютер детектировать adult изображения.
В качестве инструментов используется python, opencv и scikit-learn.
На выборке из 2500к примеров удалось получить точность около 90%.
Под катом вы найдёте описание подхода c примерами кода.

Описание

Для начала необходимо получить обучающую выборку — набор изображений, которые относятся к порно и набор любых других изображений. Я получил её следующим образом — обычные картинки взял из выдачи google и yandex фото, а так же с flickr, воспользовавшись его api поиска изображений со свободными лицензиями. Порно под свободной лицензией мне найти не удалось поэтому пришлось воспользовать thepiratebay-ем и произведениями неизвестных авторов с неизвестной лицензией. Вначале я собрал разметку из 1000 изображений (по 500 каждой категории), потом увеличил до 2500.

Разметка есть — теперь можно приступать к обучению. Первым шагом — необходимо преобразовать изображения в наборы признаков. Один из вариантов — взять непосредственно значения яркости в каждом пикселе изображения. Но в этом случае обучиться по этим признаком будет тяжело — их количество огромно а полезная информация в каждом из них не велика. Поэтому на практике часто используют другие подходы. На изображении пытаются найти ключевые точки — такие точки, которые представляют собой наибольшей интерес. Это могут быть углы, резкие смены цвета, линии или что-то ещё. После того как точки обнаружены — в их окрестности считаются дескрипторы. Дескриптор — это компактное представление этой точки (а точнее того, что содержится в небольшом радиусе вокруг неё). Основные требования к ключевым точкам и дескрипторам — устойчивость к масштабированию и повороту изображений. Сейчас существуют различные алгоритмы определения ключевых точек и дескрипторов. Мы воспользуемся алгоритмом SIFT, но можно взять и какой-то другой — SIFT является запатенованным и использовать его в США для коммерческих целей может быть затруднительно.

image

На следующем шаге среди дескрипторов, извлеченных из всех изображений в нашей выборке, необходимо найти похожие. Для этого воспользуемся одним из алгоритмов кластеризации, к примеру K-Means. Он позволяет объеденить огромную кучу дескрипторов (в среднем — около 200 c каждого изображения, а всего 2500 изображений — итого 500 000 дескрипторов) в заданное количество групп с похожими дескрипторами (например, 1000). После выполнения этой операции для любого дескриптора мы можем сказать — в какую именно группу он попадёт.

image

Для каждой из картинок мы получаем массив из 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) 

На этом всё. Полные исходники вы можете посмотреть на гитхабе. Разметку выложить не могу, т. к. не уверен что это не нарушит лицензии. Возможно выложу обученную модель, если кому-то пригодится.

ссылка на оригинал статьи http://habrahabr.ru/post/265123/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *