feat: 色情图片识别(NudeNet);明暗档位改为5档(20%~100%)

This commit is contained in:
2025-06-02 21:04:38 +08:00
parent ea88f3e766
commit a914b4be74
13 changed files with 450 additions and 25 deletions
+28 -2
View File
@@ -23,6 +23,7 @@ import (
"kenaito-media-picker/internal/config"
"kenaito-media-picker/internal/digest"
"kenaito-media-picker/internal/mover"
"kenaito-media-picker/internal/nsfw"
"kenaito-media-picker/internal/probe"
"kenaito-media-picker/internal/scanner"
"kenaito-media-picker/internal/store"
@@ -61,6 +62,7 @@ type Organizer struct {
store *store.Store // 数据库;为 nil 表示不记录媒体信息
mover *mover.Mover // 负责实际搬文件的家伙
ctx context.Context // 中止信号;ctx 被取消时探测/归类立即停下
nsfw *nsfw.Detector // 色情检测器;为 nil 表示没启用,图片不检测
buckets map[string][]string // 目标目录 → 里面该放哪些文件(只记源路径)
order []string // 目标目录的先后顺序(保证输出稳定、不来回跳)
onProgress func(phase string, done, total int)
@@ -88,6 +90,12 @@ func (o *Organizer) SetProgress(fn func(phase string, done, total int)) {
o.onProgress = fn
}
// SetNSFWDetector 设置色情检测器。传 nil 表示不检测(默认)。
// 设置了检测器后,图片探测时会额外做色情识别,命中的图片会归入「图片_敏感_*」目录。
func (o *Organizer) SetNSFWDetector(d *nsfw.Detector) {
o.nsfw = d
}
// report 上报进度。total 为 0 表示这一步没活干,就不用报了。
func (o *Organizer) report(phase string, done, total int) {
if o.onProgress != nil && total > 0 {
@@ -279,6 +287,8 @@ type imageProbeResult struct {
size int64 // 文件大小
darkScore float64 // 暗色像素占比(用于亮度分类)
lightScore float64 // 亮色像素占比
nsfwScore float64 // 色情检测最高分(未检测为 0)
nsfw bool // 是否判定为色情(检测可用时才可能为 true)
}
// probeImage 给一张图片做体检(并行阶段执行):探测尺寸 + 算 MD5 + 分析明暗。
@@ -318,6 +328,17 @@ func (o *Organizer) probeImage(f scanner.File) imageProbeResult {
r.darkScore, r.lightScore = dark, light
}
}
// 色情识别(GIF 不做;没启用检测器就直接跳过)
if o.nsfw != nil && !strings.EqualFold(f.Ext, ".gif") {
score, hit, derr := o.nsfw.Detect(f.Path)
if derr != nil {
// 检测失败按"未命中"处理,不影响整理流程
fmt.Printf("=== 色情检测失败(按未命中归类): %s, 错误: %v \n", f.Path, derr)
} else {
r.nsfwScore = score
r.nsfw = hit
}
}
return r
}
@@ -389,9 +410,14 @@ func (o *Organizer) ProcessImages(images []scanner.File) {
dup++
fmt.Printf("=== 重复图片(同路径), 忽略: %s \n", f.Path)
default:
// 正常图片:按分辨率/方向/明暗归入对应的分类目录
// 正常图片:按分辨率/方向/明暗归入对应的分类目录;
// 被判为色情的改去「图片_敏感_*」目录,明暗子目录照常保留
o.record(f, "image", r.res, r.md5Str, r.headMd5, r.tailMd5, r.size)
o.add(f, classify.ImageDir(f.Ext, r.res.Width, r.res.Height, r.darkScore, r.lightScore))
if r.nsfw {
o.add(f, classify.ImageDirSensitive(f.Ext, r.res.Width, r.res.Height, r.darkScore, r.lightScore))
} else {
o.add(f, classify.ImageDir(f.Ext, r.res.Width, r.res.Height, r.darkScore, r.lightScore))
}
success++
}
}