← Назад к вопросам

Как реализовано определение соответствия фотографии цензурным требованиям с точки зрения machine learning?

2.7 Senior🔥 41 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Определение соответствия фотографии цензурным требованиям (NSFW)

Рассмотрю методы и подходы на практике для определения NSFW (Not Safe For Work) контента.

1. Основные подходы

Есть несколько стратегий:

  1. Готовые API (простой способ)
  2. Предтренированные модели (быстро, достаточно хорошо)
  3. Fine-tune модели (точнее, но требует данных)
  4. Комбинированный подход (надёжнее)

2. Готовые API сервисы

Google Vision API

from google.cloud import vision

def check_nsfw_google(image_url):
    client = vision.ImageAnnotatorClient()
    image = vision.Image()
    image.source.image_uri = image_url
    
    response = client.safe_search_detection(image=image)
    safe_search = response.safe_search_annotation
    
    # Likelihood: UNKNOWN (0) -> VERY_LIKELY (5)
    result = {
        'adult': safe_search.adult,        # Взрослый контент
        'spoof': safe_search.spoof,        # Подделка
        'medical': safe_search.medical,    # Медицинский
        'violence': safe_search.violence,  # Насилие
        'racy': safe_search.racy          # Откровенный
    }
    
    # Если adult или racy >= LIKELY (4), то NSFW
    is_nsfw = (result['adult'] >= 4 or result['racy'] >= 4)
    return result, is_nsfw

AWS Rekognition

import boto3

def check_nsfw_aws(image_url):
    rekognition = boto3.client('rekognition')
    
    response = rekognition.detect_moderation_labels(
        Image={'S3Object': {'Bucket': 'my-bucket', 'Name': 'image.jpg'}}
    )
    
    result = []
    for label in response['ModerationLabels']:
        result.append({
            'name': label['Name'],
            'confidence': label['Confidence']
        })
    
    # Проверяем высокую уверенность для опасных категорий
    is_nsfw = any(
        label['confidence'] > 80 
        for label in result 
        if label['name'] in ['Explicit Nudity', 'Suggestive']
    )
    return result, is_nsfw

Плюсы API: простота, точность, постоянные обновления
Минусы: стоимость, задержка сети, privacy (данные отправляются на серверы)

3. Предтренированные модели локально

Yahoo NSFW Detector (ResNet-50)

# Установка
pip install nsfw_model
pip install tensorflow
import nsfw_model
import numpy as np
from PIL import Image
import requests
from io import BytesIO

def check_nsfw_local(image_url):
    # Загруженная модель
    model = nsfw_model.load_model("inceptionv3")
    
    # Загрузить изображение
    response = requests.get(image_url)
    img = Image.open(BytesIO(response.content))
    img = img.resize((224, 224))
    
    # Предсказание
    predictions = model.predict(np.array([img]))[0]
    
    result = {
        'drawings': predictions[0],      # Рисунки
        'hentai': predictions[1],        # Хентай
        'neutral': predictions[2],       # Нейтральный контент
        'porn': predictions[3],          # Порнография
        'sexy': predictions[4]           # Сексуальный контент
    }
    
    # NSFW если porn > 0.5 или sexy > 0.7
    is_nsfw = (result['porn'] > 0.5 or result['sexy'] > 0.7)
    return result, is_nsfw

# Использование
result, is_nsfw = check_nsfw_local("https://example.com/image.jpg")
print(f"NSFW: {is_nsfw}")
print(f"Porn confidence: {result['porn']:.2%}")
print(f"Sexy confidence: {result['sexy']:.2%}")

OpenCV + MobileNet

import cv2
import numpy as np
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.preprocessing import image

def check_nsfw_mobilenet(image_path):
    # Загруженная модель
    model = MobileNetV2(weights='imagenet')
    
    # Подготовка изображения
    img = image.load_img(image_path, target_size=(224, 224))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = x / 255.0
    
    # Предсказание
    predictions = model.predict(x)
    
    # Анализируем top-1 prediction
    # ImageNet классы: 0-999, некоторые связаны с NSFW
    # Например: класс 645-649 - топлесс женщины
    
    return predictions

Плюсы: приватность, быстро, бесплатно
Минусы: менее точно, требует GPU для производства

4. Fine-tuning под свои требования

Если встроенные модели не подходят:

from torch import nn, optim
from torchvision import models, transforms
from torch.utils.data import DataLoader, Dataset

class NSFWDataset(Dataset):
    def __init__(self, images, labels):
        self.images = images
        self.labels = labels
        self.transform = transforms.Compose([
            transforms.Resize((224, 224)),
            transforms.ToTensor(),
            transforms.Normalize(
                mean=[0.485, 0.456, 0.406],
                std=[0.229, 0.224, 0.225]
            )
        ])
    
    def __len__(self):
        return len(self.images)
    
    def __getitem__(self, idx):
        img = Image.open(self.images[idx])
        img = self.transform(img)
        label = self.labels[idx]
        return img, label

def train_nsfw_model(train_images, train_labels):
    # Загруженная модель ResNet50
    model = models.resnet50(pretrained=True)
    
    # Заменяем последний слой на бинарный классификатор
    model.fc = nn.Linear(2048, 2)  # 0 - safe, 1 - nsfw
    
    dataset = NSFWDataset(train_images, train_labels)
    loader = DataLoader(dataset, batch_size=32, shuffle=True)
    
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    criterion = nn.CrossEntropyLoss()
    
    model.train()
    for epoch in range(10):
        for images, labels in loader:
            optimizer.zero_grad()
            outputs = model(images)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
        
        print(f"Epoch {epoch+1}/10, Loss: {loss.item():.4f}")
    
    return model

def predict_nsfw(model, image_path):
    model.eval()
    img = Image.open(image_path)
    img = transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
        transforms.Normalize(
            mean=[0.485, 0.456, 0.406],
            std=[0.229, 0.224, 0.225]
        )
    ])(img)
    
    with torch.no_grad():
        output = model(img.unsqueeze(0))
        probabilities = torch.softmax(output, dim=1)
        is_nsfw = probabilities[0, 1].item()  # Вероятность NSFW
    
    return is_nsfw

5. Комбинированный подход (рекомендуется)

from concurrent.futures import ThreadPoolExecutor
import logging

logger = logging.getLogger(__name__)

class NSFWDetector:
    def __init__(self):
        self.local_model = nsfw_model.load_model("inceptionv3")
        self.google_client = vision.ImageAnnotatorClient()
    
    def detect_nsfw(self, image_url, use_api=True, threshold=0.7):
        """
        Определяет NSFW контент используя несколько подходов.
        
        Args:
            image_url: URL изображения
            use_api: использовать Google API для подтверждения
            threshold: порог вероятности для считания NSFW
        
        Returns:
            {'is_nsfw': bool, 'confidence': float, 'details': dict}
        """
        
        # 1. Локальная проверка (быстро)
        local_result, local_nsfw = self._check_local(image_url)
        
        # 2. Если локально неуверенно, используем API
        if use_api and 0.3 < local_result.get('porn', 0) < 0.7:
            api_result, api_nsfw = self._check_google_api(image_url)
            
            # Комбинируем результаты
            confidence = (local_result.get('porn', 0) + 
                         (api_result.get('adult', 0) / 5.0)) / 2
            is_nsfw = confidence > threshold
        else:
            confidence = local_result.get('porn', 0)
            is_nsfw = confidence > threshold
        
        return {
            'is_nsfw': is_nsfw,
            'confidence': round(confidence, 3),
            'details': {
                'local': local_result,
                'api': api_result if use_api else None
            }
        }
    
    def _check_local(self, image_url):
        try:
            response = requests.get(image_url, timeout=5)
            img = Image.open(BytesIO(response.content))
            img = img.resize((224, 224))
            predictions = self.local_model.predict(np.array([img]))[0]
            
            return {
                'porn': float(predictions[3]),
                'sexy': float(predictions[4])
            }, predictions[3] > 0.5
        except Exception as e:
            logger.error(f"Local check failed: {e}")
            return {}, False
    
    def _check_google_api(self, image_url):
        try:
            image = vision.Image()
            image.source.image_uri = image_url
            response = self.google_client.safe_search_detection(image=image)
            safe = response.safe_search_annotation
            
            return {
                'adult': safe.adult,
                'racy': safe.racy
            }, (safe.adult >= 4 or safe.racy >= 4)
        except Exception as e:
            logger.error(f"API check failed: {e}")
            return {}, False

# Использование
detector = NSFWDetector()
result = detector.detect_nsfw("https://example.com/image.jpg")

if result['is_nsfw']:
    print(f"Изображение содержит NSFW контент (уверенность: {result['confidence']:.1%})")
else:
    print("Изображение безопасно")

6. Интеграция в веб-приложение

Django модель

from django.db import models
from django.core.validators import URLValidator
import celery

class Photo(models.Model):
    url = models.URLField(validators=[URLValidator()])
    is_nsfw = models.BooleanField(null=True, default=None)
    nsfw_confidence = models.FloatField(null=True)
    checked_at = models.DateTimeField(null=True)
    
    class Meta:
        indexes = [
            models.Index(fields=['is_nsfw'])
        ]

# Asynchronous проверка
@celery.shared_task
def check_photo_nsfw(photo_id):
    photo = Photo.objects.get(id=photo_id)
    detector = NSFWDetector()
    result = detector.detect_nsfw(photo.url)
    
    photo.is_nsfw = result['is_nsfw']
    photo.nsfw_confidence = result['confidence']
    photo.checked_at = timezone.now()
    photo.save()

7. Метрики и мониторинг

from sklearn.metrics import precision_recall_fscore_support, confusion_matrix

def evaluate_model(y_true, y_pred):
    precision, recall, f1, _ = precision_recall_fscore_support(
        y_true, y_pred, average='binary'
    )
    cm = confusion_matrix(y_true, y_pred)
    
    return {
        'precision': precision,  # Из предсказанных NSFW сколько правильно
        'recall': recall,        # Из реальных NSFW сколько найдено
        'f1': f1,               # Средняя гармоническая
        'confusion_matrix': cm  # TP, FP, TN, FN
    }

Рекомендации

  1. Для быстрого старта: используй Google Vision API или AWS Rekognition
  2. Для privacy: Yahoo NSFW model локально + fine-tune на свои данные
  3. Для production: комбинированный подход с асинхронной проверкой
  4. Ложные срабатывания: используй человеческую модерацию для граничных случаев
  5. Performance: кешируй результаты проверок (фото не меняется)

NSFW детекция — постоянно развивающаяся область. Регулярно обновляй модели!