refactor(classify): 重构图片明暗分类逻辑

- 移除固定的明暗阈值等级数组,改用动态分档机制
- 引入 neutralMargin 常量,当两分差幅小于 0.15 时视为中性图
- 修改 BrightnessName 函数,实现三分法分类:bright_N、dark_N、mid
- 新增 brightnessTier 辅助函数,将占比分档到最接近的 10%
- 完全透明的像素(alpha=0)不再参与明暗统计计算
- 更新错误提示信息,明暗分析失败时按中性 mid 归类
- 调整 README 文档描述,同步更新明暗分类规则说明
This commit is contained in:
2025-04-01 21:07:48 +08:00
parent 99b9460bcd
commit 13bb195e1d
5 changed files with 40 additions and 45 deletions
+24 -26
View File
@@ -2,34 +2,32 @@ package classify
import "fmt"
// 暗色阈值等级(从高到低)
var darkThresholds = []int{100, 95, 85, 75, 65, 55}
// neutralMargin 判定明暗主导的最小分差:两分差幅小于该值视为中性图
const neutralMargin = 0.15
// 亮色阈值等级(从高到低)
var brightThresholds = []int{85, 75, 65, 55, 45, 35, 25, 15}
// BrightnessName 按主导分判定明暗等级,返回如 "dark_100" / "bright_85"。
// 亮色主导(lightScore > darkScore)归 bright,否则归 dark,每张图必归亮暗之一。
// BrightnessName 判定图片明暗等级,返回如 "dark_70" / "bright_40" / "mid"。
// 以主导分差幅判定亮暗:lightScore 高于 darkScore neutralMargin 以上归 bright,
// 反之归 dark,两者接近视为中性 mid(不再强制二选一)。
func BrightnessName(darkScore, lightScore float64) string {
darkLowest := darkThresholds[len(darkThresholds)-1]
brightLowest := brightThresholds[len(brightThresholds)-1]
if lightScore > darkScore {
picked := brightLowest
for _, t := range brightThresholds {
if lightScore >= float64(t)/100.0 {
picked = t
break
}
}
return fmt.Sprintf("bright_%d", picked)
switch {
case lightScore > darkScore+neutralMargin:
return fmt.Sprintf("bright_%d", brightnessTier(lightScore))
case darkScore > lightScore+neutralMargin:
return fmt.Sprintf("dark_%d", brightnessTier(darkScore))
default:
return "mid"
}
picked := darkLowest
for _, t := range darkThresholds {
if darkScore >= float64(t)/100.0 {
picked = t
break
}
}
// brightnessTier 将占比分档到最接近的 10%,表示命中区间 [N-5%, N+5%),最小 10、最大 100。
// 例如 0.30~0.39 归 "30",0.96 以上归 "100",避免占比不足却挂高档名。
func brightnessTier(score float64) int {
tier := int(score*100+5) / 10 * 10
if tier < 10 {
return 10
}
return fmt.Sprintf("dark_%d", picked)
if tier > 100 {
return 100
}
return tier
}
+1 -1
View File
@@ -35,7 +35,7 @@ func (o Orientation) String() string {
}
}
// ImageDir 返回图片归类子目录名,如 "图片_横屏_1080P-dark_100" / "图片_GIF"。
// ImageDir 返回图片归类子目录名,如 "图片_横屏_1080P-dark_70" / "图片_横屏_1080P-mid" / "图片_GIF"。
// darkScore/lightScore 用于明暗分类(GIF 不做明暗分类,忽略这两项)。
func ImageDir(ext string, width, height int, darkScore, lightScore float64) string {
if strings.EqualFold(ext, ".gif") {
+1 -1
View File
@@ -133,7 +133,7 @@ func (o *Organizer) ProcessImages(images []scanner.File) {
if !strings.EqualFold(f.Ext, ".gif") {
dark, light, berr := probe.AnalyzeBrightness(f.Path)
if berr != nil {
fmt.Printf("=== 明暗分析失败(按默认dark_55归类): %s \n", f.Path)
fmt.Printf("=== 明暗分析失败(按中性mid归类): %s \n", f.Path)
} else {
darkScore, lightScore = dark, light
}
+6 -2
View File
@@ -30,7 +30,8 @@ func AnalyzeBrightness(filePath string) (darkScore float64, lightScore float64,
return dark, light, nil
}
// analyzeBrightness 统计暗色/亮色像素占比(ITU-R BT.601 亮度公式),大图自动采样加速
// analyzeBrightness 统计暗色/亮色像素占比(ITU-R BT.601 亮度公式),大图自动采样加速。
// 完全透明的像素(alpha=0)不计入统计。
func analyzeBrightness(img image.Image) (darkScore float64, lightScore float64) {
bounds := img.Bounds()
totalPixels := bounds.Dx() * bounds.Dy()
@@ -46,7 +47,10 @@ func analyzeBrightness(img image.Image) (darkScore float64, lightScore float64)
for y := bounds.Min.Y; y < bounds.Max.Y; y += sampleStep {
for x := bounds.Min.X; x < bounds.Max.X; x += sampleStep {
r, g, b, _ := img.At(x, y).RGBA()
r, g, b, a := img.At(x, y).RGBA()
if a == 0 {
continue
}
r8 := uint8(r >> 8)
g8 := uint8(g >> 8)
b8 := uint8(b >> 8)