Skip to content

Commit 3d9546b

Browse files
author
Leandro
committed
Skip the temp-file/JPEG round trip when UseGray is set
AddImageToDataset, RecognizeSingle, and RecognizeMultiples used to write a grayscale JPEG to a temp file just so go-face could immediately decode that same JPEG back into pixels. With go-face v1.0.7's RecognizeRaw (Kagami/go-face#69), the already-decoded, already-grayed pixels go straight to goFace -- no temp file, no JPEG encode, no JPEG decode. UseGray=false is unchanged: the file is still handed straight to go-face, which still requires JPEG (see #45's boundary, already documented/tested). createTempGrayFile/tempFileName are gone, replaced by loadPixels (decode + grayscale + pack into an RGB byte buffer) in image.go, and a new detect() helper in recognizer.go shared by AddImageToDataset and RecognizeMultiples (RecognizeSingle keeps its own branch, using goFace's Single variants directly rather than detecting all faces and discarding extras, matching its prior performance characteristics). Verified: full test suite passes under -race, and it got noticeably faster (~50s -> ~26s) with the temp-file I/O and JPEG round trip gone. Also re-ran all three examples end-to-end -- faces.jpg/faces2.jpg came out byte-identical (annotation draws over the original color image, not the internal recognition path), landmarks.jpg shifted by ordinary decoder-rounding differences only.
1 parent 5f2db5d commit 3d9546b

4 files changed

Lines changed: 65 additions & 77 deletions

File tree

examples/landmarks.jpg

6 Bytes
Loading

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ require (
88
golang.org/x/image v0.44.0
99
)
1010

11-
require github.com/leandroveronezi/go-face v1.0.5
11+
require github.com/leandroveronezi/go-face v1.0.7

image.go

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package recognizer
22

33
import (
4-
"crypto/rand"
5-
"encoding/hex"
64
goFace "github.com/leandroveronezi/go-face"
75
"github.com/fogleman/gg"
86
"github.com/golang/freetype/truetype"
@@ -11,7 +9,6 @@ import (
119
"image/color"
1210
"image/jpeg"
1311
"os"
14-
"path/filepath"
1512
)
1613

1714
/*
@@ -79,35 +76,37 @@ func (_this *Recognizer) GrayScale(imgSrc image.Image) image.Image {
7976
}
8077

8178
/*
82-
createTempGrayFile create a temporary image in grayscale
79+
loadPixels loads Path, converts it to grayscale, and returns it as a
80+
tightly packed RGB pixel buffer (3 bytes per pixel, row-major) ready for
81+
goFace's Raw recognition methods -- no JPEG encoding/decoding involved.
8382
*/
84-
func (_this *Recognizer) createTempGrayFile(Path, Id string) (string, error) {
85-
86-
name := _this.tempFileName(Id, ".jpeg")
83+
func (_this *Recognizer) loadPixels(Path string) (pixels []byte, width, height int, err error) {
8784

8885
img, err := _this.LoadImage(Path)
8986

9087
if err != nil {
91-
return "", err
88+
return nil, 0, 0, err
9289
}
9390

9491
img = _this.GrayScale(img)
95-
err = _this.SaveImage(name, img)
9692

97-
if err != nil {
98-
os.Remove(name)
99-
return "", err
100-
}
93+
bounds := img.Bounds()
94+
width, height = bounds.Dx(), bounds.Dy()
95+
pixels = make([]byte, width*height*3)
10196

102-
return name, nil
97+
i := 0
98+
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
99+
for x := bounds.Min.X; x < bounds.Max.X; x++ {
100+
r, g, b, _ := img.At(x, y).RGBA()
101+
pixels[i] = byte(r >> 8)
102+
pixels[i+1] = byte(g >> 8)
103+
pixels[i+2] = byte(b >> 8)
104+
i += 3
105+
}
106+
}
103107

104-
}
108+
return pixels, width, height, nil
105109

106-
// tempFileName generates a temporary filename
107-
func (_this *Recognizer) tempFileName(prefix, suffix string) string {
108-
randBytes := make([]byte, 16)
109-
rand.Read(randBytes)
110-
return filepath.Join(os.TempDir(), prefix+hex.EncodeToString(randBytes)+suffix)
111110
}
112111

113112
/*

recognizer.go

Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -111,38 +111,47 @@ func (_this *Recognizer) Close() {
111111
}
112112

113113
/*
114-
AddImageToDataset add a sample image to the dataset.
115-
116-
The new entry is appended to the underlying classifier immediately (via
117-
goFace.AppendSample), so it's classifiable right away -- no need to call
118-
SetSamples afterward. SetSamples is still required after LoadDataset or
119-
after mutating Dataset directly, since those don't go through this
120-
incremental path.
114+
detect runs face detection on Path, returning every face found. It
115+
prefers the raw-pixel path when UseGray is set -- decoding, grayscaling,
116+
and handing pixels straight to goFace, with no JPEG round trip involved
117+
-- and otherwise hands Path straight to go-face, which only understands
118+
JPEG.
121119
*/
122-
func (_this *Recognizer) AddImageToDataset(Path string, Id string) error {
123-
124-
file := Path
125-
var err error
120+
func (_this *Recognizer) detect(Path string) ([]goFace.Face, error) {
126121

127122
if _this.UseGray {
128123

129-
file, err = _this.createTempGrayFile(file, Id)
130-
124+
pixels, width, height, err := _this.loadPixels(Path)
131125
if err != nil {
132-
return err
126+
return nil, err
133127
}
134128

135-
defer os.Remove(file)
129+
if _this.UseCNN {
130+
return _this.rec.RecognizeRawCNN(pixels, width, height)
131+
}
132+
return _this.rec.RecognizeRaw(pixels, width, height)
136133

137134
}
138135

139-
var faces []goFace.Face
140-
141136
if _this.UseCNN {
142-
faces, err = _this.rec.RecognizeFileCNN(file)
143-
} else {
144-
faces, err = _this.rec.RecognizeFile(file)
137+
return _this.rec.RecognizeFileCNN(Path)
145138
}
139+
return _this.rec.RecognizeFile(Path)
140+
141+
}
142+
143+
/*
144+
AddImageToDataset add a sample image to the dataset.
145+
146+
The new entry is appended to the underlying classifier immediately (via
147+
goFace.AppendSample), so it's classifiable right away -- no need to call
148+
SetSamples afterward. SetSamples is still required after LoadDataset or
149+
after mutating Dataset directly, since those don't go through this
150+
incremental path.
151+
*/
152+
func (_this *Recognizer) AddImageToDataset(Path string, Id string) error {
153+
154+
faces, err := _this.detect(Path)
146155

147156
if err != nil {
148157
return err
@@ -199,31 +208,33 @@ func (_this *Recognizer) SetSamples() {
199208

200209
/*
201210
RecognizeSingle returns face if it's the only face on the image or nil otherwise.
202-
Only JPEG format is currently supported.
203211
*/
204212
func (_this *Recognizer) RecognizeSingle(Path string) (goFace.Face, error) {
205213

206-
file := Path
214+
var idFace *goFace.Face
207215
var err error
208216

209217
if _this.UseGray {
210218

211-
file, err = _this.createTempGrayFile(file, "64ab59ac42d69274f06eadb11348969e")
212-
213-
if err != nil {
214-
return goFace.Face{}, err
219+
pixels, width, height, lerr := _this.loadPixels(Path)
220+
if lerr != nil {
221+
return goFace.Face{}, lerr
215222
}
216223

217-
defer os.Remove(file)
224+
if _this.UseCNN {
225+
idFace, err = _this.rec.RecognizeSingleRawCNN(pixels, width, height)
226+
} else {
227+
idFace, err = _this.rec.RecognizeSingleRaw(pixels, width, height)
228+
}
218229

219-
}
230+
} else {
220231

221-
var idFace *goFace.Face
232+
if _this.UseCNN {
233+
idFace, err = _this.rec.RecognizeSingleFileCNN(Path)
234+
} else {
235+
idFace, err = _this.rec.RecognizeSingleFile(Path)
236+
}
222237

223-
if _this.UseCNN {
224-
idFace, err = _this.rec.RecognizeSingleFileCNN(file)
225-
} else {
226-
idFace, err = _this.rec.RecognizeSingleFile(file)
227238
}
228239

229240
if err != nil {
@@ -242,32 +253,10 @@ func (_this *Recognizer) RecognizeSingle(Path string) (goFace.Face, error) {
242253
RecognizeMultiples returns all faces found on the provided image, sorted from
243254
left to right. Empty list is returned if there are no faces, error is
244255
returned if there was some error while decoding/processing image.
245-
Only JPEG format is currently supported.
246256
*/
247257
func (_this *Recognizer) RecognizeMultiples(Path string) ([]goFace.Face, error) {
248258

249-
file := Path
250-
var err error
251-
252-
if _this.UseGray {
253-
254-
file, err = _this.createTempGrayFile(file, "64ab59ac42d69274f06eadb11348969e")
255-
256-
if err != nil {
257-
return nil, err
258-
}
259-
260-
defer os.Remove(file)
261-
262-
}
263-
264-
var idFaces []goFace.Face
265-
266-
if _this.UseCNN {
267-
idFaces, err = _this.rec.RecognizeFileCNN(file)
268-
} else {
269-
idFaces, err = _this.rec.RecognizeFile(file)
270-
}
259+
idFaces, err := _this.detect(Path)
271260

272261
if err != nil {
273262
return nil, fmt.Errorf("Can't recognize: %v", err)

0 commit comments

Comments
 (0)