feat: 色情识别结果可视化与日志;超长路径修复;旋转开关、明暗5档等
This commit is contained in:
+66
-3
@@ -2,14 +2,29 @@
|
||||
"""NudeNet 本地检测服务:由 Go 侧启动,常驻提供 /health 和 /detect。
|
||||
|
||||
只依赖 Python 标准库 + nudenet 包,模型在进程里只加载一次。
|
||||
收到 /detect 就把图片路径丢给 NudeDetector,返回结果 JSON。
|
||||
收到 /detect 就把图片喂给 NudeDetector,返回结果 JSON。
|
||||
|
||||
Windows 超长路径说明:OpenCV 的 imread 走老式 C API,路径超过 260 字符就
|
||||
打不开(报 can't open/read file)。所以这里改用 Python 的 open() 读字节,
|
||||
再把字节直接交给 NudeNet(v3 的 detect 支持 bytes)——从内存解码,彻底
|
||||
绕开路径限制,同时兼容老版 v2(只收路径时落到临时短路径文件再试)。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
from nudenet import NudeDetector
|
||||
|
||||
# 只报 OpenCV 致命错误,别让 imread 的 WARN 刷屏(也避免误导排查)
|
||||
try:
|
||||
import cv2
|
||||
|
||||
cv2.setLogLevel(8) # LOG_LEVEL_ERROR
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 只有这些"暴露部位"才算色情;其它如肚子/腋窝/脚踝暴露不判。
|
||||
SENSITIVE = {
|
||||
"FEMALE_GENITALIA_EXPOSED",
|
||||
@@ -28,6 +43,51 @@ def get_detector():
|
||||
return _detector
|
||||
|
||||
|
||||
def _longpath(path):
|
||||
"""Windows 下给绝对路径加 \\\\?\\ 前缀,让 Python 原生 open 也能处理超长路径。"""
|
||||
if os.name != "nt":
|
||||
return path
|
||||
if path.startswith("\\\\?\\"):
|
||||
return path
|
||||
if path.startswith("\\\\"):
|
||||
# UNC 网络路径:\\server\share → \\?\UNC\server\share
|
||||
return "\\\\?\\UNC\\" + path.lstrip("\\")
|
||||
if os.path.isabs(path):
|
||||
return "\\\\?\\" + os.path.normpath(path)
|
||||
return path
|
||||
|
||||
|
||||
def _read_bytes(path):
|
||||
"""用 Python 读文件字节(不经过 OpenCV),读不了就抛异常由上层兜底。"""
|
||||
lp = _longpath(path)
|
||||
if not os.path.exists(lp):
|
||||
raise OSError("文件不存在: %s" % path)
|
||||
with open(lp, "rb") as f:
|
||||
data = f.read()
|
||||
if not data:
|
||||
raise OSError("空文件: %s" % path)
|
||||
return data
|
||||
|
||||
|
||||
def _detect_bytes(det, data, path):
|
||||
"""把图片字节喂给 NudeNet。v3 直接吃 bytes;老版 v2 只收路径时落到临时短路径重试。"""
|
||||
try:
|
||||
return det.detect(data)
|
||||
except (TypeError, AttributeError):
|
||||
tmp = None
|
||||
try:
|
||||
fd, tmp = tempfile.mkstemp(suffix=os.path.splitext(path)[1] or ".jpg")
|
||||
with os.fdopen(fd, "wb") as f:
|
||||
f.write(data)
|
||||
return det.detect(tmp)
|
||||
finally:
|
||||
if tmp:
|
||||
try:
|
||||
os.unlink(tmp)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def _send(self, obj, status=200):
|
||||
data = json.dumps(obj, ensure_ascii=False).encode("utf-8")
|
||||
@@ -51,7 +111,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
body = json.loads(self.rfile.read(length) or b"{}")
|
||||
path = body.get("path", "")
|
||||
threshold = float(body.get("threshold", 0.5))
|
||||
detections = get_detector().detect(path)
|
||||
if not path:
|
||||
return self._send({"ok": False, "error": "缺少图片路径"})
|
||||
data = _read_bytes(path)
|
||||
detections = _detect_bytes(get_detector(), data, path)
|
||||
except Exception as exc: # noqa: BLE001 - 服务端任何异常都要回给 Go,别让进程崩
|
||||
return self._send({"ok": False, "error": str(exc)})
|
||||
|
||||
@@ -73,4 +136,4 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 0
|
||||
HTTPServer(("127.0.0.1", port), Handler).serve_forever()
|
||||
HTTPServer(("127.0.0.1", port), Handler).serve_forever()
|
||||
Reference in New Issue
Block a user