Calibrate orb detection from botty reference: vertical slice method, correct HSV ranges

- Add ReadOrbSlicePercentage: thin vertical strip method for accurate fill reading
- Support HSV hue wrapping in colorInRange (for red hues crossing 360°)
- Health HSV: H 350-370 (wrapping), S 60+, V 20+ (calibrated from screenshot)
- Mana HSV: H 226-240, S 100+, V 20+ (calibrated from screenshot)
- Add health_slice/mana_slice regions (10px wide vertical strips through orb centers)
- Update health_globe/mana_globe to full botty-referenced regions
- Verified: health 96.3%, mana 100% on testdata/d2r_1080p.png (both at 100% fill)
This commit is contained in:
Hoid 2026-02-14 11:56:18 +00:00
parent 0716aeb5e1
commit 35a1944179
8 changed files with 117 additions and 44 deletions

View file

@ -206,6 +206,36 @@ func (p *Pipeline) ReadOrbPercentage(frame image.Image, orbRegion image.Rectangl
return float64(filledPixels) / float64(totalPixels)
}
// ReadOrbSlicePercentage reads an orb's fill level using a thin vertical slice.
// The orb fills from bottom to top, so we count matching pixels in the slice
// and return the percentage. This is much more accurate than scanning the whole orb.
func (p *Pipeline) ReadOrbSlicePercentage(frame image.Image, sliceRegion image.Rectangle, filledColor ColorRange) float64 {
bounds := sliceRegion.Intersect(frame.Bounds())
if bounds.Empty() {
return 0.0
}
totalPixels := 0
filledPixels := 0
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
c := frame.At(x, y)
hsv := RGBToHSV(c)
totalPixels++
if p.colorInRange(hsv, filledColor) {
filledPixels++
}
}
}
if totalPixels == 0 {
return 0.0
}
return float64(filledPixels) / float64(totalPixels)
}
// GetPixelColor returns the color at a specific pixel.
func (p *Pipeline) GetPixelColor(frame image.Image, x, y int) color.Color {
return frame.At(x, y)
@ -281,10 +311,18 @@ func (p *Pipeline) colorsMatch(c1, c2 color.Color, tolerance int) bool {
}
// colorInRange checks if HSV color is within range.
// Supports hue wrapping: if UpperH > 360, it wraps around (e.g., 350-370 means 350-360 OR 0-10).
func (p *Pipeline) colorInRange(hsv HSV, colorRange ColorRange) bool {
return hsv.H >= colorRange.LowerH && hsv.H <= colorRange.UpperH &&
hsv.S >= colorRange.LowerS && hsv.S <= colorRange.UpperS &&
hsv.V >= colorRange.LowerV && hsv.V <= colorRange.UpperV
if hsv.S < colorRange.LowerS || hsv.S > colorRange.UpperS ||
hsv.V < colorRange.LowerV || hsv.V > colorRange.UpperV {
return false
}
// Handle hue wrapping
if colorRange.UpperH > 360 {
// Wrapping range: e.g., 350-370 means H >= 350 OR H <= 10
return hsv.H >= colorRange.LowerH || hsv.H <= (colorRange.UpperH-360)
}
return hsv.H >= colorRange.LowerH && hsv.H <= colorRange.UpperH
}
// floodFill finds connected pixels of the same color.