ListFaces now carries head yaw, so a caller choosing WHICH face to swap can see the thing that decides whether the swap will read — not only learn it afterwards from the swap report. In the run that prompted this the target's three faces sat at -82, -8 and -11 degrees; only the first was hopeless, and nothing in a bounding box said so.
138 lines
5.8 KiB
Go
138 lines
5.8 KiB
Go
package imagegen
|
|
|
|
import "context"
|
|
|
|
// FaceSwapRequest transfers an identity from Source into Target.
|
|
//
|
|
// This is a DIFFERENT OPERATION from Edit, not a better-tuned one. Measured
|
|
// against the instruction-edit models on 2026-07-31, asking a diffusion model
|
|
// to put a specific person's face into a photo does not work by any route —
|
|
// by name, by description, or by supplying the portrait as a reference image.
|
|
// Face swapping is a dedicated detect/align/blend pipeline; a provider that
|
|
// cannot do it should not pretend Edit is a substitute.
|
|
type FaceSwapRequest struct {
|
|
// Target is the photo to edit — the pose, expression, lighting and
|
|
// everything outside the face are preserved from it.
|
|
Target Image
|
|
|
|
// Source is a photo of the face to put in. Only the identity travels;
|
|
// the source's own pose and expression do not.
|
|
Source Image
|
|
|
|
// Index selects WHICH face in Target, in the provider's documented
|
|
// ordering (llamaswap: left to right by box centre, as reported by
|
|
// ListFaces). nil = the largest face, which is right for a portrait and
|
|
// wrong for a group — enumerate first when it matters.
|
|
Index *int
|
|
|
|
// All swaps every detected face and ignores Index.
|
|
All bool
|
|
}
|
|
|
|
// FaceSwapOption mutates a FaceSwapRequest before it is sent.
|
|
type FaceSwapOption func(*FaceSwapRequest)
|
|
|
|
// WithFaceIndex selects which face in the target to replace.
|
|
func WithFaceIndex(i int) FaceSwapOption { return func(r *FaceSwapRequest) { r.Index = &i } }
|
|
|
|
// WithAllFaces swaps every detected face.
|
|
func WithAllFaces() FaceSwapOption { return func(r *FaceSwapRequest) { r.All = true } }
|
|
|
|
// Apply returns a copy of the request with all options applied.
|
|
func (r FaceSwapRequest) Apply(opts ...FaceSwapOption) FaceSwapRequest {
|
|
for _, opt := range opts {
|
|
opt(&r)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// DetectedFace is one face located in an image, in PIXEL coordinates.
|
|
type DetectedFace struct {
|
|
// Index is the face's position in the provider's stable ordering, and
|
|
// the value FaceSwapRequest.Index expects.
|
|
Index int
|
|
// Box is [x0, y0, x1, y1].
|
|
Box [4]int
|
|
// Score is the detector's confidence, 0-1.
|
|
Score float64
|
|
|
|
// Yaw is how far the head is turned from camera, in degrees, or nil when
|
|
// the provider does not report pose. Exposed on ENUMERATION, not just
|
|
// after the fact, because it is how a caller picks a face a swap will
|
|
// actually work on: past roughly ±45° the features carrying identity are
|
|
// edge-on, and the result reads as a generic person however good the
|
|
// transfer is. A bounding box cannot show this.
|
|
Yaw *float64
|
|
}
|
|
|
|
// Size returns the box dimensions. Derived rather than stored: carrying
|
|
// width/height alongside Box is two sources of truth for one fact, and the
|
|
// pair can disagree after any transform.
|
|
func (f DetectedFace) Size() (w, h int) {
|
|
return f.Box[2] - f.Box[0], f.Box[3] - f.Box[1]
|
|
}
|
|
|
|
// SwappedFace is the MEASURED outcome for one face the provider replaced.
|
|
//
|
|
// It exists because "the call returned an image" and "the likeness
|
|
// transferred" are different claims that look identical from outside, and a
|
|
// caller that cannot tell them apart will go looking for another way to
|
|
// check. The one it reaches for — asking a vision model who the result looks
|
|
// like — is wrong in exactly the cases that matter: a VLM shown a jogger in a
|
|
// Georgetown cap holding McDonald's cups answers "Bill Clinton" whoever's
|
|
// face is on him, so it reports failure on a correct swap.
|
|
type SwappedFace struct {
|
|
// Index is the face's position in the provider's left-to-right ordering.
|
|
Index int
|
|
|
|
// Width, Height are the replaced face's pixel size in the TARGET.
|
|
// Meaningful only against ImageWidth/ImageHeight: a 138px face is large
|
|
// in a 400px picture and nearly invisible in a 2000px one, and it is the
|
|
// ratio, not the absolute size, that decides whether a person notices.
|
|
Width, Height int
|
|
|
|
// ImageWidth, ImageHeight are the target image's dimensions, repeated on
|
|
// every entry so a single face is self-describing without the caller
|
|
// holding onto the rest of the response.
|
|
ImageWidth, ImageHeight int
|
|
|
|
// Yaw is how far the head is turned from camera, in degrees, or nil when
|
|
// the provider does not report pose. The best single predictor of whether
|
|
// a swap will READ as the source person: past roughly ±45° the features
|
|
// carrying identity are edge-on and the result looks like a generic
|
|
// person rather than a specific one.
|
|
Yaw *float64
|
|
|
|
// IdentitySimilarity is cosine similarity between the source face and the
|
|
// face actually present in the result, 0-1, or nil when the provider
|
|
// could not measure it. Above ~0.5 the identity transferred; a LOW value
|
|
// is the only evidence that a swap genuinely failed.
|
|
IdentitySimilarity *float64
|
|
}
|
|
|
|
// FractionOfImage is the swapped face's width as a share of the image's, 0-1.
|
|
// The number that predicts whether a person will SEE the change: the swap
|
|
// that prompted all this replaced a 138px face in a 1010px-wide photo — 14%,
|
|
// correct by every measure and invisible at a glance — while the same code on
|
|
// a 168px face in a 385px picture (44%) is unmistakable. Returns 0 when the
|
|
// dimensions are unknown.
|
|
func (f SwappedFace) FractionOfImage() float64 {
|
|
if f.ImageWidth <= 0 || f.Width <= 0 {
|
|
return 0
|
|
}
|
|
return float64(f.Width) / float64(f.ImageWidth)
|
|
}
|
|
|
|
// FaceSwapper is the optional face-transfer surface. Separate interface so
|
|
// existing providers keep compiling; callers type-assert.
|
|
type FaceSwapper interface {
|
|
// ListFaces enumerates the faces in an image, in the SAME ordering
|
|
// FaceSwapRequest.Index uses. Exposed because a caller asked to change
|
|
// "the man on the right" needs a way to name one face and to check its
|
|
// own choice against pixel boxes.
|
|
ListFaces(ctx context.Context, img Image) ([]DetectedFace, error)
|
|
|
|
// FaceSwap transfers Source's identity into Target.
|
|
FaceSwap(ctx context.Context, req FaceSwapRequest, opts ...FaceSwapOption) (*Result, error)
|
|
}
|