nsfwpy 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nsfwpy-0.0.1/PKG-INFO +141 -0
- nsfwpy-0.0.1/README.md +128 -0
- nsfwpy-0.0.1/nsfwpy/__init__.py +9 -0
- nsfwpy-0.0.1/nsfwpy/api.py +111 -0
- nsfwpy-0.0.1/nsfwpy/cli.py +20 -0
- nsfwpy-0.0.1/nsfwpy/nsfw.py +214 -0
- nsfwpy-0.0.1/nsfwpy/server.py +46 -0
- nsfwpy-0.0.1/nsfwpy.egg-info/PKG-INFO +141 -0
- nsfwpy-0.0.1/nsfwpy.egg-info/SOURCES.txt +13 -0
- nsfwpy-0.0.1/nsfwpy.egg-info/dependency_links.txt +1 -0
- nsfwpy-0.0.1/nsfwpy.egg-info/entry_points.txt +3 -0
- nsfwpy-0.0.1/nsfwpy.egg-info/requires.txt +6 -0
- nsfwpy-0.0.1/nsfwpy.egg-info/top_level.txt +1 -0
- nsfwpy-0.0.1/setup.cfg +4 -0
- nsfwpy-0.0.1/setup.py +45 -0
nsfwpy-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nsfwpy
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: 基于OpenNSFW的图像内容检测工具
|
|
5
|
+
Home-page: https://github.com/HG-ha/nsfwpy
|
|
6
|
+
Author: YiMing
|
|
7
|
+
Author-email: 1790233968@qq.com
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Description: # nsfwpy
|
|
10
|
+
|
|
11
|
+
[English](README_EN.md) | 简体中文
|
|
12
|
+
|
|
13
|
+
图像敏感内容检测工具,提供简单易用的Python接口进行图像内容分析和过滤,并提供API和CLI工具。
|
|
14
|
+
|
|
15
|
+
## 简介
|
|
16
|
+
|
|
17
|
+
nsfwpy 是一个轻量级Python库,使用深度学习模型进行图像内容分析,可以识别图像是否包含不适宜内容。本项目基于[nsfw_model](https://github.com/GantMan/nsfw_model)提供的模型实现。
|
|
18
|
+
|
|
19
|
+
## 特性
|
|
20
|
+
|
|
21
|
+
- 轻量级实现,依赖少,易于部署
|
|
22
|
+
- 支持多种图像格式输入(JPG、PNG等)
|
|
23
|
+
- 提供命令行工具、Python API和HTTP API接口
|
|
24
|
+
- 使用TensorFlow Lite优化性能
|
|
25
|
+
- 支持Windows和其他操作系统
|
|
26
|
+
- 自动下载和缓存模型文件
|
|
27
|
+
|
|
28
|
+
## 安装要求
|
|
29
|
+
> 由依赖导致安装出错时,可以尝试移除版本号安装,但tflite-runtime版本必须>=2.5.0
|
|
30
|
+
- Python 3.7+
|
|
31
|
+
- NumPy <= 1.26.4
|
|
32
|
+
- Pillow <= 11.1.0
|
|
33
|
+
- FastAPI <= 0.115.11
|
|
34
|
+
- uvicorn <= 0.34.0
|
|
35
|
+
- python-multipart <= 0.0.20
|
|
36
|
+
- tflite-runtime = 2.13.0 (Windows) 或 >= 2.5.0 (其他系统)
|
|
37
|
+
|
|
38
|
+
## 安装
|
|
39
|
+
|
|
40
|
+
### 通过pip安装
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install nsfwpy
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 从源码安装
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/HG-ha/nsfwpy.git
|
|
50
|
+
cd nsfwpy
|
|
51
|
+
pip install -e .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 使用方法
|
|
55
|
+
|
|
56
|
+
### Python API
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from nsfwpy import NSFW
|
|
60
|
+
|
|
61
|
+
# 初始化检测器(首次运行会自动下载模型)
|
|
62
|
+
detector = NSFW()
|
|
63
|
+
|
|
64
|
+
# 预测单个图像
|
|
65
|
+
result = detector.predict_image("path/to/image.jpg")
|
|
66
|
+
print(result)
|
|
67
|
+
|
|
68
|
+
# 预测PIL图像
|
|
69
|
+
from PIL import Image
|
|
70
|
+
img = Image.open("path/to/image.jpg")
|
|
71
|
+
result = detector.predict_pil_image(img)
|
|
72
|
+
print(result)
|
|
73
|
+
|
|
74
|
+
# 批量预测目录中的图像
|
|
75
|
+
results = detector.predict_batch("path/to/image/directory")
|
|
76
|
+
print(results)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 命令行工具
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 基本用法
|
|
83
|
+
nsfwpy --input path/to/image.jpg
|
|
84
|
+
|
|
85
|
+
# 指定自定义模型路径
|
|
86
|
+
nsfwpy --model path/to/model.tflite --input path/to/image.jpg
|
|
87
|
+
|
|
88
|
+
# 指定图像尺寸
|
|
89
|
+
nsfwpy --dim 299 --input path/to/image.jpg
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Web API服务(完全兼容 nsfwjs-api)
|
|
93
|
+
|
|
94
|
+
启动API服务器:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 基本用法
|
|
98
|
+
nsfwpy -w
|
|
99
|
+
|
|
100
|
+
# 指定主机和端口
|
|
101
|
+
nsfwpy -w --host 127.0.0.1 --port 8080
|
|
102
|
+
|
|
103
|
+
# 指定自定义模型
|
|
104
|
+
nsfwpy -w --model path/to/model.tflite
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
API端点:
|
|
108
|
+
- `POST /classify`: 分析单张图片
|
|
109
|
+
- `POST /classify-many`: 批量分析多张图片
|
|
110
|
+
|
|
111
|
+
### 预测结果格式
|
|
112
|
+
|
|
113
|
+
返回包含以下类别概率值的字典:
|
|
114
|
+
```python
|
|
115
|
+
{
|
|
116
|
+
"drawings": 0.1, # 绘画/动画
|
|
117
|
+
"hentai": 0.0, # 动漫色情内容
|
|
118
|
+
"neutral": 0.8, # 中性/安全内容
|
|
119
|
+
"porn": 0.0, # 色情内容
|
|
120
|
+
"sexy": 0.1 # 性感内容
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 开发说明
|
|
125
|
+
|
|
126
|
+
- 项目使用MIT许可证
|
|
127
|
+
- 欢迎提交Issue和Pull Request
|
|
128
|
+
- 自动发布到PyPI使用GitHub Actions
|
|
129
|
+
|
|
130
|
+
## 致谢
|
|
131
|
+
|
|
132
|
+
本项目的模型基于[nsfw_model](https://github.com/GantMan/nsfw_model)。感谢原作者的贡献。
|
|
133
|
+
|
|
134
|
+
## 许可证
|
|
135
|
+
|
|
136
|
+
[MIT License](LICENSE)
|
|
137
|
+
|
|
138
|
+
Platform: UNKNOWN
|
|
139
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
140
|
+
Classifier: Operating System :: OS Independent
|
|
141
|
+
Description-Content-Type: text/markdown
|
nsfwpy-0.0.1/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# nsfwpy
|
|
2
|
+
|
|
3
|
+
[English](README_EN.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
图像敏感内容检测工具,提供简单易用的Python接口进行图像内容分析和过滤,并提供API和CLI工具。
|
|
6
|
+
|
|
7
|
+
## 简介
|
|
8
|
+
|
|
9
|
+
nsfwpy 是一个轻量级Python库,使用深度学习模型进行图像内容分析,可以识别图像是否包含不适宜内容。本项目基于[nsfw_model](https://github.com/GantMan/nsfw_model)提供的模型实现。
|
|
10
|
+
|
|
11
|
+
## 特性
|
|
12
|
+
|
|
13
|
+
- 轻量级实现,依赖少,易于部署
|
|
14
|
+
- 支持多种图像格式输入(JPG、PNG等)
|
|
15
|
+
- 提供命令行工具、Python API和HTTP API接口
|
|
16
|
+
- 使用TensorFlow Lite优化性能
|
|
17
|
+
- 支持Windows和其他操作系统
|
|
18
|
+
- 自动下载和缓存模型文件
|
|
19
|
+
|
|
20
|
+
## 安装要求
|
|
21
|
+
> 由依赖导致安装出错时,可以尝试移除版本号安装,但tflite-runtime版本必须>=2.5.0
|
|
22
|
+
- Python 3.7+
|
|
23
|
+
- NumPy <= 1.26.4
|
|
24
|
+
- Pillow <= 11.1.0
|
|
25
|
+
- FastAPI <= 0.115.11
|
|
26
|
+
- uvicorn <= 0.34.0
|
|
27
|
+
- python-multipart <= 0.0.20
|
|
28
|
+
- tflite-runtime = 2.13.0 (Windows) 或 >= 2.5.0 (其他系统)
|
|
29
|
+
|
|
30
|
+
## 安装
|
|
31
|
+
|
|
32
|
+
### 通过pip安装
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install nsfwpy
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 从源码安装
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/HG-ha/nsfwpy.git
|
|
42
|
+
cd nsfwpy
|
|
43
|
+
pip install -e .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 使用方法
|
|
47
|
+
|
|
48
|
+
### Python API
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from nsfwpy import NSFW
|
|
52
|
+
|
|
53
|
+
# 初始化检测器(首次运行会自动下载模型)
|
|
54
|
+
detector = NSFW()
|
|
55
|
+
|
|
56
|
+
# 预测单个图像
|
|
57
|
+
result = detector.predict_image("path/to/image.jpg")
|
|
58
|
+
print(result)
|
|
59
|
+
|
|
60
|
+
# 预测PIL图像
|
|
61
|
+
from PIL import Image
|
|
62
|
+
img = Image.open("path/to/image.jpg")
|
|
63
|
+
result = detector.predict_pil_image(img)
|
|
64
|
+
print(result)
|
|
65
|
+
|
|
66
|
+
# 批量预测目录中的图像
|
|
67
|
+
results = detector.predict_batch("path/to/image/directory")
|
|
68
|
+
print(results)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 命令行工具
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 基本用法
|
|
75
|
+
nsfwpy --input path/to/image.jpg
|
|
76
|
+
|
|
77
|
+
# 指定自定义模型路径
|
|
78
|
+
nsfwpy --model path/to/model.tflite --input path/to/image.jpg
|
|
79
|
+
|
|
80
|
+
# 指定图像尺寸
|
|
81
|
+
nsfwpy --dim 299 --input path/to/image.jpg
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Web API服务(完全兼容 nsfwjs-api)
|
|
85
|
+
|
|
86
|
+
启动API服务器:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 基本用法
|
|
90
|
+
nsfwpy -w
|
|
91
|
+
|
|
92
|
+
# 指定主机和端口
|
|
93
|
+
nsfwpy -w --host 127.0.0.1 --port 8080
|
|
94
|
+
|
|
95
|
+
# 指定自定义模型
|
|
96
|
+
nsfwpy -w --model path/to/model.tflite
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
API端点:
|
|
100
|
+
- `POST /classify`: 分析单张图片
|
|
101
|
+
- `POST /classify-many`: 批量分析多张图片
|
|
102
|
+
|
|
103
|
+
### 预测结果格式
|
|
104
|
+
|
|
105
|
+
返回包含以下类别概率值的字典:
|
|
106
|
+
```python
|
|
107
|
+
{
|
|
108
|
+
"drawings": 0.1, # 绘画/动画
|
|
109
|
+
"hentai": 0.0, # 动漫色情内容
|
|
110
|
+
"neutral": 0.8, # 中性/安全内容
|
|
111
|
+
"porn": 0.0, # 色情内容
|
|
112
|
+
"sexy": 0.1 # 性感内容
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 开发说明
|
|
117
|
+
|
|
118
|
+
- 项目使用MIT许可证
|
|
119
|
+
- 欢迎提交Issue和Pull Request
|
|
120
|
+
- 自动发布到PyPI使用GitHub Actions
|
|
121
|
+
|
|
122
|
+
## 致谢
|
|
123
|
+
|
|
124
|
+
本项目的模型基于[nsfw_model](https://github.com/GantMan/nsfw_model)。感谢原作者的贡献。
|
|
125
|
+
|
|
126
|
+
## 许可证
|
|
127
|
+
|
|
128
|
+
[MIT License](LICENSE)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import List, Dict
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from fastapi import FastAPI, File, UploadFile, HTTPException
|
|
4
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
5
|
+
|
|
6
|
+
from .nsfw import NSFWDetector
|
|
7
|
+
|
|
8
|
+
# 全局模型实例
|
|
9
|
+
global_detector = None
|
|
10
|
+
|
|
11
|
+
# 加载模型的辅助函数
|
|
12
|
+
def get_detector(model_path=None):
|
|
13
|
+
global global_detector
|
|
14
|
+
|
|
15
|
+
# 如果已经有全局模型实例,直接返回
|
|
16
|
+
if (global_detector is not None):
|
|
17
|
+
return global_detector
|
|
18
|
+
|
|
19
|
+
# 创建新的检测器实例
|
|
20
|
+
detector = NSFWDetector(model_path=model_path)
|
|
21
|
+
|
|
22
|
+
# 保存为全局实例
|
|
23
|
+
global_detector = detector
|
|
24
|
+
|
|
25
|
+
return detector
|
|
26
|
+
|
|
27
|
+
# 数据模型定义
|
|
28
|
+
class ClassifyItem(BaseModel):
|
|
29
|
+
image: UploadFile = File(..., description="上传的图像文件")
|
|
30
|
+
|
|
31
|
+
class ClassifyManyItem(BaseModel):
|
|
32
|
+
images: List[UploadFile] = File(..., description="上传的图像文件列表")
|
|
33
|
+
|
|
34
|
+
# 将响应模型直接定义为分类结果字典
|
|
35
|
+
class ClassificationResult(Dict[str, float]):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
class MultipleClassificationResult(BaseModel):
|
|
39
|
+
results: List[Dict[str, float]]
|
|
40
|
+
|
|
41
|
+
# 创建FastAPI应用
|
|
42
|
+
app = FastAPI(
|
|
43
|
+
title="NSFW内容检测API",
|
|
44
|
+
description="基于MobileNet V2的NSFW内容检测API,兼容nsfwjs接口",
|
|
45
|
+
version="1.0.0"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# 添加CORS支持
|
|
50
|
+
app.add_middleware(
|
|
51
|
+
CORSMiddleware,
|
|
52
|
+
allow_origins=["*"],
|
|
53
|
+
allow_credentials=True,
|
|
54
|
+
allow_methods=["*"],
|
|
55
|
+
allow_headers=["*"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# 在启动时预加载模型
|
|
59
|
+
@app.on_event("startup")
|
|
60
|
+
async def startup_event():
|
|
61
|
+
# 在服务启动时预加载模型
|
|
62
|
+
get_detector()
|
|
63
|
+
|
|
64
|
+
# 辅助函数:从上传文件读取图像
|
|
65
|
+
async def read_image_file(file: UploadFile):
|
|
66
|
+
try:
|
|
67
|
+
contents = await file.read()
|
|
68
|
+
return contents
|
|
69
|
+
except Exception as e:
|
|
70
|
+
raise HTTPException(status_code=400, detail=f"无法读取上传文件: {str(e)}")
|
|
71
|
+
|
|
72
|
+
@app.post("/classify", response_model=Dict[str, float])
|
|
73
|
+
async def classify_image(image: UploadFile = File(...)):
|
|
74
|
+
"""
|
|
75
|
+
对单张上传的图像文件进行NSFW分类
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
detector = get_detector()
|
|
79
|
+
image_bytes = await read_image_file(image)
|
|
80
|
+
result = detector.predict_from_bytes(image_bytes)
|
|
81
|
+
|
|
82
|
+
if not result:
|
|
83
|
+
raise HTTPException(status_code=500, detail="图像处理失败")
|
|
84
|
+
|
|
85
|
+
return result
|
|
86
|
+
except Exception as e:
|
|
87
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
88
|
+
|
|
89
|
+
@app.post("/classify-many", response_model=List[Dict[str, float]])
|
|
90
|
+
async def classify_many_images(images: List[UploadFile] = File(...)):
|
|
91
|
+
"""
|
|
92
|
+
对多张上传的图像文件进行NSFW分类
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
detector = get_detector()
|
|
96
|
+
results = []
|
|
97
|
+
|
|
98
|
+
for image in images:
|
|
99
|
+
try:
|
|
100
|
+
image_bytes = await read_image_file(image)
|
|
101
|
+
result = detector.predict_from_bytes(image_bytes)
|
|
102
|
+
if result:
|
|
103
|
+
results.append(result)
|
|
104
|
+
else:
|
|
105
|
+
results.append({"error": "处理失败"})
|
|
106
|
+
except Exception as e:
|
|
107
|
+
results.append({"error": str(e)})
|
|
108
|
+
|
|
109
|
+
return results
|
|
110
|
+
except Exception as e:
|
|
111
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
def main():
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
from nsfwpy.nsfw import NSFWDetector
|
|
5
|
+
|
|
6
|
+
parser = argparse.ArgumentParser(description='NSFW图像内容检测')
|
|
7
|
+
parser.add_argument('--model', help='TFLite模型文件路径(可选)')
|
|
8
|
+
parser.add_argument('--dim', type=int, default=224, help='图像尺寸(默认:224)')
|
|
9
|
+
parser.add_argument('--input', required=True, help='要检测的图像文件或目录')
|
|
10
|
+
|
|
11
|
+
args = parser.parse_args()
|
|
12
|
+
|
|
13
|
+
# 创建检测器,如果未指定模型路径,则使用默认值
|
|
14
|
+
detector = NSFWDetector(model_path=args.model, image_dim=args.dim)
|
|
15
|
+
results = detector.predict_batch(args.input)
|
|
16
|
+
|
|
17
|
+
print(json.dumps(results, ensure_ascii=False, indent=2))
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
main()
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
from PIL import Image
|
|
4
|
+
import tflite_runtime.interpreter as tflite
|
|
5
|
+
import io
|
|
6
|
+
import platform
|
|
7
|
+
import urllib.request
|
|
8
|
+
|
|
9
|
+
class NSFWDetector:
|
|
10
|
+
"""NSFW内容检测器,基于MobileNet V2模型"""
|
|
11
|
+
|
|
12
|
+
CATEGORIES = ['drawings', 'hentai', 'neutral', 'porn', 'sexy']
|
|
13
|
+
MODEL_URL = "https://ghproxy.cn/github.com/HG-ha/nsfwpy/raw/main/model/model.tflite"
|
|
14
|
+
|
|
15
|
+
def __init__(self, model_path=None, image_dim=224):
|
|
16
|
+
"""
|
|
17
|
+
初始化NSFW检测器
|
|
18
|
+
|
|
19
|
+
参数:
|
|
20
|
+
model_path: TFLite模型文件路径,若未提供则自动从缓存或网络获取
|
|
21
|
+
image_dim: 模型输入图像尺寸(默认224x224)
|
|
22
|
+
"""
|
|
23
|
+
self.image_dim = image_dim
|
|
24
|
+
|
|
25
|
+
# 优先检查环境变量中是否设置了模型路径
|
|
26
|
+
env_model_path = os.environ.get("NSFW_MODEL_PATH")
|
|
27
|
+
if env_model_path and os.path.exists(env_model_path):
|
|
28
|
+
model_path = env_model_path
|
|
29
|
+
# 若未通过环境变量或参数提供模型路径,则自动获取
|
|
30
|
+
elif model_path is None:
|
|
31
|
+
model_path = self._get_model_path()
|
|
32
|
+
|
|
33
|
+
if not os.path.exists(model_path):
|
|
34
|
+
raise ValueError(f"模型文件不存在: {model_path}")
|
|
35
|
+
|
|
36
|
+
self.model_path = model_path
|
|
37
|
+
|
|
38
|
+
# 加载TFLite模型
|
|
39
|
+
self.interpreter = tflite.Interpreter(model_path=model_path)
|
|
40
|
+
self.interpreter.allocate_tensors()
|
|
41
|
+
|
|
42
|
+
# 获取输入输出细节
|
|
43
|
+
self.input_details = self.interpreter.get_input_details()
|
|
44
|
+
self.output_details = self.interpreter.get_output_details()
|
|
45
|
+
|
|
46
|
+
def _get_model_path(self):
|
|
47
|
+
"""根据平台获取缓存路径,检查模型文件是否存在,不存在则下载"""
|
|
48
|
+
# 首先检查环境变量
|
|
49
|
+
env_model_path = os.environ.get("NSFW_MODEL_PATH")
|
|
50
|
+
if env_model_path:
|
|
51
|
+
# 如果环境变量指定的是目录而非文件,则在目录下查找model.tflite
|
|
52
|
+
if os.path.isdir(env_model_path):
|
|
53
|
+
model_path = os.path.join(env_model_path, "model.tflite")
|
|
54
|
+
else:
|
|
55
|
+
model_path = env_model_path
|
|
56
|
+
|
|
57
|
+
if os.path.exists(model_path):
|
|
58
|
+
return model_path
|
|
59
|
+
|
|
60
|
+
# 确定平台相关的用户缓存目录
|
|
61
|
+
system = platform.system()
|
|
62
|
+
if system == "Windows":
|
|
63
|
+
cache_dir = os.path.join(os.environ.get("LOCALAPPDATA"), "nsfwpy")
|
|
64
|
+
elif system == "Darwin": # macOS
|
|
65
|
+
cache_dir = os.path.join(os.path.expanduser("~"), "Library", "Caches", "nsfwpy")
|
|
66
|
+
else: # Linux和其他系统
|
|
67
|
+
cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "nsfwpy")
|
|
68
|
+
|
|
69
|
+
# 确保目录存在
|
|
70
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
model_path = os.path.join(cache_dir, "model.tflite")
|
|
73
|
+
# 检查模型文件是否存在,不存在则下载
|
|
74
|
+
if not os.path.exists(model_path):
|
|
75
|
+
print(f"模型文件不存在,正在下载到 {model_path}...")
|
|
76
|
+
try:
|
|
77
|
+
self._download_file(self.MODEL_URL, model_path)
|
|
78
|
+
print("模型下载完成")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
raise ValueError(f"模型下载失败: {e}")
|
|
81
|
+
|
|
82
|
+
return model_path
|
|
83
|
+
|
|
84
|
+
def _download_file(self, url, destination):
|
|
85
|
+
"""从指定URL下载文件到目标路径"""
|
|
86
|
+
try:
|
|
87
|
+
with urllib.request.urlopen(url) as response:
|
|
88
|
+
with open(destination, "wb") as f:
|
|
89
|
+
f.write(response.read())
|
|
90
|
+
except Exception as e:
|
|
91
|
+
raise ValueError(f"下载失败: {e}")
|
|
92
|
+
|
|
93
|
+
def _load_image(self, image_path):
|
|
94
|
+
"""加载并处理单个图像"""
|
|
95
|
+
try:
|
|
96
|
+
image = Image.open(image_path)
|
|
97
|
+
if image.mode != 'RGB':
|
|
98
|
+
image = image.convert('RGB')
|
|
99
|
+
image = image.resize((self.image_dim, self.image_dim), Image.NEAREST)
|
|
100
|
+
image = np.array(image, dtype=np.float32) / 255.0
|
|
101
|
+
return image
|
|
102
|
+
except Exception as ex:
|
|
103
|
+
print(f"处理图像出错 {image_path}: {ex}")
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def _process_pil_image(self, pil_image):
|
|
107
|
+
"""处理PIL图像对象"""
|
|
108
|
+
try:
|
|
109
|
+
if pil_image.mode != 'RGB':
|
|
110
|
+
pil_image = pil_image.convert('RGB')
|
|
111
|
+
resized_image = pil_image.resize((self.image_dim, self.image_dim), Image.NEAREST)
|
|
112
|
+
image = np.array(resized_image, dtype=np.float32) / 255.0
|
|
113
|
+
return image
|
|
114
|
+
except Exception as ex:
|
|
115
|
+
print(f"处理PIL图像出错: {ex}")
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
def _predict_single(self, image):
|
|
119
|
+
"""对单个图像进行预测"""
|
|
120
|
+
self.interpreter.set_tensor(self.input_details[0]['index'], np.expand_dims(image, axis=0))
|
|
121
|
+
self.interpreter.invoke()
|
|
122
|
+
return self.interpreter.get_tensor(self.output_details[0]['index'])[0]
|
|
123
|
+
|
|
124
|
+
def _format_predictions(self, predictions):
|
|
125
|
+
"""将预测结果格式化为类别和概率"""
|
|
126
|
+
# 按概率降序排列
|
|
127
|
+
indices = np.argsort(-predictions)
|
|
128
|
+
result = {}
|
|
129
|
+
for i, idx in enumerate(indices):
|
|
130
|
+
category = self.CATEGORIES[idx]
|
|
131
|
+
probability = float(predictions[idx])
|
|
132
|
+
result[category] = probability
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
def predict_image(self, image_path):
|
|
136
|
+
"""
|
|
137
|
+
预测单个图像的NSFW内容
|
|
138
|
+
|
|
139
|
+
参数:
|
|
140
|
+
image_path: 图像文件路径
|
|
141
|
+
|
|
142
|
+
返回:
|
|
143
|
+
包含各类别预测概率的字典
|
|
144
|
+
"""
|
|
145
|
+
if not os.path.exists(image_path):
|
|
146
|
+
raise ValueError(f"图像文件不存在: {image_path}")
|
|
147
|
+
|
|
148
|
+
image = self._load_image(image_path)
|
|
149
|
+
if image is None:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
predictions = self._predict_single(image)
|
|
153
|
+
return self._format_predictions(predictions)
|
|
154
|
+
|
|
155
|
+
def predict_pil_image(self, pil_image):
|
|
156
|
+
"""
|
|
157
|
+
从PIL图像对象预测NSFW内容
|
|
158
|
+
|
|
159
|
+
参数:
|
|
160
|
+
pil_image: PIL图像对象
|
|
161
|
+
|
|
162
|
+
返回:
|
|
163
|
+
包含各类别预测概率的字典
|
|
164
|
+
"""
|
|
165
|
+
image = self._process_pil_image(pil_image)
|
|
166
|
+
if image is None:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
predictions = self._predict_single(image)
|
|
170
|
+
return self._format_predictions(predictions)
|
|
171
|
+
|
|
172
|
+
def predict_from_bytes(self, image_bytes):
|
|
173
|
+
"""
|
|
174
|
+
从字节流预测NSFW内容
|
|
175
|
+
|
|
176
|
+
参数:
|
|
177
|
+
image_bytes: 图像字节流
|
|
178
|
+
|
|
179
|
+
返回:
|
|
180
|
+
包含各类别预测概率的字典
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
image = Image.open(io.BytesIO(image_bytes))
|
|
184
|
+
return self.predict_pil_image(image)
|
|
185
|
+
except Exception as ex:
|
|
186
|
+
print(f"从字节流处理图像出错: {ex}")
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def predict_batch(self, image_paths):
|
|
190
|
+
"""
|
|
191
|
+
批量预测多个图像
|
|
192
|
+
|
|
193
|
+
参数:
|
|
194
|
+
image_paths: 单个图像路径或包含图像的目录
|
|
195
|
+
|
|
196
|
+
返回:
|
|
197
|
+
包含每个图像预测结果的列表
|
|
198
|
+
"""
|
|
199
|
+
# 处理目录参数
|
|
200
|
+
if os.path.isdir(image_paths):
|
|
201
|
+
paths = [os.path.join(image_paths, f) for f in os.listdir(image_paths)
|
|
202
|
+
if os.path.isfile(os.path.join(image_paths, f))]
|
|
203
|
+
elif isinstance(image_paths, list):
|
|
204
|
+
paths = image_paths
|
|
205
|
+
else:
|
|
206
|
+
paths = [image_paths]
|
|
207
|
+
|
|
208
|
+
results = []
|
|
209
|
+
for path in paths:
|
|
210
|
+
prediction = self.predict_image(path)
|
|
211
|
+
if prediction:
|
|
212
|
+
results.append(prediction)
|
|
213
|
+
|
|
214
|
+
return results
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import uvicorn
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
parser = argparse.ArgumentParser(description="启动NSFW检测API服务器或命令行工具")
|
|
8
|
+
parser.add_argument("--host", default="0.0.0.0", help="API服务器主机名")
|
|
9
|
+
parser.add_argument("--port", type=int, default=8000, help="API服务器端口")
|
|
10
|
+
parser.add_argument("--model", help="模型文件路径")
|
|
11
|
+
parser.add_argument("-w", "--web", action="store_true", help="启用Web API服务")
|
|
12
|
+
parser.add_argument("--input", help="要检测的图像文件或目录")
|
|
13
|
+
parser.add_argument("--dim", type=int, default=224, help="图像尺寸(默认:224)")
|
|
14
|
+
|
|
15
|
+
args, unknown_args = parser.parse_known_args()
|
|
16
|
+
|
|
17
|
+
# 如果指定了模型路径,设置环境变量
|
|
18
|
+
if args.model:
|
|
19
|
+
os.environ["NSFW_MODEL_PATH"] = str(Path(args.model).absolute())
|
|
20
|
+
|
|
21
|
+
# 只在指定--web参数时启动API服务器
|
|
22
|
+
if args.web:
|
|
23
|
+
# 启动服务器
|
|
24
|
+
# 移除上传文件大小限制
|
|
25
|
+
from starlette.formparsers import MultiPartParser
|
|
26
|
+
MultiPartParser.max_part_size = 1024 * 1024 * 1024
|
|
27
|
+
MultiPartParser.max_file_size = 1024 * 1024 * 1024
|
|
28
|
+
uvicorn.run("nsfwpy.api:app", host=args.host, port=args.port)
|
|
29
|
+
else:
|
|
30
|
+
# 运行命令行版本
|
|
31
|
+
from nsfwpy.cli import main as cli_main
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
# 重建参数,传递给cli模块
|
|
35
|
+
cli_args = ["--dim", str(args.dim)]
|
|
36
|
+
if args.model:
|
|
37
|
+
cli_args.extend(["--model", args.model])
|
|
38
|
+
if args.input:
|
|
39
|
+
cli_args.extend(["--input", args.input])
|
|
40
|
+
|
|
41
|
+
# 添加未知参数
|
|
42
|
+
sys.argv[1:] = cli_args + unknown_args
|
|
43
|
+
cli_main()
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
main()
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nsfwpy
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: 基于OpenNSFW的图像内容检测工具
|
|
5
|
+
Home-page: https://github.com/HG-ha/nsfwpy
|
|
6
|
+
Author: YiMing
|
|
7
|
+
Author-email: 1790233968@qq.com
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Description: # nsfwpy
|
|
10
|
+
|
|
11
|
+
[English](README_EN.md) | 简体中文
|
|
12
|
+
|
|
13
|
+
图像敏感内容检测工具,提供简单易用的Python接口进行图像内容分析和过滤,并提供API和CLI工具。
|
|
14
|
+
|
|
15
|
+
## 简介
|
|
16
|
+
|
|
17
|
+
nsfwpy 是一个轻量级Python库,使用深度学习模型进行图像内容分析,可以识别图像是否包含不适宜内容。本项目基于[nsfw_model](https://github.com/GantMan/nsfw_model)提供的模型实现。
|
|
18
|
+
|
|
19
|
+
## 特性
|
|
20
|
+
|
|
21
|
+
- 轻量级实现,依赖少,易于部署
|
|
22
|
+
- 支持多种图像格式输入(JPG、PNG等)
|
|
23
|
+
- 提供命令行工具、Python API和HTTP API接口
|
|
24
|
+
- 使用TensorFlow Lite优化性能
|
|
25
|
+
- 支持Windows和其他操作系统
|
|
26
|
+
- 自动下载和缓存模型文件
|
|
27
|
+
|
|
28
|
+
## 安装要求
|
|
29
|
+
> 由依赖导致安装出错时,可以尝试移除版本号安装,但tflite-runtime版本必须>=2.5.0
|
|
30
|
+
- Python 3.7+
|
|
31
|
+
- NumPy <= 1.26.4
|
|
32
|
+
- Pillow <= 11.1.0
|
|
33
|
+
- FastAPI <= 0.115.11
|
|
34
|
+
- uvicorn <= 0.34.0
|
|
35
|
+
- python-multipart <= 0.0.20
|
|
36
|
+
- tflite-runtime = 2.13.0 (Windows) 或 >= 2.5.0 (其他系统)
|
|
37
|
+
|
|
38
|
+
## 安装
|
|
39
|
+
|
|
40
|
+
### 通过pip安装
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install nsfwpy
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 从源码安装
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/HG-ha/nsfwpy.git
|
|
50
|
+
cd nsfwpy
|
|
51
|
+
pip install -e .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 使用方法
|
|
55
|
+
|
|
56
|
+
### Python API
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from nsfwpy import NSFW
|
|
60
|
+
|
|
61
|
+
# 初始化检测器(首次运行会自动下载模型)
|
|
62
|
+
detector = NSFW()
|
|
63
|
+
|
|
64
|
+
# 预测单个图像
|
|
65
|
+
result = detector.predict_image("path/to/image.jpg")
|
|
66
|
+
print(result)
|
|
67
|
+
|
|
68
|
+
# 预测PIL图像
|
|
69
|
+
from PIL import Image
|
|
70
|
+
img = Image.open("path/to/image.jpg")
|
|
71
|
+
result = detector.predict_pil_image(img)
|
|
72
|
+
print(result)
|
|
73
|
+
|
|
74
|
+
# 批量预测目录中的图像
|
|
75
|
+
results = detector.predict_batch("path/to/image/directory")
|
|
76
|
+
print(results)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 命令行工具
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 基本用法
|
|
83
|
+
nsfwpy --input path/to/image.jpg
|
|
84
|
+
|
|
85
|
+
# 指定自定义模型路径
|
|
86
|
+
nsfwpy --model path/to/model.tflite --input path/to/image.jpg
|
|
87
|
+
|
|
88
|
+
# 指定图像尺寸
|
|
89
|
+
nsfwpy --dim 299 --input path/to/image.jpg
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Web API服务(完全兼容 nsfwjs-api)
|
|
93
|
+
|
|
94
|
+
启动API服务器:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# 基本用法
|
|
98
|
+
nsfwpy -w
|
|
99
|
+
|
|
100
|
+
# 指定主机和端口
|
|
101
|
+
nsfwpy -w --host 127.0.0.1 --port 8080
|
|
102
|
+
|
|
103
|
+
# 指定自定义模型
|
|
104
|
+
nsfwpy -w --model path/to/model.tflite
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
API端点:
|
|
108
|
+
- `POST /classify`: 分析单张图片
|
|
109
|
+
- `POST /classify-many`: 批量分析多张图片
|
|
110
|
+
|
|
111
|
+
### 预测结果格式
|
|
112
|
+
|
|
113
|
+
返回包含以下类别概率值的字典:
|
|
114
|
+
```python
|
|
115
|
+
{
|
|
116
|
+
"drawings": 0.1, # 绘画/动画
|
|
117
|
+
"hentai": 0.0, # 动漫色情内容
|
|
118
|
+
"neutral": 0.8, # 中性/安全内容
|
|
119
|
+
"porn": 0.0, # 色情内容
|
|
120
|
+
"sexy": 0.1 # 性感内容
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 开发说明
|
|
125
|
+
|
|
126
|
+
- 项目使用MIT许可证
|
|
127
|
+
- 欢迎提交Issue和Pull Request
|
|
128
|
+
- 自动发布到PyPI使用GitHub Actions
|
|
129
|
+
|
|
130
|
+
## 致谢
|
|
131
|
+
|
|
132
|
+
本项目的模型基于[nsfw_model](https://github.com/GantMan/nsfw_model)。感谢原作者的贡献。
|
|
133
|
+
|
|
134
|
+
## 许可证
|
|
135
|
+
|
|
136
|
+
[MIT License](LICENSE)
|
|
137
|
+
|
|
138
|
+
Platform: UNKNOWN
|
|
139
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
140
|
+
Classifier: Operating System :: OS Independent
|
|
141
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
nsfwpy/__init__.py
|
|
4
|
+
nsfwpy/api.py
|
|
5
|
+
nsfwpy/cli.py
|
|
6
|
+
nsfwpy/nsfw.py
|
|
7
|
+
nsfwpy/server.py
|
|
8
|
+
nsfwpy.egg-info/PKG-INFO
|
|
9
|
+
nsfwpy.egg-info/SOURCES.txt
|
|
10
|
+
nsfwpy.egg-info/dependency_links.txt
|
|
11
|
+
nsfwpy.egg-info/entry_points.txt
|
|
12
|
+
nsfwpy.egg-info/requires.txt
|
|
13
|
+
nsfwpy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nsfwpy
|
nsfwpy-0.0.1/setup.cfg
ADDED
nsfwpy-0.0.1/setup.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import setuptools
|
|
2
|
+
import platform
|
|
3
|
+
|
|
4
|
+
with open("README.md", "r", encoding='utf8') as fh:
|
|
5
|
+
long_description = fh.read()
|
|
6
|
+
|
|
7
|
+
install_requires = [
|
|
8
|
+
"numpy<=1.26.4",
|
|
9
|
+
"pillow<=11.1.0",
|
|
10
|
+
"fastapi<=0.115.11",
|
|
11
|
+
"uvicorn<=0.34.0",
|
|
12
|
+
"python-multipart<=0.0.20"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
if platform.system() == "Windows":
|
|
16
|
+
dependency_links = [
|
|
17
|
+
"https://ghproxy.cn/github.com/NexelOfficial/tflite-runtime-win/raw/main/win_amd64/tflite_runtime-2.13.0-cp311-cp311-win_amd64.whl"
|
|
18
|
+
]
|
|
19
|
+
install_requires.append("tflite-runtime==2.13.0")
|
|
20
|
+
else:
|
|
21
|
+
dependency_links = []
|
|
22
|
+
install_requires.append("tflite-runtime>=2.5.0")
|
|
23
|
+
|
|
24
|
+
setuptools.setup(
|
|
25
|
+
name="nsfwpy",
|
|
26
|
+
version="0.0.1",
|
|
27
|
+
author="YiMing",
|
|
28
|
+
author_email="1790233968@qq.com",
|
|
29
|
+
description="基于OpenNSFW的图像内容检测工具",
|
|
30
|
+
long_description=long_description,
|
|
31
|
+
long_description_content_type="text/markdown",
|
|
32
|
+
url="https://github.com/HG-ha/nsfwpy",
|
|
33
|
+
packages=setuptools.find_packages(),
|
|
34
|
+
install_requires=install_requires,
|
|
35
|
+
dependency_links=dependency_links,
|
|
36
|
+
classifiers=[
|
|
37
|
+
"Programming Language :: Python :: 3.7",
|
|
38
|
+
"Operating System :: OS Independent",
|
|
39
|
+
],
|
|
40
|
+
entry_points={
|
|
41
|
+
'console_scripts': [
|
|
42
|
+
'nsfwpy=nsfwpy.server:main',
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
)
|