125 lines
3.5 KiB
Go
125 lines
3.5 KiB
Go
|
|
package imageproc
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"image"
|
||
|
|
"math"
|
||
|
|
"net/url"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type AnalysisImage struct {
|
||
|
|
Image image.Image
|
||
|
|
OriginalWidth int
|
||
|
|
OriginalHeight int
|
||
|
|
Region image.Rectangle
|
||
|
|
Strategy string
|
||
|
|
RoomLike bool
|
||
|
|
RoomLikeConfidence string
|
||
|
|
RoomLikeReason string
|
||
|
|
}
|
||
|
|
|
||
|
|
func PrepareAnalysisImage(src image.Image, source string) AnalysisImage {
|
||
|
|
b := src.Bounds()
|
||
|
|
roomLike, confidence, reason := DetectRoomLike(src, source)
|
||
|
|
region := b
|
||
|
|
strategy := "full_texture"
|
||
|
|
if roomLike {
|
||
|
|
top := b.Min.Y + int(float64(b.Dy())*0.45)
|
||
|
|
left := b.Min.X + int(float64(b.Dx())*0.08)
|
||
|
|
right := b.Max.X - int(float64(b.Dx())*0.08)
|
||
|
|
region = image.Rect(left, top, right, b.Max.Y).Intersect(b)
|
||
|
|
strategy = "room_like_lower_floor_crop"
|
||
|
|
}
|
||
|
|
return AnalysisImage{
|
||
|
|
Image: Crop(src, region), OriginalWidth: b.Dx(), OriginalHeight: b.Dy(),
|
||
|
|
Region: region, Strategy: strategy, RoomLike: roomLike,
|
||
|
|
RoomLikeConfidence: confidence, RoomLikeReason: reason,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func DetectRoomLike(src image.Image, source string) (bool, string, string) {
|
||
|
|
b := src.Bounds()
|
||
|
|
w, h := b.Dx(), b.Dy()
|
||
|
|
if w == 0 || h == 0 {
|
||
|
|
return false, "", ""
|
||
|
|
}
|
||
|
|
urlHint, hint := roomSceneHint(source)
|
||
|
|
ratio := float64(w) / float64(h)
|
||
|
|
roomGeometry := false
|
||
|
|
if urlHint {
|
||
|
|
top := image.Rect(b.Min.X, b.Min.Y, b.Max.X, b.Min.Y+h/2)
|
||
|
|
bottom := image.Rect(b.Min.X, b.Min.Y+h/2, b.Max.X, b.Max.Y)
|
||
|
|
topEdge, topVar := regionEdgeAndVariance(src, top)
|
||
|
|
bottomEdge, bottomVar := regionEdgeAndVariance(src, bottom)
|
||
|
|
roomGeometry = (ratio > 1.28 || ratio < 0.78) && bottomEdge > topEdge*1.25 && bottomVar > topVar*1.10
|
||
|
|
}
|
||
|
|
|
||
|
|
switch {
|
||
|
|
case urlHint && roomGeometry:
|
||
|
|
return true, "high", fmt.Sprintf("source image path has %q token plus room-like edge split (ratio %.2f)", hint, ratio)
|
||
|
|
case urlHint:
|
||
|
|
return true, "medium", fmt.Sprintf("source image path contains %q room-scene token", hint)
|
||
|
|
default:
|
||
|
|
return false, "", ""
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func roomSceneHint(source string) (bool, string) {
|
||
|
|
candidate := source
|
||
|
|
if parsed, err := url.Parse(source); err == nil && parsed.Path != "" {
|
||
|
|
candidate = parsed.Path
|
||
|
|
}
|
||
|
|
if decoded, err := url.PathUnescape(candidate); err == nil {
|
||
|
|
candidate = decoded
|
||
|
|
}
|
||
|
|
for _, token := range sceneTokens(strings.ToLower(candidate)) {
|
||
|
|
switch token {
|
||
|
|
case "room", "rooms", "lifestyle", "installed", "installation", "gallery", "ambient", "beauty":
|
||
|
|
return true, token
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false, ""
|
||
|
|
}
|
||
|
|
|
||
|
|
func sceneTokens(s string) []string {
|
||
|
|
return strings.FieldsFunc(s, func(r rune) bool {
|
||
|
|
return !((r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'))
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func regionEdgeAndVariance(src image.Image, rect image.Rectangle) (float64, float64) {
|
||
|
|
rect = rect.Intersect(src.Bounds())
|
||
|
|
if rect.Dx() < 3 || rect.Dy() < 3 {
|
||
|
|
return 0, 0
|
||
|
|
}
|
||
|
|
step := int(math.Sqrt(float64(rect.Dx()*rect.Dy())/12000.0)) + 1
|
||
|
|
var n int
|
||
|
|
var sum, sumSq float64
|
||
|
|
var edges int
|
||
|
|
for y := rect.Min.Y + step; y < rect.Max.Y-step; y += step {
|
||
|
|
for x := rect.Min.X + step; x < rect.Max.X-step; x += step {
|
||
|
|
l := luminanceAt(src, x, y)
|
||
|
|
lx := luminanceAt(src, x+step, y) - luminanceAt(src, x-step, y)
|
||
|
|
ly := luminanceAt(src, x, y+step) - luminanceAt(src, x, y-step)
|
||
|
|
if math.Hypot(lx, ly) > 26 {
|
||
|
|
edges++
|
||
|
|
}
|
||
|
|
sum += l
|
||
|
|
sumSq += l * l
|
||
|
|
n++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if n == 0 {
|
||
|
|
return 0, 0
|
||
|
|
}
|
||
|
|
mean := sum / float64(n)
|
||
|
|
variance := sumSq/float64(n) - mean*mean
|
||
|
|
return float64(edges) / float64(n), variance
|
||
|
|
}
|
||
|
|
|
||
|
|
func luminanceAt(src image.Image, x, y int) float64 {
|
||
|
|
r, g, b := rgba8(src.At(x, y))
|
||
|
|
return 0.2126*float64(r) + 0.7152*float64(g) + 0.0722*float64(b)
|
||
|
|
}
|