# nsfw-model **Repository Path**: ryanvan2000/nsfw-model ## Basic Information - **Project Name**: nsfw-model - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-04 - **Last Updated**: 2026-03-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # nsfw-model > 🔒 基于 MobileNetV2 的轻量级 NSFW 图片检测模型 这是使用开源 NSFW MobileNetV2 自己量化导出 **NSFW** 模型 ### 📁 模型文件 | 文件 | 描述 | 大小 | | ---- | ---- | ---- | | [`nsfw_only.tflite`](nsfw_only.tflite) | 量化后的 TFLite 模型 | ~2MB | ### 🚀 快速开始 **Python 测试:** ```python import tensorflow as tf import numpy as np from PIL import Image # 加载模型 interpreter = tf.lite.Interpreter("nsfw_only.tflite") interpreter.allocate_tensors() # 获取输入输出 input_details = interpreter.get_input_details() output_details = interpreter.get_output_details() # 预处理图片 img = Image.open("test.png").resize((224, 224)) input_data = np.array(img).reshape(1, 224, 224, 3).astype(np.float32) # 推理 interpreter.set_tensor(input_details[0]['index'], input_data) interpreter.invoke() nsfw_prob = interpreter.get_tensor(output_details[0]['index'])[0][0] print(f"NSFW 概率: {nsfw_prob}") ``` ### 📦 推荐模型规格 | 项目 | 建议 | | -------- | ---------------- | | Backbone | MobileNetV2 | | 输入尺寸 | 224×224 | | 输出 | 2 类(safe / nsfw) | | 量化 | Full int8 | | 模型大小 | ~2MB–3MB | | 推理时间 | 5–15ms(中端手机) | ### 移动端最佳架构 ``` 生成图片 ↓ MobileNetV2-int8 (2MB) ↓ nsfwProb > 0.6 ? ↓ 是 → 不展示 否 → 展示 ``` ```shell PS E:\Carp\home\Administrator\py\nsfw-classifier> python -m venv v38 PS E:\Carp\home\Administrator\py\nsfw-classifier> v38\Scripts\activate (v38) PS E:\Carp\home\Administrator\py\nsfw-classifier> python .\test_tfile.nsfw.py 2026-03-03 15:08:46.516887: W tensorflow/stream_executor/platform/default/dso_loader.cc:60] Could not load dynamic library 'cudart64_110.dll'; dlerror: cudart64_110.dll not found 2026-03-03 15:08:46.517147: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine. 📌 输入信息: [{'name': 'mobilenetv2_1.00_224_input', 'index': 0, 'shape': array([ 1, 224, 224, 3]), 'shape_signature': array([ -1, 224, 224, 3]), 'dtype': , 'quantization': (0.0, 0), 'quantization_parameters': {'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32), 'quantized_dimension': 0}, 'sparsity_parameters': {}}] 📌 输出信息: [{'name': 'Identity', 'index': 198, 'shape': array([1, 1]), 'shape_signature': array([-1, 1]), 'dtype': , 'quantization': (0.0, 0), 'quantization_parameters': {'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32), 'quantized_dimension': 0}, 'sparsity_parameters': {}}] 🚀 NSFW 概率: 0.9973682165145874 🔞 NSFW Image ``` ### 📦 依赖配置 #### Android (build.gradle) ```gradle dependencies { implementation 'org.tensorflow:tensorflow-lite:2.14.0' } ``` #### iOS (Podfile) ```ruby pod 'TensorFlowLiteSwift', '~> 2.14.0' ``` --- ### 📱 Android 推理(TFLite) ```kotlin // Android TFLite 推理示例 class NSFWClassifier(context: Context) { private val interpreter: Interpreter init { val modelFile = loadModelFile(context, "nsfw_only.tflite") val options = Interpreter.Options().apply { numThreads = 4 } interpreter = Interpreter(modelFile, options) } fun detectNsfw(bitmap: Bitmap): Float { // 1. 预处理:缩放到 224x224 并归一化 val inputBuffer = preprocessImage(bitmap) // 2. 推理 val outputBuffer = Array(1) { FloatArray(1) } interpreter.run(inputBuffer, outputBuffer) // 3. 返回 NSFW 概率 return outputBuffer[0][0] } private fun preprocessImage(bitmap: Bitmap): ByteBuffer { val scaled = Bitmap.createScaledBitmap(bitmap, 224, 224, true) val inputBuffer = ByteBuffer.allocateDirect(224 * 224 * 3) inputBuffer.order(ByteOrder.nativeOrder()) val pixels = IntArray(224 * 224) scaled.getPixels(pixels, 0, 224, 0, 0, 224, 224) for (pixel in pixels) { inputBuffer.put((pixel shr 16 and 0xFF).toByte()) // R inputBuffer.put((pixel shr 8 and 0xFF).toByte()) // G inputBuffer.put((pixel and 0xFF).toByte()) // B } return inputBuffer } private fun loadModelFile(context: Context, filename: String): MappedByteBuffer { val fileDescriptor = context.assets.openFd(filename) val inputStream = FileInputStream(fileDescriptor.fileDescriptor) return inputStream.channel.map( FileChannel.MapMode.READ_ONLY, fileDescriptor.startOffset, fileDescriptor.declaredLength ) } } // 使用示例 val classifier = NSFWClassifier(context) val nsfwProb = classifier.detectNsfw(imageBitmap) if (nsfwProb > 0.6f) { // 不展示图片 } else { // 展示图片 } ``` **Android 推理注意点:** - ✅ 使用 `MappedByteBuffer` 加载模型,避免频繁读取 - ✅ 设置 `numThreads = 4` 提升推理速度 - ✅ 输入预处理:RGB 通道顺序,BMP 像素格式 - ⚠️ 避免在主线程执行推理,会阻塞 UI - ⚠️ 释放资源:`interpreter.close()` --- ### 🍎 iOS 推理(Core ML / TFLite) ```swift // iOS TFLite 推理示例 import TFLiteSwift class NSFWClassifier { private var interpreter: Interpreter? init() { loadModel() } private func loadModel() { guard let modelPath = Bundle.main.path(forResource: "nsfw_only", ofType: "tflite") else { print("模型文件未找到") return } do { interpreter = try Interpreter(modelPath: modelPath) try interpreter?.allocateTensors() } catch { print("模型加载失败: \(error)") } } func detectNsfw(image: UIImage) -> Float? { guard let interpreter = interpreter, let inputData = preprocessImage(image) else { return nil } do { // 设置输入 try interpreter.copy(inputData, toInputAt: 0) // 推理 try interpreter.invoke() // 获取输出 let output = interpreter.output(at: 0) let outputData = output.data return outputData.withUnsafeBytes { $0.load(as: Float.self) } } catch { print("推理失败: \(error)") return nil } } private func preprocessImage(_ image: UIImage) -> Data? { guard let cgImage = image.cgImage else { return nil } // 缩放到 224x224 let targetSize = CGSize(width: 224, height: 224) let width = Int(targetSize.width) let height = Int(targetSize.height) guard let context = CGContext( data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 3, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.noneSkipRaw.rawValue ) else { return nil } context.draw(cgImage, in: CGRect(origin: .zero, size: targetSize)) guard let data = context.data else { return nil } // 转换为 RGB 数据 var byteData = [UInt8](repeating: 0, count: width * height * 3) let pointer = data.bindMemory(to: UInt8.self, capacity: width * height * 4) for i in 0..<(width * height) { byteData[i * 3] = pointer[i * 4] // R byteData[i * 3 + 1] = pointer[i * 4 + 1] // G byteData[i * 3 + 2] = pointer[i * 4 + 2] // B } return Data(byteData) } } // 使用示例 let classifier = NSFWClassifier() if let nsfwProb = classifier.detectNsfw(image: uiImage) { if nsfwProb > 0.6 { // 不展示图片 } else { // 展示图片 } } ``` **iOS 推理注意点:** - ✅ 使用 `TFLiteSwift` 或官方 `TensorFlowLiteC` 框架 - ✅ 图片预处理:确保 RGB 通道顺序正确 - ✅ 内存管理:模型加载后及时释放 - ⚠️ 后台线程执行推理,避免阻塞主线程 - ⚠️ 模型路径放在 Bundle 中,确保正确配置 Build Phases --- ### ⚡ 性能优化 #### Android 优化 - ✅ 使用 `Interpreter.Options()` 启用 GPU 加速: ```kotlin val options = Interpreter.Options().apply { numThreads = 4 useGpuDelegate() // GPU 加速 } ``` - ✅ 预加载模型,避免重复创建 - ✅ 复用 `ByteBuffer` 减少内存分配 - ✅ 使用 `Bitmap.Config.ARGB_8888` 提高兼容性 #### iOS 优化 - ✅ 使用 `Metal` 委托加速(`Interpreter.Options().setUseMetal(true)`) - ✅ 缓存预处理后的图片数据 - ✅ 批量推理时复用 buffer --- ### 📋 通用最佳实践 | 项目 | Android | iOS | | ---- | ------- | --- | | 输入尺寸 | 224×224 | 224×224 | | 预处理 | RGB 归一化 | RGB 归一化 | | 线程 | 后台线程 | 后台线程 | | 阈值 | 0.6 | 0.6 | | 模型格式 | .tflite | .tflite / .mlmodel | | GPU 加速 | GPUDelegate | Metal Delegate | | 推荐框架 | TFLite | TFLiteSwift | --- ### 📖 相关资源 - [TensorFlow Lite 官方文档](https://www.tensorflow.org/lite) - [MobileNetV2 论文](https://arxiv.org/abs/1801.04381) - [NSFW 数据集](https://github.com/alex000kim/nsfw_data_scraper)