共修改 66 个文件,包含 31729 行新增和 204 行删除

没有与搜索条件相匹配的文件

+2
查看文件
@@ -44,3 +44,5 @@ reference-binaries/
!releases/1.3.1/*.pkg
!releases/1.3.2/*.pkg
!releases/1.3.3/*.pkg
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.3.2</string>
<string>1.3.3</string>
<key>CFBundleVersion</key>
<string>17</string>
<string>18</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>PlugInKit</key>
@@ -72,7 +72,7 @@
<key>uuid</key>
<string>FB14B0E6-59B2-4836-916F-CD0C559E9241</string>
<key>version</key>
<string>1.3.2</string>
<string>1.3.3</string>
</dict>
<dict>
<key>className</key>
@@ -84,7 +84,7 @@
<key>uuid</key>
<string>E0E432C8-3D71-4ABA-B006-E7F519E06C87</string>
<key>version</key>
<string>1.3.2</string>
<string>1.3.3</string>
<key>protocolNames</key>
<array>
<string>FxOnScreenControl</string>
@@ -90,7 +90,7 @@ static NSArray *PGSanitizedPoints76(id value) {
return [points copy];
}
static NSArray *PGFullPoints76(VNFaceLandmarkRegion2D *region, CGRect box) {
if (region.pointCount != 76 || !region.normalizedPoints) return nil;
if (region.pointCount < 76 || !region.normalizedPoints) return nil;
const CGPoint *raw = region.normalizedPoints;
NSMutableArray *points = [NSMutableArray arrayWithCapacity:76];
for (NSUInteger i=0; i<76; i++) {
@@ -110,6 +110,7 @@ static NSDictionary *PGGeometry(VNFaceObservation *observation) {
@"mouth": PGRegion(lm.innerLips, box), @"contour": PGRegion(lm.faceContour, box)} mutableCopy];
NSArray *points = PGFullPoints76(lm.allPoints, box);
if (points) geometry[@"points76"] = points;
geometry[@"landmarkConfidence"] = @(lm ? lm.confidence : 0);
return [geometry copy];
}
static NSDictionary *PGSanitizedGeometry(id value) {
@@ -133,6 +134,7 @@ static NSDictionary *PGSanitizedGeometry(id value) {
// thin/chin/jaw/short fields without discarding otherwise usable old records.
NSArray *points76 = PGSanitizedPoints76(value[@"points76"]);
if (points76) result[@"points76"] = points76;
if ([value[@"landmarkConfidence"] isKindOfClass:NSNumber.class]) result[@"landmarkConfidence"] = value[@"landmarkConfidence"];
return [result copy];
}
static CGRect PGRegionBounds(NSDictionary *geometry, NSString *name, CGRect fallback) {
@@ -178,154 +180,270 @@ static CGPoint PGRemapPoint(NSArray *point, CGRect old, CGRect current) {
return CGPointMake(current.origin.x+([point[0] doubleValue]-old.origin.x)*current.size.width/old.size.width,
current.origin.y+([point[1] doubleValue]-old.origin.y)*current.size.height/old.size.height);
}
static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previous, double dt, BOOL stabilizePoints76) {
CGRect old=PGArrayRect(previous[@"box"]),box=PGArrayRect(current[@"box"]);
if(!PGValidRect(old) || !PGValidRect(box))return current;
// Independent stabilization: translate/scale old landmarks into the current
// detection box first, so normal head motion does not accumulate box lag.
double weight=.9*fmin(1,fmax(0,dt/.03));
CGPoint oldLE=PGCenter(PGRegionBounds(previous,@"leftEye",PGInFace(old,.16,.53,.25,.15)));
CGPoint oldRE=PGCenter(PGRegionBounds(previous,@"rightEye",PGInFace(old,.59,.53,.25,.15)));
CGPoint newLE=PGCenter(PGRegionBounds(current,@"leftEye",PGInFace(box,.16,.53,.25,.15)));
CGPoint newRE=PGCenter(PGRegionBounds(current,@"rightEye",PGInFace(box,.59,.53,.25,.15)));
double ox=oldRE.x-oldLE.x,oy=oldRE.y-oldLE.y,nx=newRE.x-newLE.x,ny=newRE.y-newLE.y;
double oldLength=hypot(ox,oy),newLength=hypot(nx,ny);
BOOL axisHold=oldLength>MAX(.003,.055*MIN(old.size.height,box.size.height)) &&
newLength>MAX(.003,.055*MIN(old.size.height,box.size.height)) &&
(ox*nx+oy*ny)/(oldLength*newLength)<-.18;
NSMutableDictionary *stable=[NSMutableDictionary dictionaryWithObject:current[@"box"] forKey:@"box"];
for(NSString *name in PGGeometryRegions()) {
NSArray *a=previous[name],*b=current[name];
if(!a.count || (!axisHold && a.count!=b.count)) {stable[name]=b;continue;}
NSMutableArray *points=[NSMutableArray arrayWithCapacity:a.count];
for(NSUInteger i=0;i<a.count;i++) {
CGPoint p=PGRemapPoint(a[i],old,box);
if(!axisHold) {p.x+=( [b[i][0] doubleValue]-p.x)*weight;p.y+=([b[i][1] doubleValue]-p.y)*weight;}
[points addObject:@[@(p.x),@(p.y)]];
// 2.4.0's default pin path accepts good points without a temporal blend.
// Geometry remains in our existing bottom-left coordinate convention; distances,
// dot products and the two-axis sign test are invariant to the reference's Y flip.
static CGPoint PGPoint(NSArray *p) { return CGPointMake([p[0] doubleValue], [p[1] doubleValue]); }
static CGPoint PGMeanPoints(NSArray *points, NSUInteger first, NSUInteger count) {
if (!count || first+count>points.count) return CGPointMake(NAN,NAN);
CGPoint sum=CGPointZero;
for(NSUInteger i=first;i<first+count;i++) {CGPoint p=PGPoint(points[i]);sum.x+=p.x;sum.y+=p.y;}
return CGPointMake(sum.x/count,sum.y/count);
}
static CGPoint PGLocalPoint(CGPoint p,CGRect box) {
return CGPointMake((p.x-box.origin.x)/box.size.width,(p.y-box.origin.y)/box.size.height);
}
static NSString *PGGeometryQualityReason(NSDictionary *g) {
CGRect box=PGArrayRect(g[@"box"]);NSArray *points=PGSanitizedPoints76(g[@"points76"]),*contour=g[@"contour"];
if(!PGValidRect(box)||points.count!=76)return @"incomplete76";
if(contour.count<=10)return @"contour";
double confidence=g[@"landmarkConfidence"]?[g[@"landmarkConfidence"] doubleValue]:1;
if(!isfinite(confidence)||confidence<.55)return @"confidence";
for(NSArray *point in points) {
CGPoint p=PGLocalPoint(PGPoint(point),box);
if(!isfinite(p.x)||!isfinite(p.y)||p.x<-.35||p.x>1.35||p.y<-.35||p.y>1.35)return @"localBounds";
}
stable[name]=[points copy];
CGPoint centers[5];NSArray *names=@[@"leftEye",@"rightEye",@"nose",@"lips"];
for(NSUInteger i=0;i<4;i++) {
NSArray *region=g[names[i]];if(!region.count)return @"missingRegion";
centers[i]=PGLocalPoint(PGMeanPoints(region,0,region.count),box);
if(!isfinite(centers[i].x)||!isfinite(centers[i].y))return @"regionBounds";
}
NSArray *current76 = PGSanitizedPoints76(current[@"points76"]);
NSArray *previous76 = PGSanitizedPoints76(previous[@"points76"]);
if (current76) {
if (stabilizePoints76 && previous76) {
NSMutableArray *points = [NSMutableArray arrayWithCapacity:76];
for (NSUInteger i=0; i<76; i++) {
CGPoint p = PGRemapPoint(previous76[i], old, box);
if (!axisHold) {
p.x += ([current76[i][0] doubleValue]-p.x)*weight;
p.y += ([current76[i][1] doubleValue]-p.y)*weight;
centers[4]=PGLocalPoint(PGMeanPoints(contour,contour.count/2-1,3),box);
CGPoint left=centers[0],right=centers[1],nose=centers[2],mouth=centers[3],chin=centers[4];
double dx=right.x-left.x,dy=right.y-left.y,length=hypot(dx,dy);
if(!isfinite(length)||length<.1)return @"eyeDistance";
dx/=length;dy/=length;
CGPoint eye=CGPointMake((left.x+right.x)*.5,(left.y+right.y)*.5);
double vx=-dy,vy=dx;
if(vx*(mouth.x-eye.x)+vy*(mouth.y-eye.y)<0){vx=-vx;vy=-vy;}
double mouthProjection=vx*(mouth.x-eye.x)+vy*(mouth.y-eye.y);
double noseProjection=vx*(nose.x-eye.x)+vy*(nose.y-eye.y);
double chinProjection=vx*(chin.x-eye.x)+vy*(chin.y-eye.y);
double lateral=dx*(chin.x-eye.x)+dy*(chin.y-eye.y);
if(!isfinite(chinProjection)||mouthProjection<.08||chinProjection-mouthProjection<.06||chinProjection<.2)return @"anatomy";
if(noseProjection<-.12||noseProjection>mouthProjection+.16||fabs(lateral)>length*.5+.3)return @"anatomy";
return @"";
}
static double PGBoxIoU(CGRect a,CGRect b) {
CGRect intersection=CGRectIntersection(a,b);double area=CGRectIsNull(intersection)?0:intersection.size.width*intersection.size.height;
double total=a.size.width*a.size.height+b.size.width*b.size.height-area;return total>0?area/total:0;
}
static BOOL PGCompatibleBoxes(CGRect a,CGRect b) {
if(!PGValidRect(a)||!PGValidRect(b))return NO;
CGPoint x=PGCenter(a),y=PGCenter(b);
return hypot(x.x-y.x,x.y-y.y)<=MAX(.04,.82*MAX(b.size.width,b.size.height))||PGBoxIoU(a,b)>=.08;
}
static CGPoint PGChinAxis(NSDictionary *g) {
NSArray *p=g[@"points76"];if(p.count!=76)return CGPointMake(NAN,NAN);
CGPoint a=PGPoint(p[66]),b=PGPoint(p[67]),c=PGPoint(p[68]),center=PGCenter(PGArrayRect(g[@"box"]));
return CGPointMake((a.x+1.65*b.x+c.x)/3.65-center.x,(a.y+1.65*b.y+c.y)/3.65-center.y);
}
static BOOL PGGeometryAxisFlip(NSDictionary *current,NSDictionary *previous) {
CGRect a=PGArrayRect(current[@"box"]),b=PGArrayRect(previous[@"box"]);
if(!PGCompatibleBoxes(a,b))return NO;
CGPoint x=PGChinAxis(current),y=PGChinAxis(previous);double lx=hypot(x.x,x.y),ly=hypot(y.x,y.y);
double threshold=MAX(.003,.055*MIN(a.size.height,b.size.height));
if(!isfinite(lx)||!isfinite(ly)||lx<.0008||ly<.0008||lx<threshold||ly<threshold)return NO;
if(x.y*y.y>=0||fabs(x.y)<.35*threshold||fabs(y.y)<.35*threshold)return NO;
double cosine=fmax(-1,fmin(1,(x.x*y.x+x.y*y.y)/(lx*ly)));
return cosine<=-.18||fabs(x.y-y.y)>=threshold;
}
static NSDictionary *PGRemappedGeometry(NSDictionary *previous,CGRect box) {
CGRect old=PGArrayRect(previous[@"box"]);if(!PGValidRect(old)||!PGValidRect(box))return previous;
NSMutableDictionary *result=[previous mutableCopy];result[@"box"]=PGRectArray(box);
NSMutableArray *names=[PGGeometryRegions() mutableCopy];[names addObject:@"points76"];
for(NSString *name in names) {
NSMutableArray *points=[NSMutableArray new];
for(NSArray *point in previous[name]) {CGPoint p=PGRemapPoint(point,old,box);[points addObject:@[@(p.x),@(p.y)]];}
if(points.count)result[name]=points;
}
[points addObject:@[@(p.x),@(p.y)]];
return result;
}
// Reference hold validation uses the 76 points plus the box center. Keep our
// persisted topology at 76 and reconstruct the center rather than changing archives.
static BOOL PGValidHeldGeometry(NSDictionary *g) {
CGRect box=PGArrayRect(g[@"box"]);NSArray *p=PGSanitizedPoints76(g[@"points76"]);
if(!PGValidRect(box)||p.count!=76)return NO;
CGRect expanded=CGRectInset(box,-.4*box.size.width,-.4*box.size.height);
double minX=CGRectGetMidX(box),maxX=minX,minY=CGRectGetMidY(box),maxY=minY;
double contourMinX=DBL_MAX,contourMaxX=-DBL_MAX,contourMinY=DBL_MAX,contourMaxY=-DBL_MAX,left=0,mid=0,right=0;
for(NSUInteger i=0;i<76;i++) {
CGPoint point=PGPoint(p[i]);
if(point.x<-.25||point.x>1.25||point.y<-.25||point.y>1.25)return NO;
minX=MIN(minX,point.x);maxX=MAX(maxX,point.x);minY=MIN(minY,point.y);maxY=MAX(maxY,point.y);
if(i<59)continue;
if(!CGRectContainsPoint(expanded,point))return NO;
contourMinX=MIN(contourMinX,point.x);contourMaxX=MAX(contourMaxX,point.x);
contourMinY=MIN(contourMinY,point.y);contourMaxY=MAX(contourMaxY,point.y);
if(i<66)left+=point.x;else if(i<=68)mid+=point.x;else right+=point.x;
}
stable[@"points76"] = [points copy];
} else {
// Reacquired detections and changes from/to incomplete topology do
// not borrow a held face's old projected pose or invent missing points.
stable[@"points76"] = current76;
double w=box.size.width,h=box.size.height,cw=contourMaxX-contourMinX,ch=contourMaxY-contourMinY;
left/=7;mid/=3;right/=7;
return maxX-minX>=.25*w&&maxY-minY>=.25*h&&cw>=.2*w&&ch>=.04*h&&cw<=1.85*w&&ch<=1.85*h&&
fabs(right-left)>=.12*w&&mid>=MIN(left,right)-.35*w&&mid<=MAX(left,right)+.35*w;
}
static NSString *PGGeometryTransitionReason(NSDictionary *current,NSDictionary *previous) {
CGRect box=PGArrayRect(current[@"box"]),old=PGArrayRect(previous[@"box"]);
if(!PGCompatibleBoxes(box,old))return @"";
if(PGGeometryAxisFlip(current,previous))return @"axisFlip";
NSArray *a=current[@"points76"],*b=previous[@"points76"];
if(a.count!=76||b.count!=76)return @"";
NSMutableArray *distances=[NSMutableArray new];double scale=MAX(box.size.width,box.size.height);
for(NSUInteger i=0;i<76;i++) {
CGPoint p=PGRemapPoint(b[i],old,box),q=PGPoint(a[i]);
[distances addObject:@(hypot(p.x-q.x,p.y-q.y)/scale)];
}
}
return [stable copy];
[distances sortUsingSelector:@selector(compare:)];
return [distances[38] doubleValue]>.30||[distances[67] doubleValue]>.58?@"pointJump":@"";
}
static CIImage *PGPreparedVisionImage(CIImage *image,CGFloat limit) {
CGRect e=image.extent;
CIImage *prepared=[image imageByApplyingTransform:CGAffineTransformMakeTranslation(-e.origin.x,-e.origin.y)];
CGFloat scale=MIN(1,limit/MAX(e.size.width,e.size.height));
if(scale<1)prepared=[prepared imageByApplyingTransform:CGAffineTransformMakeScale(scale,scale)];
return [prepared imageByCroppingToRect:CGRectMake(0,0,MAX(1,floor(e.size.width*scale)),MAX(1,floor(e.size.height*scale)))];
}
// This live selector is deliberately independent of _targets. Its past-only
// .35s reference window is a documented project policy, not a recovered exact
// vendor formula. Continuous held outputs advance time, so this is not a hold
// timeout. Locks protect state, not global render-order equivalence: a cold seek
// cannot reconstruct frames the host has never supplied.
@interface PGRealtimeFaceTracker : NSObject {
NSLock *_mutex;
NSMutableArray<NSDictionary *> *_entries;
NSUInteger _estimatedBytes;
NSDictionary *_liveReference;
VNSequenceRequestHandler *_sequence;
VNTrackObjectRequest *_objectRequest;
CGRect _objectBox;
CMTime _objectTime;
BOOL _objectValid;
}
- (NSDictionary *)resultForGeometry:(NSArray<NSDictionary *> *)raw settings:(PGBeautySettings)settings time:(CMTime)time extent:(CGRect)extent;
- (NSDictionary *)resultForGeometry:(NSArray<NSDictionary *> *)raw settings:(PGBeautySettings)settings time:(CMTime)time extent:(CGRect)extent image:(CIImage *)image;
- (CGRect)trackedBoxForImage:(CIImage *)image time:(CMTime)time seedBox:(CGRect)seed;
- (void)reset;
- (void)removeCachedResult:(NSDictionary *)result;
- (NSDictionary *)cacheStatistics;
@end
@implementation PGRealtimeFaceTracker
- (instancetype)init {if((self=[super init])){_mutex=[NSLock new];_entries=[NSMutableArray new];}return self;}
- (void)reset {[_mutex lock];[_entries removeAllObjects];_estimatedBytes=0;[_mutex unlock];}
- (instancetype)init {if((self=[super init])){_mutex=[NSLock new];_entries=[NSMutableArray new];_objectTime=kCMTimeInvalid;}return self;}
- (void)resetObjectTracker { _sequence=[VNSequenceRequestHandler new];_objectRequest=nil;_objectBox=CGRectNull;_objectTime=kCMTimeInvalid;_objectValid=NO; }
- (void)reset {[_mutex lock];[_entries removeAllObjects];_estimatedBytes=0;_liveReference=nil;[self resetObjectTracker];[_mutex unlock];}
- (void)removeCachedResult:(NSDictionary *)result {
[_mutex lock];
for(NSInteger i=(NSInteger)_entries.count-1;i>=0;i--) {
NSDictionary *entry=_entries[(NSUInteger)i];
if([entry[@"context"] isEqual:result[@"context"]]&&[entry[@"scope"] isEqual:result[@"scope"]]&&[entry[@"key"] isEqual:result[@"key"]]) {
_estimatedBytes-=[entry[@"bytes"] unsignedIntegerValue];[_entries removeObjectAtIndex:(NSUInteger)i];
}
}
[_mutex unlock];
}
- (NSDictionary *)cacheStatistics {[_mutex lock];NSDictionary *stats=@{@"frames":@(_entries.count),@"estimatedBytes":@(_estimatedBytes)};[_mutex unlock];return stats;}
- (NSDictionary *)resultForGeometry:(NSArray<NSDictionary *> *)input settings:(PGBeautySettings)s time:(CMTime)time extent:(CGRect)extent {
- (CGRect)trackedBoxForImage:(CIImage *)image time:(CMTime)time seedBox:(CGRect)seed {
if(!image||!PGTimeKey(time)){[self resetObjectTracker];return CGRectNull;}
BOOL hasSeed=PGValidRect(seed),hasBox=PGValidRect(_objectBox);
double dt=PGTimeKey(_objectTime)?CMTimeGetSeconds(CMTimeSubtract(time,_objectTime)):NAN;
BOOL backward=isfinite(dt)&&dt<0;
if(hasBox&&_objectValid&&isfinite(dt)&&fabs(dt)<=.0005)return _objectBox;
if(!_sequence)_sequence=[VNSequenceRequestHandler new];
if(!_objectRequest||!_objectValid||backward||(isfinite(dt)&&dt>.2)) {
if(!backward&&hasBox&&!_objectValid&&isfinite(dt)&&dt<=.45&&(!hasSeed||!PGCompatibleBoxes(seed,_objectBox)))return _objectBox;
if(!hasSeed){_objectValid=NO;_objectRequest=nil;_sequence=[VNSequenceRequestHandler new];return CGRectNull;}
_sequence=[VNSequenceRequestHandler new];
_objectRequest=[[VNTrackObjectRequest alloc] initWithDetectedObjectObservation:[VNDetectedObjectObservation observationWithBoundingBox:seed]];
NSUInteger revision=VNTrackObjectRequest.defaultRevision;
if(![VNTrackObjectRequest.supportedRevisions containsIndex:revision])revision=VNTrackObjectRequest.currentRevision;
if([VNTrackObjectRequest.supportedRevisions containsIndex:revision])_objectRequest.revision=revision;
_objectRequest.trackingLevel=VNRequestTrackingLevelAccurate;
_objectBox=seed;_objectTime=time;_objectValid=YES;return seed;
}
NSError *error=nil;
BOOL ok=[_sequence performRequests:@[_objectRequest] onCIImage:PGPreparedVisionImage(image,960) orientation:kCGImagePropertyOrientationUp error:&error];
VNDetectedObjectObservation *result=ok?_objectRequest.results.firstObject:nil;
CGRect box=result.boundingBox;
if(result&&result.confidence>=.22&&PGValidRect(box)) {
_objectRequest.inputObservation=result;_objectRequest.trackingLevel=VNRequestTrackingLevelAccurate;
_objectBox=box;_objectTime=time;_objectValid=YES;return box;
}
_objectRequest=nil;_sequence=[VNSequenceRequestHandler new];_objectValid=NO;
return hasBox?_objectBox:(hasSeed?seed:CGRectNull);
}
- (NSDictionary *)resultForGeometry:(NSArray<NSDictionary *> *)raw settings:(PGBeautySettings)s time:(CMTime)time extent:(CGRect)extent {
return [self resultForGeometry:raw settings:s time:time extent:extent image:nil];
}
- (NSDictionary *)resultForGeometry:(NSArray<NSDictionary *> *)input settings:(PGBeautySettings)s time:(CMTime)time extent:(CGRect)extent image:(CIImage *)image {
NSMutableArray *valid=[NSMutableArray new];
for(id item in input) {NSDictionary *g=PGSanitizedGeometry(item);if(g && valid.count<16)[valid addObject:g];}
NSArray *raw=[valid copy];
NSUInteger mode=s.fastMultiFace?2:(s.singleFace?1:0);
for(id item in input) {NSDictionary *g=PGSanitizedGeometry(item);if(g&&valid.count<16)[valid addObject:g];}
NSArray *raw=[valid copy];NSUInteger mode=s.fastMultiFace?2:(s.singleFace?1:0);
NSString *key=PGTimeKey(time);
NSString *context=[NSString stringWithFormat:@"%lu/%u/%lld/%.17g/%.17g/%.17g/%.17g/%.9g",(unsigned long)mode,s.targetIndex,
(long long)time.epoch,extent.origin.x,extent.origin.y,extent.size.width,extent.size.height,s.faceJumpGuardRange];
[_mutex lock];
NSDictionary *head=nil;
for(NSDictionary *record in _entries) if([record[@"context"] isEqual:context] &&
(!head || CMTimeCompare(PGRecordTime(record),PGRecordTime(head))>=0)) head=record;
NSString *scope=@"automatic";
BOOL contextChanged=![_liveReference[@"context"] isEqual:context];
NSDictionary *reference=contextChanged?nil:_liveReference;
if(contextChanged) {
// Fast-multi bypasses pin selection. Returning to a prior mode can use
// its accepted shape, but must never continue another mode's VN sequence.
for(NSDictionary *record in _entries)if([record[@"context"] isEqual:context]&&CMTimeCompare(PGRecordTime(record),time)<=0&&
(!reference||CMTimeCompare(PGRecordTime(record),PGRecordTime(reference))>0))reference=record;
}
double seedX=PGClamp(s.selectionX,0,1),seedY=PGClamp(s.selectionY,0,1);
if(mode==1) {
// A real point change (> .01) starts a new identity scope. Older render
// snapshots may use their own past scope, but never replace a newer head.
NSDictionary *seedRecord=head;
if(seedRecord && CMTimeCompare(time,PGRecordTime(seedRecord))<0) {
seedRecord=nil;
for(NSDictionary *record in _entries) if([record[@"context"] isEqual:context] && PGSeedDistance(record,s)<=.01 &&
CMTimeCompare(PGRecordTime(record),time)<=0 && (!seedRecord || CMTimeCompare(PGRecordTime(record),PGRecordTime(seedRecord))>=0))seedRecord=record;
}
if(seedRecord && PGSeedDistance(seedRecord,s)<=.01) {
scope=seedRecord[@"scope"];seedX=[seedRecord[@"seedX"] doubleValue];seedY=[seedRecord[@"seedY"] doubleValue];
} else scope=[NSString stringWithFormat:@"%@/%.9g/%.9g",key?:@"invalid",seedX,seedY];
}
NSDictionary *reference=nil;
if(key) for(NSDictionary *record in _entries) {
if(![record[@"context"] isEqual:context] || ![record[@"scope"] isEqual:scope])continue;
if([record[@"key"] isEqual:key] && [record[@"raw"] isEqual:raw]) {[_mutex unlock];return record;}
CMTime stored=PGRecordTime(record);
// Same-time changed detections recompute from an earlier frame, never
// feed an already smoothed result into another pass at that same time.
if(CMTimeCompare(stored,time)>=0)continue;
double dt=CMTimeGetSeconds(CMTimeSubtract(time,stored));
if(dt<=.35 && (!reference || CMTimeCompare(stored,PGRecordTime(reference))>0))reference=record;
}
NSArray *prior=reference[@"output"];
NSDictionary *previous=prior.count?prior.firstObject:nil;
NSString *scope=mode==1?[NSString stringWithFormat:@"%.9g/%.9g",seedX,seedY]:@"automatic";
if(mode==1&&reference&&PGSeedDistance(reference,s)<=.01){scope=reference[@"scope"];seedX=[reference[@"seedX"] doubleValue];seedY=[reference[@"seedY"] doubleValue];}
if(reference&&![reference[@"scope"] isEqual:scope])reference=nil;
// Exact frame reuse is independent of a second Vision result or render count.
// A new uncached rewind clears live geometry and the stateful sequence handler.
NSDictionary *cached=nil;
if(key)for(NSDictionary *record in _entries)if([record[@"context"] isEqual:context]&&[record[@"scope"] isEqual:scope]&&[record[@"key"] isEqual:key]){cached=record;break;}
BOOL backward=reference&&CMTimeCompare(time,PGRecordTime(reference))<0;
if(contextChanged||!reference||backward)[self resetObjectTracker];
if(cached){_liveReference=cached;[_mutex unlock];return cached;}
if(backward||!key)reference=nil;
NSDictionary *previous=reference[@"accepted"];
CGRect previousBox=PGArrayRect(previous[@"box"]);
CGRect tracked=mode==2?CGRectNull:[self trackedBoxForImage:image time:time seedBox:CGRectNull];
CGRect selectionBox=PGValidRect(tracked)?tracked:previousBox;
NSInteger index=-1;double best=-DBL_MAX;
if(mode!=2) for(NSUInteger i=0;i<raw.count;i++) {
if(mode!=2)for(NSUInteger i=0;i<raw.count;i++) {
CGRect box=PGArrayRect(raw[i][@"box"]);CGPoint center=PGCenter(box);double score;
if(PGValidRect(previousBox)) {
CGFloat factor=PGClamp(s.faceJumpGuardRange,1,4);
CGRect guard=CGRectIntersection(CGRectMake(0,0,1,1),CGRectInset(previousBox,-previousBox.size.width*(factor-1)*.5,-previousBox.size.height*(factor-1)*.5));
if(center.x<CGRectGetMinX(guard)||center.x>CGRectGetMaxX(guard)||center.y<CGRectGetMinY(guard)||center.y>CGRectGetMaxY(guard))continue;
CGPoint p=PGCenter(previousBox);score=-((center.x-p.x)*(center.x-p.x)+(center.y-p.y)*(center.y-p.y));
} else if(mode==1) score=-((center.x-seedX)*(center.x-seedX)+(center.y-seedY)*(center.y-seedY));
if(!CGRectContainsPoint(guard,center))continue;
}
if(PGValidRect(selectionBox)) {
CGPoint p=PGCenter(selectionBox);double distance=hypot(center.x-p.x,center.y-p.y);
score=8*PGBoxIoU(box,selectionBox)+3.2*(1-MIN(1,distance/.32))-.35*distance;
if(CGRectContainsPoint(box,p))score+=1.2;
}else if(mode==1)score=-hypot(center.x-seedX,center.y-seedY);
else {CGRect clipped=CGRectIntersection(box,CGRectMake(0,0,1,1));CGPoint p=PGCenter(clipped);
score=1.8*fmin(1,clipped.size.width*clipped.size.height)+1.1*(1-fmin(1,hypot(p.x-.5,p.y-.5)/.72));}
if(index<0 || score>best) {index=(NSInteger)i;best=score;}
if(index<0||score>best){index=(NSInteger)i;best=score;}
}
NSDictionary *current=index>=0?raw[(NSUInteger)index]:nil;
if(mode!=2&&!PGValidRect(tracked)&&current)tracked=[self trackedBoxForImage:image time:time seedBox:PGArrayRect(current[@"box"])];
NSString *reason=current?PGGeometryQualityReason(current):@"missingFace";
if(!reason.length&&previous)reason=PGGeometryTransitionReason(current,previous);
BOOL held=NO;NSArray *output=@[];NSDictionary *accepted=previous;
if(mode==2){output=raw;accepted=nil;reason=@"fastMulti";}
else if(!reason.length){output=@[current];accepted=current;}
else if(previous) {
CGRect box=PGValidRect(tracked)?tracked:(current?PGArrayRect(current[@"box"]):previousBox);
NSDictionary *mapped=PGRemappedGeometry(previous,box);
output=@[PGValidHeldGeometry(mapped)?mapped:previous];held=YES;
}
NSArray *output;BOOL held=NO;
if(mode==2) output=raw;
else if(index>=0) {
NSDictionary *current=raw[(NSUInteger)index];
double dt=reference?CMTimeGetSeconds(CMTimeSubtract(time,PGRecordTime(reference))):0;
output=@[previous?PGStableGeometry(current,previous,dt,![reference[@"held"] boolValue]):current];
} else {output=prior?:@[];held=output.count>0;}
NSMutableDictionary *record=[@{@"context":context,@"scope":scope,@"key":key?:@"invalid",@"value":@(time.value),@"scale":@(time.timescale),@"epoch":@(time.epoch),
@"seedX":@(seedX),@"seedY":@(seedY),@"raw":raw,@"output":output,@"index":@(index),@"held":@(held),@"referenceKey":reference[@"key"]?:@""} mutableCopy];
@"seedX":@(seedX),@"seedY":@(seedY),@"raw":raw,@"output":output,@"index":@(index),@"held":@(held),@"reason":reason,
@"trackedBox":PGValidRect(tracked)?PGRectArray(tracked):@[],@"referenceKey":reference[@"key"]?:@""} mutableCopy];
if(accepted)record[@"accepted"]=accepted;
// Keep the accepted time distinct from the output/held time for diagnostics.
record[@"acceptedKey"]=(!reason.length?key:reference[@"acceptedKey"])?:@"";
if(key) {
NSData *serialized=[NSPropertyListSerialization dataWithPropertyList:record format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
NSUInteger bytes=serialized.length+512;
record[@"bytes"]=@(bytes);
// Replace changed raw geometry at the same timestamp rather than letting
// callbacks grow history or make smoothing depend on redraw count.
for(NSInteger i=(NSInteger)_entries.count-1;i>=0;i--) {
NSDictionary *old=_entries[(NSUInteger)i];
if([old[@"context"] isEqual:context] && [old[@"scope"] isEqual:scope] && [old[@"key"] isEqual:key]) {
_estimatedBytes-=[old[@"bytes"] unsignedIntegerValue];[_entries removeObjectAtIndex:(NSUInteger)i];
}
}
if(bytes<=16*1024*1024) {[_entries addObject:[record copy]];_estimatedBytes+=bytes;}
while(_entries.count>1200 || _estimatedBytes>16*1024*1024) {
_estimatedBytes-=[_entries.firstObject[@"bytes"] unsignedIntegerValue];[_entries removeObjectAtIndex:0];
}
}
NSUInteger bytes=serialized.length+512;record[@"bytes"]=@(bytes);
if(bytes<=16*1024*1024){[_entries addObject:[record copy]];_estimatedBytes+=bytes;}
while(_entries.count>1200||_estimatedBytes>16*1024*1024){_estimatedBytes-=[_entries.firstObject[@"bytes"] unsignedIntegerValue];[_entries removeObjectAtIndex:0];}
_liveReference=[record copy];
}else{_liveReference=nil;[self resetObjectTracker];}
[_mutex unlock];return [record copy];
}
@end
@@ -338,6 +456,8 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
CIColorKernel *_subtract;
CIColorKernel *_box;
PGRealtimeFaceTracker *_realtime;
NSMutableArray<NSDictionary *> *_observationCache;
CIContext *_visionContext;
}
@end
@@ -348,9 +468,11 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
if (self) {
_lock = [[NSLock alloc] init];
_realtime = [PGRealtimeFaceTracker new];
_observationCache = [NSMutableArray new];
_visionContext = [CIContext contextWithOptions:@{kCIContextCacheIntermediates:@NO}];
_targets = [NSMutableArray arrayWithObjects:[NSMutableDictionary dictionary], [NSMutableDictionary dictionary], [NSMutableDictionary dictionary], nil];
// Thin/V/chin/jaw/short reconstruct recovered local coordinate math.
// Four other controls retain this project's analytic approximation.
// Narrow/cheek/small-head retain this project's analytic approximation.
// Recovered V -> jaw -> chin -> short order follows the reference. Other
// analytic body controls still precede V, so the full combined chain
// with narrow/cheek/small-head/eyes is not claimed reference-equivalent.
@@ -407,10 +529,10 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
[source appendString:@" float weight=mod(crossings,2.0)>0.5?1.0:clamp(1.0-nearest/length(shortAxis),0.0,1.0);"
" q=clamp(q-shortAxis*shortRatio*weight,vec2(0.0),vec2(1.0));"
" q.y=1.0-q.y; p=imageOrigin+q*imageSize; }"
" vec2 dl = (p-le)/er; float wl = 1.0-smoothstep(0.0,1.0,length(dl));"
" p -= (p-le)*bigEye*0.33*wl*wl;"
" vec2 dr = (p-re)/er; float wr = 1.0-smoothstep(0.0,1.0,length(dr));"
" p -= (p-re)*bigEye*0.33*wr*wr; return p; }"];
" vec2 dl=p-le; float lr=er.x;"
" if(lr>0.0001) { float wl=clamp(1.0-dot(dl,dl)/(lr*lr),0.0,1.0); p=le+dl*(1.0-bigEye*wl); }"
" vec2 dr=p-re; float rr=er.y;"
" if(rr>0.0001) { float wr=clamp(1.0-dot(dr,dr)/(rr*rr),0.0,1.0); p=re+dr*(1.0-bigEye*wr); } return p; }"];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_warp = [CIWarpKernel kernelWithString:source];
@@ -423,26 +545,53 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
}
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)settings error:(NSError **)error {
CGRect extent = image.extent;
CGFloat longest = MAX(extent.size.width, extent.size.height);
CGFloat scale = MIN(1.0, (settings.fastMultiFace ? 720.0 : 1280.0) / longest);
CIImage *prepared = [image imageByApplyingTransform:CGAffineTransformMakeTranslation(-extent.origin.x, -extent.origin.y)];
prepared = [prepared imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)];
VNDetectFaceLandmarksRequest *request = [[VNDetectFaceLandmarksRequest alloc] init];
request.revision = VNDetectFaceLandmarksRequestRevision3;
request.constellation = VNRequestFaceLandmarksConstellation76Points;
// An image request handler is intentionally local to one invocation. A shared
// sequence handler would make parallel / reverse render order observable.
VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCIImage:prepared options:@{}];
if (![handler performRequests:@[request] error:error]) return nil;
NSArray<VNFaceObservation *> *faces = request.results ?: @[];
return [faces sortedArrayUsingComparator:^NSComparisonResult(VNFaceObservation *a, VNFaceObservation *b) {
CGRect x = a.boundingBox, y = b.boundingBox;
if (CGRectGetMidX(x) != CGRectGetMidX(y)) return CGRectGetMidX(x) < CGRectGetMidX(y) ? NSOrderedAscending : NSOrderedDescending;
if (CGRectGetMidY(x) != CGRectGetMidY(y)) return CGRectGetMidY(x) < CGRectGetMidY(y) ? NSOrderedAscending : NSOrderedDescending;
return x.size.width > y.size.width ? NSOrderedAscending : NSOrderedDescending;
// 2.4.0 defaults to a 960-pixel long edge (its configurable downsample
// limit), not the former 1280-pixel path. The explicit fast mode keeps its
// lower-cost detector. Both handlers receive an upright, zero-origin image.
CIImage *prepared=PGPreparedVisionImage(image,settings.fastMultiFace?720:960);
VNDetectFaceLandmarksRequest *(^newRequest)(void)=^{
VNDetectFaceLandmarksRequest *r=[VNDetectFaceLandmarksRequest new];
r.revision=VNDetectFaceLandmarksRequestRevision3;r.constellation=VNRequestFaceLandmarksConstellation76Points;return r;
};
VNDetectFaceLandmarksRequest *request=newRequest();NSError *firstError=nil;
VNImageRequestHandler *handler=[[VNImageRequestHandler alloc] initWithCIImage:prepared orientation:kCGImagePropertyOrientationUp options:@{}];
BOOL ok=[handler performRequests:@[request] error:&firstError];
NSArray<VNFaceObservation *> *faces=ok?(request.results?:@[]):@[];
NSUInteger count=0;for(VNFaceObservation *face in faces)count=MAX(count,face.landmarks.allPoints.pointCount);
if(!ok||!faces.count||count<76) {
// The reference retries a materialized RGBA image, not the same lazy
// CIImage request. Retain a successful first result if retry finds none.
CGColorSpaceRef space=CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CGImageRef cg=[_visionContext createCGImage:prepared fromRect:prepared.extent format:kCIFormatRGBA8 colorSpace:space];
CGColorSpaceRelease(space);
if(cg) {
VNDetectFaceLandmarksRequest *retry=newRequest();NSError *retryError=nil;
VNImageRequestHandler *fallback=[[VNImageRequestHandler alloc] initWithCGImage:cg orientation:kCGImagePropertyOrientationUp options:@{}];
BOOL retried=[fallback performRequests:@[retry] error:&retryError];CGImageRelease(cg);
if(retried){ok=YES;firstError=nil;if(retry.results.count)faces=retry.results;}
else if(!ok)firstError=retryError?:firstError;
}
}
if(!ok){if(error)*error=firstError;return nil;}
return [faces sortedArrayUsingComparator:^NSComparisonResult(VNFaceObservation *a,VNFaceObservation *b) {
CGRect x=a.boundingBox,y=b.boundingBox;
if(CGRectGetMidX(x)!=CGRectGetMidX(y))return CGRectGetMidX(x)<CGRectGetMidX(y)?NSOrderedAscending:NSOrderedDescending;
if(CGRectGetMidY(x)!=CGRectGetMidY(y))return CGRectGetMidY(x)<CGRectGetMidY(y)?NSOrderedAscending:NSOrderedDescending;
return x.size.width>y.size.width?NSOrderedAscending:NSOrderedDescending;
}];
}
- (NSArray<VNFaceObservation *> *)cachedObservationsInImage:(CIImage *)image settings:(PGBeautySettings)settings time:(CMTime)time error:(NSError **)error {
NSString *key=PGTimeKey(time);CGRect e=image.extent;
NSString *context=[NSString stringWithFormat:@"%u/%.17g/%.17g/%.17g/%.17g",settings.fastMultiFace,e.origin.x,e.origin.y,e.size.width,e.size.height];
@synchronized(_observationCache) {
if(key)for(NSDictionary *entry in _observationCache)if([entry[@"key"] isEqual:key]&&[entry[@"context"] isEqual:context])return entry[@"faces"];
NSArray *faces=[self observationsInImage:image settings:settings error:error];
// A transient API error is retriable; a successful empty result is a
// real frame result and is cached, so redraws cannot toggle the effect.
if(key&&faces){[_observationCache addObject:@{@"key":key,@"context":context,@"faces":faces}];while(_observationCache.count>120)[_observationCache removeObjectAtIndex:0];}
return faces;
}
}
- (NSDictionary *)recordForTime:(CMTime)time target:(NSUInteger)target nearest:(BOOL)nearest {
NSString *key = PGTimeKey(time);
@@ -508,7 +657,7 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
if (error) *error = [NSError errorWithDomain:PGFaceErrorDomain code:1 userInfo:@{NSLocalizedDescriptionKey:@"Face analysis requires a finite image and valid frame time."}];
return NO;
}
NSArray *observations = [self observationsInImage:image settings:settings error:error];
NSArray *observations = [self cachedObservationsInImage:image settings:settings time:time error:error];
if (!observations) return NO;
NSUInteger target = MIN((NSUInteger)settings.targetIndex, 2);
NSDictionary *prior = [self recordForTime:time target:target nearest:YES];
@@ -539,7 +688,9 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
double height = 2*hypot(([points76[67][0] doubleValue]-midX)*extent.size.width,
([points76[67][1] doubleValue]-midY)*extent.size.height);
double gain = fmin(2.25,fmax(1,280/fmax(1,fmax(width,height))));
thinRatio = PGClamp(s.faceThin,0,1)*.025*gain;
// The 2.4.0 pin shader consumes this CPU coefficient directly;
// its thin field no longer applies the older 280-pixel size gain.
thinRatio = PGClamp(s.faceThin,0,1)*.025;
float chin = PGClamp(s.faceChin,-1,1);
chinRatio = chin*(chin>0 ? .04 : .02)*gain;
jawRatio = PGClamp(s.faceJaw,0,1)*.05*gain;
@@ -555,9 +706,18 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
CGRect le = PGRegionBounds(face, @"leftEye", PGInFace(box, 0.16, 0.53, 0.25, 0.15));
CGRect re = PGRegionBounds(face, @"rightEye", PGInFace(box, 0.59, 0.53, 0.25, 0.15));
CGPoint left = PGCenter(PGToPixels(le, extent)), right = PGCenter(PGToPixels(re, extent));
double leftRadius=MAX(2,pixels.size.width*.24),rightRadius=MAX(2,pixels.size.width*.24);
if(points76) {
CGPoint lp=PGPoint(points76[6]),rp=PGPoint(points76[13]);
left=CGPointMake(extent.origin.x+lp.x*extent.size.width,extent.origin.y+lp.y*extent.size.height);
right=CGPointMake(extent.origin.x+rp.x*extent.size.width,extent.origin.y+rp.y*extent.size.height);
CGPoint a=PGPoint(points76[0]),b=PGPoint(points76[1]),c=PGPoint(points76[7]),d=PGPoint(points76[8]);
leftRadius=hypot((a.x-b.x)*extent.size.width,(a.y-b.y)*extent.size.height);
rightRadius=hypot((c.x-d.x)*extent.size.width,(c.y-d.y)*extent.size.height);
}
NSMutableArray *arguments = [@[PGVector(PGCenter(pixels)), [CIVector vectorWithX:pixels.size.width Y:pixels.size.height],
PGVector(left), PGVector(right), [CIVector vectorWithX:MAX(2, pixels.size.width * 0.24) Y:MAX(2, pixels.size.height * 0.17)],
@(thinRatio), @(PGClamp(s.faceBigEye, 0, 1)), @(shortRatio),
PGVector(left), PGVector(right), [CIVector vectorWithX:leftRadius Y:rightRadius],
@(thinRatio), @(PGClamp(s.faceBigEye, 0, 1)*.15), @(shortRatio),
@(PGClamp(s.faceNarrow, -1, 1)), @(chinRatio), @(vRatio),
@(jawRatio), @(PGClamp(s.faceCheekbone, 0, 1)), @(PGClamp(s.faceSmallHead, 0, 1)), @(PGClamp(s.faceSmallHeadRange, 0, 1)),
[CIVector vectorWithX:extent.origin.x Y:extent.origin.y], [CIVector vectorWithX:extent.size.width Y:extent.size.height]] mutableCopy];
@@ -628,8 +788,14 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
for (NSDictionary *face in geometry) [restored addObject:[VNFaceObservation faceObservationWithRequestRevision:VNDetectFaceLandmarksRequestRevision3 boundingBox:PGArrayRect(face[@"box"]) roll:nil yaw:nil pitch:nil]];
observations = restored;
} else {
NSArray *detected = [self observationsInImage:image settings:settings error:error];
if (!detected) return nil;
NSArray *detected = [self cachedObservationsInImage:image settings:settings time:time error:error];
BOOL transientDetectionFailure=detected==nil;
if (!detected) {
if(settings.trackingEnabled)return nil;
// A failed live detection takes the same shape-hold path as a lost
// observation; it must not fail the host's entire render request.
detected=@[];if(error)*error=nil;
}
if(settings.trackingEnabled) {
// Preserve the explicit offline-analysis contract. Its stored
// identity fallback never reads or writes realtime temporal state.
@@ -645,7 +811,10 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
for(VNFaceObservation *observation in detected) if(PGValidRect(observation.boundingBox) && raw.count<16) {
[raw addObject:PGGeometry(observation)];[validObservations addObject:observation];
}
NSDictionary *live=[_realtime resultForGeometry:raw settings:settings time:time extent:extent];
NSDictionary *live=[_realtime resultForGeometry:raw settings:settings time:time extent:extent image:image];
// A successful empty detection is frozen; an API error is not. Its
// temporary held output must allow a same-time retry to acquire a face.
if(transientDetectionFailure)[_realtime removeCachedResult:live];
geometry=live[@"output"];
if(settings.singleFace && !settings.fastMultiFace) {
NSInteger index=[live[@"index"] integerValue];
@@ -665,6 +834,16 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
// skin/teeth/eyes support, which still covers the detected group.
appearance=raw;observations=validObservations;
}
if(([live[@"held"] boolValue] || transientDetectionFailure) && geometry.count) {
// Keep beauty masks on the same held primary shape as the warp,
// while retaining support for independently detected secondary faces.
NSMutableArray *supported=[appearance mutableCopy];
NSInteger index=[live[@"index"] integerValue];
if(!settings.singleFace && index>=0 && (NSUInteger)index<supported.count) supported[(NSUInteger)index]=geometry.firstObject;
else if(settings.singleFace || !supported.count) supported=[geometry mutableCopy];
else [supported addObjectsFromArray:geometry];
appearance=supported;
}
}
}
CIImage *skin = PGBlack(extent), *teeth = PGBlack(extent), *eyes = PGBlack(extent), *protection = PGBlack(extent), *overlay = PGTransparent(extent);
@@ -724,6 +903,7 @@ static NSDictionary *PGStableGeometry(NSDictionary *current, NSDictionary *previ
for (NSMutableDictionary *target in _targets) [target removeAllObjects];
[_lock unlock];
[_realtime reset];
@synchronized(_observationCache) { [_observationCache removeAllObjects]; }
}
- (NSData *)trackingDataForTarget:(NSUInteger)target {
@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.3.2</string>
<string>1.3.3</string>
<key>CFBundleVersion</key>
<string>17</string>
<string>18</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
+3 -1
查看文件
@@ -8,6 +8,8 @@
1.3.2(17)已统一工程、APP、插件类名和应用ID,正式安装为 `FCP-BeautyGirl.app` / `com.dayu.fcpbeautygirl`,嵌套插件ID为 `com.dayu.fcpbeautygirl.fxplug`。旧APP已迁移清理,滤镜UUID、参数编号和归档格式保持兼容。1901项自动回归及身份迁移检查通过;APP和含配套模板的PKG均已签名、公证、粘附票据和验证。更名后的旧工程重开仍由用户实测。
1.3.3(18)已对齐目标2.4.0的默认pin跟踪主链,并改用新版瘦脸/大眼局部公式。完整点、质量筛选、连续框、坏帧保持及同帧复用已补齐;同点合成场与目标Metal最大误差约0.005523像素。草帽素材469帧的离线采样不代表原生时间线的闪烁已经修复,仍需同段实测。详见[本轮分析与验证](docs/analysis/跟踪闪烁对齐分析.md)。2122项自动回归通过。APP和包含指定四项模板资源的PKG均已签名、公证Accepted并验证票据;本机正式安装仍为1.3.2,等待用户保存退出FCP/Motion后替换。安装与原生视觉验收状态以验证记录为准。
## 使用
正式安装路径为 `/Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app`。Motion 滤镜分类为「大鱼」,滤镜名「大鱼人像美化」。Final Cut Pro 在「效果 → 大鱼磨皮瘦脸 → 大鱼人像美化」中使用配套 Motion 效果,模板目录必须同时保留 `.moef` 与 `Drop Zone.tiff`。美化会随当前帧的人脸自动更新,不需要点击分析。界面以磨皮、美白、瘦脸三张卡片呈现,默认跟随主要人脸;多人模式对多脸变形。单项眼睛可暂时停用并保留数值,右侧箭头恢复默认;磨皮半径、饱和度、锐化和整体混合等收在「更多设置」。旧手动分析与调试参数仅保留隐藏兼容,实际输出统一使用实时自动跟随。
@@ -16,7 +18,7 @@
1.3.1(16)修复 V 脸影响眼区的问题,改为四组下颌轮廓到外唇的局部采样,并对齐新目标的 0.065 强度映射。1901 项自动检查通过,APP 与本机 PKG 工坊制作的安装包均已签名、公证和验证。正式 APP 已安装;新效果的原生宿主视觉仍由用户实测。原理与边界见 [V 脸下颌区域修复](docs/analysis/V脸下颌区域修复.md)。
当前 [1.3.2 正式下载](https://github.com/dayumeida/FCP-BeautyGirl/releases/tag/v1.3.2) 与 [大鱼文章](https://dayu.media/15593/) 是本版发布入口,安装包包含「大鱼磨皮瘦脸」分类的配套效果及资源,源码快照也已同步。
当前 [1.3.3 发行入口](https://github.com/dayumeida/FCP-BeautyGirl/releases/tag/v1.3.3) 与 [大鱼文章](https://dayu.media/15593/) 是本版发布入口,安装包包含「大鱼磨皮瘦脸」分类的配套效果及资源,源码快照也已同步。
## 开发和恢复
+17
查看文件
@@ -140,3 +140,20 @@ GitHub用户名现为 `dayumeida`,账号ID117798680保持一致。当前公开
发行入口为 `v1.3.2`;正式 APP 源码为 `767bba8ac11e9bf9268a1e02d1a06d3c120c46aa`。新机器按 `product.json` 的工程、scheme 与路径恢复,运行 `python3 scripts/deliver.py --wait-for-hosts --user-tests`;旧产品先验证 Bundle ID、签名团队和滤镜 UUID,再迁移并支持失败回滚。最终包为 `releases/1.3.2/FCP-BeautyGirl-1.3.2.pkg`,SHA-256 `20a88dacf68cc8812f5af86cfe94c1c0614414a68a019eaeac7ca9d078165ae1`,签名、公证 Accepted `4a831775-2317-4372-a4cc-102415657ea3`、票据和 Gatekeeper 均通过。正式 APP 已安装并清理旧身份。1901 项自动回归、8 项 APP 迁移、8 项最终 PKG 身份脚本迁移与模板隔离测试通过;未运行真实 installer,原生旧工程重开仍待用户检查。
源码包须从发行标签导出,排除 PKG 二进制并加入 SOURCE-SNAPSHOT.json。发布与独立克隆恢复回执位于 `docs/verification/v1.3.2/`,不包含用户人脸素材、签名私钥或公证凭据。历史标签和旧版本安装包保留,不覆盖。
1.3.2 发行标签完整 SHA 为 `8a70d38b7cd17e0ea530c5226f2be8c2df4308b3`。独立匿名 GitHub 克隆 271 个文件的字节/blob/执行位、strict/full fsck、Xcode 工程读取和恢复包签名/票据/Gatekeeper 均通过,无 LFS/子模块,临时克隆已清理。见 `docs/verification/v1.3.2/final-tag-recovery.json`。GitHub 与文章的安装包、源码快照已匿名下载核对哈希,源 ZIP 对应本标签;后续 main 补充回执,不移动发行标签或改变已签名产品源码。
## 1.3.2 后续跟踪闪烁分析
分析节点 `a0c5357efd87a63dbee6269d9335e6aa3ba78799` 保留新目标2.4.0静态证据、实际当前FCP草帽素材的469帧独立采样摘要与两个通用诊断入口;产品源码和安装版仍为v1.3.2,未宣布闪烁修复。独立匿名克隆284文件/fsck/合成诊断重建复现通过,无LFS或子模块。具体运行与边界见 `docs/analysis/跟踪闪烁对齐分析.md`,用户素材和完整真实关键点不在仓库中。
## 1.3.3 跟踪稳定链
签名产品源码为 `edf64ace6bc17f4db3b2dba71897997809c1029d`,工作分支 `codex/tracking-stability-240`,发行入口 `v1.3.3`。Release通用APP保持固定身份与路径,公证Accepted `379a432b-ccca-4676-9567-30dc49bb2400`;PKG由本机PKG工坊基础包加入已验证的身份迁移脚本后重新签名,公证Accepted `3b44edbb-612d-47b2-8b11-e79874b2c635`,4,905,560 bytes,SHA-256 `214c109af60bce72046c89ff12b364938b6c00c3a348fa3740dabef661649e0d`。包含用户指定“大鱼磨皮瘦脸”分类的moef、Drop Zone.tiff与两张锁定缩略图,payload字节及隔离安装/回滚核验通过;没有执行真实installer。
当前本机正式APP仍为1.3.2,FCP/Motion运行中,待用户保存退出后以 `python3 scripts/deliver.py --resume-prepared --wait-for-hosts --user-tests` 安装已公证精确构建并清理。新机器不存在本机暂存APP时,从标签恢复并按既有 `deliver.py --wait-for-hosts --user-tests` 重新编译签名公证,或安装发行PKG;不要复制本机prepared回执当作新机器已安装证明。
独立匿名GitHub克隆签名源码节点304文件字节/模式及strict/full fsck通过,重建跟踪87项和眼部126项;无需目标APP、用户视频或Apple私钥即可运行这两组合成检查。完整正常回归2122项;目标同点坐标场40例最大误差0.005523px。469帧离线草帽采样均通过质量筛选,不能代表原生时间线闪烁已消除。无LFS/子模块;SDK、签名私钥和公证凭据按本文既有说明独立恢复。最终标签恢复与发布回执补充至 `docs/verification/v1.3.3/`,历史标签不移动。
+3
查看文件
@@ -45,3 +45,6 @@ GitHub仓库从 `FAMEDaYu/FCP-BeatyGril` 更名为 `FAMEDaYu/FCP-BeautyGirl`,
## 1.3.2 产品身份统一
发行标签 `v1.3.2`,正式 APP 签名源码 `767bba8ac11e9bf9268a1e02d1a06d3c120c46aa`。新工程和 APP 名为 FCP-BeautyGirl,应用标识及安装路径见 product.json;旧身份仅保留经过验证的迁移入口。GitHub、大鱼 Release 和文章 15593 使用新名称安装包及源码快照,历史发行资料不改写。当前交付与发布核验保存在 verification/v1.3.2。
1.3.2 标签完整 SHA `8a70d38b7cd17e0ea530c5226f2be8c2df4308b3`,双远端引用回读一致。已匿名独立克隆核对 271 个文件、strict/full fsck、Xcode 工程和恢复包签名/票据/Gatekeeper。正式包 SHA256 `20a88dacf68cc8812f5af86cfe94c1c0614414a68a019eaeac7ca9d078165ae1`,源码 ZIP SHA256 `c75a516192375c45e446b3f0170615773ec2bd63b0430c3441a87282ba8b6834`;GitHub 与文章四项匿名下载均与本地字节一致。文章作者、封面和免费规则保留,正常及新请求前台版本/路径/SEO通过。大鱼 Release 只回读附件名称/大小,未绕过站点登录网关。
@@ -0,0 +1,321 @@
{
"decodedSize": [
1920,
1080
],
"decodeOrVisionErrors": 0,
"duplicateSourceFramePairs": 0,
"durationSeconds": 18.752066666666668,
"emptyDetectionFrames": 0,
"frames": 469,
"heldFrames": 0,
"landmarkConfidenceBelow055Frames": 0,
"largestContourChanges": [
{
"actualSeconds": 18.651966666666667,
"bboxCenterStep": 0.010619987358574903,
"bboxRelativeSizeStep": 0.05204244367729771,
"faceConfidence": 1,
"faceCount": 1,
"frame": 467,
"held": false,
"landmarkConfidence": 0.817047119140625,
"outputAllShapeStep": 0.061719217959260164,
"outputContourShapeStep": 0.06457580929340678,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.061719217959260164,
"rawContourShapeStep": 0.06457580929340678,
"rawEyeShapeStep": 0.057909273674708016,
"requestedSeconds": 18.68,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 6.3063,
"bboxCenterStep": 0.009772060063233024,
"bboxRelativeSizeStep": 0.013879898508151411,
"faceConfidence": 1,
"faceCount": 1,
"frame": 158,
"held": false,
"landmarkConfidence": 0.842681884765625,
"outputAllShapeStep": 0.0594987598669281,
"outputContourShapeStep": 0.059070265481990666,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.0594987598669281,
"rawContourShapeStep": 0.059070265481990666,
"rawEyeShapeStep": 0.059058695174623185,
"requestedSeconds": 6.32,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 0.9342666666666667,
"bboxCenterStep": 0.005945873831186665,
"bboxRelativeSizeStep": 0.04857391780763898,
"faceConfidence": 1,
"faceCount": 1,
"frame": 24,
"held": false,
"landmarkConfidence": 0.860382080078125,
"outputAllShapeStep": 0.03990833410408158,
"outputContourShapeStep": 0.05205992923951523,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.03990833410408158,
"rawContourShapeStep": 0.05205992923951523,
"rawEyeShapeStep": 0.03331563844059785,
"requestedSeconds": 0.96,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 1.0343666666666667,
"bboxCenterStep": 0.0035810993172984357,
"bboxRelativeSizeStep": 0.07745302528191012,
"faceConfidence": 1,
"faceCount": 1,
"frame": 26,
"held": false,
"landmarkConfidence": 0.86907958984375,
"outputAllShapeStep": 0.04015940118859808,
"outputContourShapeStep": 0.05153229113886389,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.04015940118859808,
"rawContourShapeStep": 0.05153229113886389,
"rawEyeShapeStep": 0.04106869710572574,
"requestedSeconds": 1.04,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 6.8735333333333335,
"bboxCenterStep": 0.011081990813070965,
"bboxRelativeSizeStep": 0.041020958444265565,
"faceConfidence": 1,
"faceCount": 1,
"frame": 172,
"held": false,
"landmarkConfidence": 0.861907958984375,
"outputAllShapeStep": 0.04919706037542044,
"outputContourShapeStep": 0.047788293575915936,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.04919706037542044,
"rawContourShapeStep": 0.047788293575915936,
"rawEyeShapeStep": 0.04811240328922642,
"requestedSeconds": 6.88,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 5.538866666666666,
"bboxCenterStep": 0.009174106285954426,
"bboxRelativeSizeStep": 0.0696669648408832,
"faceConfidence": 1,
"faceCount": 1,
"frame": 139,
"held": false,
"landmarkConfidence": 0.86724853515625,
"outputAllShapeStep": 0.04712320285757311,
"outputContourShapeStep": 0.047267593615817144,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.04712320285757311,
"rawContourShapeStep": 0.047267593615817144,
"rawEyeShapeStep": 0.04635983371628053,
"requestedSeconds": 5.56,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 5.5055,
"bboxCenterStep": 0.009205666419765468,
"bboxRelativeSizeStep": 0.04702815798474558,
"faceConfidence": 1,
"faceCount": 1,
"frame": 138,
"held": false,
"landmarkConfidence": 0.8653411865234375,
"outputAllShapeStep": 0.04678117299069693,
"outputContourShapeStep": 0.0464975192908153,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.04678117299069693,
"rawContourShapeStep": 0.0464975192908153,
"rawEyeShapeStep": 0.044731979492293225,
"requestedSeconds": 5.52,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 6.2729333333333335,
"bboxCenterStep": 0.010523844047228498,
"bboxRelativeSizeStep": 0.04067544887614405,
"faceConfidence": 1,
"faceCount": 1,
"frame": 157,
"held": false,
"landmarkConfidence": 0.835052490234375,
"outputAllShapeStep": 0.05580485366082702,
"outputContourShapeStep": 0.045154734035835926,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.05580485366082702,
"rawContourShapeStep": 0.045154734035835926,
"rawEyeShapeStep": 0.06010488116983429,
"requestedSeconds": 6.28,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 18.7187,
"bboxCenterStep": 0.00847507465061345,
"bboxRelativeSizeStep": 0.001839415731091032,
"faceConfidence": 1,
"faceCount": 1,
"frame": 468,
"held": false,
"landmarkConfidence": 0.81689453125,
"outputAllShapeStep": 0.039795819667405995,
"outputContourShapeStep": 0.043322334703151985,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.039795819667405995,
"rawContourShapeStep": 0.043322334703151985,
"rawEyeShapeStep": 0.033949487128638954,
"requestedSeconds": 18.72,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 6.973633333333333,
"bboxCenterStep": 0.004802853824391058,
"bboxRelativeSizeStep": 0.0033162879732389072,
"faceConfidence": 1,
"faceCount": 1,
"frame": 175,
"held": false,
"landmarkConfidence": 0.850616455078125,
"outputAllShapeStep": 0.029986598311175987,
"outputContourShapeStep": 0.042164697332718326,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.029986598311175987,
"rawContourShapeStep": 0.042164697332718326,
"rawEyeShapeStep": 0.026900138652230798,
"requestedSeconds": 7,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 6.740066666666666,
"bboxCenterStep": 0.008964342160362288,
"bboxRelativeSizeStep": 0.018475143321662002,
"faceConfidence": 1,
"faceCount": 1,
"frame": 169,
"held": false,
"landmarkConfidence": 0.857940673828125,
"outputAllShapeStep": 0.04248999607570733,
"outputContourShapeStep": 0.04199222726351612,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.04248999607570733,
"rawContourShapeStep": 0.04199222726351612,
"rawEyeShapeStep": 0.0441200591307651,
"requestedSeconds": 6.76,
"resultReason": "",
"trackedBoxAvailable": 1
},
{
"actualSeconds": 6.172833333333333,
"bboxCenterStep": 0.006930516638962855,
"bboxRelativeSizeStep": 0.031192722054552036,
"faceConfidence": 1,
"faceCount": 1,
"frame": 155,
"held": false,
"landmarkConfidence": 0.8632049560546875,
"outputAllShapeStep": 0.03366104158267517,
"outputContourShapeStep": 0.04180684654646812,
"points76Count": 76,
"qualityReason": "",
"rawAllShapeStep": 0.03366104158267517,
"rawContourShapeStep": 0.04180684654646812,
"rawEyeShapeStep": 0.029757180729629833,
"requestedSeconds": 6.2,
"resultReason": "",
"trackedBoxAvailable": 1
}
],
"multipleFaceFrames": 0,
"nominalSourceFPS": 29.970029830932617,
"outputWithout76Frames": 0,
"partial76Frames": 0,
"samplingFPS": 25,
"scope": "Independent offline decoding and the current production detector/tracker; not FCP render logs or target execution. Shape steps remove previous bbox translation/scale, but still include actual pose/expression changes.",
"sourceBasename": "girl-in-sun-hat-smiling-in-rural-field-2026-10-03-13-54-14-utc.mov",
"statistics": {
"bboxCenterStep": {
"max": 0.011081990813070965,
"median": 0.002747360384438056,
"min": 0.00014653804115170532,
"n": 468,
"p95": 0.006589111497080662
},
"bboxRelativeSizeStep": {
"max": 0.07745302528191012,
"median": 0.009236047113881152,
"min": 9.126808167081446e-05,
"n": 468,
"p95": 0.03867668227601517
},
"landmarkConfidence": {
"max": 0.9249267578125,
"median": 0.867706298828125,
"min": 0.81689453125,
"n": 469,
"p95": 0.9015045166015625
},
"outputAllShapeStep": {
"max": 0.061719217959260164,
"median": 0.011493205285858545,
"min": 0.003266396399148707,
"n": 468,
"p95": 0.03265326908712946
},
"outputContourShapeStep": {
"max": 0.06457580929340678,
"median": 0.013767751423829355,
"min": 0.0022585721609373914,
"n": 468,
"p95": 0.03343570504134135
},
"rawAllShapeStep": {
"max": 0.061719217959260164,
"median": 0.011493205285858545,
"min": 0.003266396399148707,
"n": 468,
"p95": 0.03265326908712946
},
"rawContourShapeStep": {
"max": 0.06457580929340678,
"median": 0.013767751423829355,
"min": 0.0022585721609373914,
"n": 468,
"p95": 0.03343570504134135
},
"rawEyeShapeStep": {
"max": 0.06010488116983429,
"median": 0.010554830995294154,
"min": 0.0018890160668284224,
"n": 468,
"p95": 0.03347895433098691
}
},
"timelineOffsetSeconds": 18.72
}
@@ -0,0 +1,318 @@
{
"decodedSize": [
1920,
1080
],
"decodeOrVisionErrors": 0,
"duplicateSourceFramePairs": 0,
"durationSeconds": 18.752066666666668,
"emptyDetectionFrames": 0,
"frames": 469,
"heldFrames": 0,
"landmarkConfidenceBelow055Frames": 0,
"largestContourChanges": [
{
"actualSeconds": 5.7057,
"bboxCenterStep": 0.010612391844050412,
"bboxRelativeSizeStep": 0.07225176667426436,
"faceConfidence": 1,
"faceCount": 1,
"frame": 143,
"held": false,
"landmarkConfidence": 0.85870361328125,
"outputAllShapeStep": 0.05320087993361461,
"outputContourShapeStep": 0.0524041977802718,
"points76Count": 76,
"rawAllShapeStep": 0.06465122287331607,
"rawContourShapeStep": 0.06330400492305996,
"rawEyeShapeStep": 0.07106297705037405,
"requestedSeconds": 5.72
},
{
"actualSeconds": 5.538866666666666,
"bboxCenterStep": 0.011085862886644501,
"bboxRelativeSizeStep": 0.06308677201537605,
"faceConfidence": 1,
"faceCount": 1,
"frame": 139,
"held": false,
"landmarkConfidence": 0.86663818359375,
"outputAllShapeStep": 0.05242709853363116,
"outputContourShapeStep": 0.046564814499130476,
"points76Count": 76,
"rawAllShapeStep": 0.06161259278352428,
"rawContourShapeStep": 0.05478966859045086,
"rawEyeShapeStep": 0.06493439304604706,
"requestedSeconds": 5.56
},
{
"actualSeconds": 5.6723333333333334,
"bboxCenterStep": 0.009899754844891629,
"bboxRelativeSizeStep": 0.07041863176014063,
"faceConfidence": 1,
"faceCount": 1,
"frame": 142,
"held": false,
"landmarkConfidence": 0.86846923828125,
"outputAllShapeStep": 0.0504812129727618,
"outputContourShapeStep": 0.04661140849311553,
"points76Count": 76,
"rawAllShapeStep": 0.05657646866920014,
"rawContourShapeStep": 0.05209665662633769,
"rawEyeShapeStep": 0.0633232078503364,
"requestedSeconds": 5.68
},
{
"actualSeconds": 18.7187,
"bboxCenterStep": 0.012083635671277248,
"bboxRelativeSizeStep": 0.045801810508214524,
"faceConfidence": 1,
"faceCount": 1,
"frame": 468,
"held": false,
"landmarkConfidence": 0.813995361328125,
"outputAllShapeStep": 0.034052174045594144,
"outputContourShapeStep": 0.043147628575738506,
"points76Count": 76,
"rawAllShapeStep": 0.04111983043006513,
"rawContourShapeStep": 0.05091513919680358,
"rawEyeShapeStep": 0.029191044989766733,
"requestedSeconds": 18.72
},
{
"actualSeconds": 5.739066666666667,
"bboxCenterStep": 0.0096878552974905,
"bboxRelativeSizeStep": 0.050414751769179866,
"faceConfidence": 1,
"faceCount": 1,
"frame": 144,
"held": false,
"landmarkConfidence": 0.86083984375,
"outputAllShapeStep": 0.036875562148072825,
"outputContourShapeStep": 0.036457754092126635,
"points76Count": 76,
"rawAllShapeStep": 0.0466359793805061,
"rawContourShapeStep": 0.04577259881583104,
"rawEyeShapeStep": 0.05000862947922753,
"requestedSeconds": 5.76
},
{
"actualSeconds": 0.8675333333333334,
"bboxCenterStep": 0.003874961092189615,
"bboxRelativeSizeStep": 0.07178938570952054,
"faceConfidence": 1,
"faceCount": 1,
"frame": 22,
"held": false,
"landmarkConfidence": 0.8583984375,
"outputAllShapeStep": 0.031532331820470494,
"outputContourShapeStep": 0.0381074357036598,
"points76Count": 76,
"rawAllShapeStep": 0.03707185765347939,
"rawContourShapeStep": 0.04568537667821508,
"rawEyeShapeStep": 0.034575716035532285,
"requestedSeconds": 0.88
},
{
"actualSeconds": 18.651966666666667,
"bboxCenterStep": 0.007272824106484237,
"bboxRelativeSizeStep": 0.03719342311108376,
"faceConfidence": 1,
"faceCount": 1,
"frame": 467,
"held": false,
"landmarkConfidence": 0.8199462890625,
"outputAllShapeStep": 0.03952595504917539,
"outputContourShapeStep": 0.0403761896420276,
"points76Count": 76,
"rawAllShapeStep": 0.04399124673462057,
"rawContourShapeStep": 0.0450952404425199,
"rawEyeShapeStep": 0.04542945579876513,
"requestedSeconds": 18.68
},
{
"actualSeconds": 0.9009,
"bboxCenterStep": 0.0017162987254983866,
"bboxRelativeSizeStep": 0.014351427482534596,
"faceConfidence": 1,
"faceCount": 1,
"frame": 23,
"held": false,
"landmarkConfidence": 0.85809326171875,
"outputAllShapeStep": 0.026103724683818753,
"outputContourShapeStep": 0.04109990769809984,
"points76Count": 76,
"rawAllShapeStep": 0.028649663225073875,
"rawContourShapeStep": 0.04465304774876588,
"rawEyeShapeStep": 0.02979010461963986,
"requestedSeconds": 0.92
},
{
"actualSeconds": 0.7007,
"bboxCenterStep": 0.007675079557145801,
"bboxRelativeSizeStep": 0.059965605197173644,
"faceConfidence": 1,
"faceCount": 1,
"frame": 18,
"held": false,
"landmarkConfidence": 0.877166748046875,
"outputAllShapeStep": 0.038188462475739905,
"outputContourShapeStep": 0.040855905682333056,
"points76Count": 76,
"rawAllShapeStep": 0.04142583196750897,
"rawContourShapeStep": 0.04413941822893812,
"rawEyeShapeStep": 0.04424396634844577,
"requestedSeconds": 0.72
},
{
"actualSeconds": 0.8341666666666666,
"bboxCenterStep": 0.0019315279136915449,
"bboxRelativeSizeStep": 0.03041757827231506,
"faceConfidence": 1,
"faceCount": 1,
"frame": 21,
"held": false,
"landmarkConfidence": 0.861602783203125,
"outputAllShapeStep": 0.023950562918532762,
"outputContourShapeStep": 0.0391113963086702,
"points76Count": 76,
"rawAllShapeStep": 0.026769304364061126,
"rawContourShapeStep": 0.043656761461571575,
"rawEyeShapeStep": 0.021617825564847367,
"requestedSeconds": 0.84
},
{
"actualSeconds": 5.9726333333333335,
"bboxCenterStep": 0.0076538482284609945,
"bboxRelativeSizeStep": 0.009531954788836172,
"faceConfidence": 1,
"faceCount": 1,
"frame": 150,
"held": false,
"landmarkConfidence": 0.85198974609375,
"outputAllShapeStep": 0.03696981884576201,
"outputContourShapeStep": 0.038389239030267384,
"points76Count": 76,
"rawAllShapeStep": 0.04143716031529852,
"rawContourShapeStep": 0.042871635488955444,
"rawEyeShapeStep": 0.0428226737500748,
"requestedSeconds": 6
},
{
"actualSeconds": 16.649966666666668,
"bboxCenterStep": 0.006605729070676864,
"bboxRelativeSizeStep": 0.025478189396700213,
"faceConfidence": 1,
"faceCount": 1,
"frame": 417,
"held": false,
"landmarkConfidence": 0.8674774169921875,
"outputAllShapeStep": 0.03284710686769975,
"outputContourShapeStep": 0.036339332152690744,
"points76Count": 76,
"rawAllShapeStep": 0.037116421558418974,
"rawContourShapeStep": 0.04240155312389841,
"rawEyeShapeStep": 0.03197382659897345,
"requestedSeconds": 16.68
}
],
"multipleFaceFrames": 0,
"nominalSourceFPS": 29.970029830932617,
"outputWithout76Frames": 0,
"partial76Frames": 0,
"samplingFPS": 25,
"scope": "Independent offline decoding and the current production detector/tracker; not FCP render logs or target execution. Shape steps remove previous bbox translation/scale, but still include actual pose/expression changes.",
"sourceBasename": "girl-in-sun-hat-smiling-in-rural-field-2026-10-03-13-54-14-utc.mov",
"statistics": {
"bboxCenterStep": {
"max": 0.012083635671277248,
"median": 0.0025497870742485452,
"min": 0.00020005997144872253,
"n": 468,
"p95": 0.006821726779186577
},
"bboxRelativeSizeStep": {
"max": 0.07225176667426436,
"median": 0.009157247402053037,
"min": 6.255832853296894e-06,
"n": 468,
"p95": 0.03431959948374386
},
"landmarkConfidence": {
"max": 0.9240875244140625,
"median": 0.867706298828125,
"min": 0.813995361328125,
"n": 469,
"p95": 0.902191162109375
},
"outputAllShapeStep": {
"max": 0.05320087993361461,
"median": 0.009735492145012412,
"min": 0.0029755122029975372,
"n": 468,
"p95": 0.026782532902396017
},
"outputContourShapeStep": {
"max": 0.0524041977802718,
"median": 0.011810624254892923,
"min": 0.002450992809745613,
"n": 468,
"p95": 0.028720841841711312
},
"rawAllShapeStep": {
"max": 0.06465122287331607,
"median": 0.01108023509780489,
"min": 0.0031002818411607846,
"n": 468,
"p95": 0.030464142596339925
},
"rawContourShapeStep": {
"max": 0.06330400492305996,
"median": 0.013550650249078297,
"min": 0.002692092571665042,
"n": 468,
"p95": 0.03357642027607019
},
"rawEyeShapeStep": {
"max": 0.07106297705037405,
"median": 0.010302814344493833,
"min": 0.0013460890263982167,
"n": 468,
"p95": 0.02979010461963986
}
},
"timelineOffsetSeconds": 18.72,
"nativeContext": {
"app": "Final Cut Pro",
"project": "大鱼人像美化验收-20261021",
"format": "1080p25",
"selectedClipStartTimecode": "00:00:18:18",
"selectedClipEndTimecode": "00:00:37:11",
"timelinePlayheadAX": "00:00:24:11",
"viewerLCD": "00:00:35:22",
"faceThin": 1,
"faceBigEye": 0.9035486629353234,
"fastMultiFace": false,
"sourceResolvedBy": "Selected timeline clip > File > Reveal in Finder",
"timelineMediaMapping": "Offset18.72 is inferred from clip placement; source in-point/retiming not exported, so source5.72 -> timeline24:11 is approximate. Viewer LCD differs from AX playhead; displayed frame not asserted."
},
"interpretation": {
"partialTopologySwitchObserved": false,
"candidate": "Fully detected geometry changes retained by the current box-remapping and 90% current-frame point blend; eye effect also active",
"notProven": [
"Observed geometric change includes actual head motion, rotation and expression; not all measured change is detector noise",
"No target output frames or live target internal geometry captured",
"Native FCP arrival order and detection/color/scale can differ from the independent decode",
"Big-eye contribution not measured independently on the native timeline"
]
},
"referenceProcessIdentity": {
"observedAlreadyRunning": true,
"outerApp": "/Library/Plug-Ins/FxPlug/MSE18V2.app",
"version": "2.4.0",
"pluginSha256": "577303e0de72e07feb576c882e82382b3f056b023a7e987d990ffb37f6b16109",
"matchesRequestedDownloadsBinary": true,
"analysisLaunchedTarget": false,
"internalTargetGeometryCaptured": false
}
}
@@ -0,0 +1,36 @@
{
"scope": "Current source review, isolated authored diagnostics, static REA of requested2.4.0, and independent25fps scan of the user-selected current clip; no native dual-plugin comparison",
"sourceBaseline": "b58fec1a86db7a2e474e1cc65a5911447b5906b9",
"installedVersion": "1.3.2 (17)",
"productSourceUnchanged": true,
"selectedTarget": {
"path": "/Users/dayu/Downloads/MSE18V2.app",
"version": "2.4.0",
"build": "20260724150000",
"universalPluginSha256": "577303e0de72e07feb576c882e82382b3f056b023a7e987d990ffb37f6b16109",
"arm64Sha256": "afc71d0d21fd3c1ca95ca745ddafcd729e36ad95f17d8638009f2e45d9b7e8df"
},
"comparisonCaution": "Existing auto-follow-report.json describes the older /Applications target; its temporal weights/history must not be asserted as 2.4.0 default behavior",
"evidenceRecordsRetained": 17,
"checks": {
"FCP-BeautyGirl/FCP-BeautyGirl/Plugin/PGFaceProcessor.m": "cca826d5602b45b2c79ea3c3c78be7c78ad1defe7b062ed8ec54c311d53ac395",
"FCP-BeautyGirl/FCP-BeautyGirl/Plugin/FCPBeautyGirlPlugIn.m": "9d3d30d1f7f6467c634e8d6e832d2330618b9ff240de37f57de9bc48e2792de8",
"scripts/diagnose-tracking.m": "bc758bf5ec4825006930be8997889404f63c28da1fe06ad8872b0901b2795d2c",
"docs/analysis/tracking-flicker-20261022/synthetic-results.json": "5fdbad4887dd78658c06f5c6d8e70c9e53fd7b54e11a9cc31ee1958631cbc1a8",
"docs/analysis/tracking-flicker-20261022/rea-evidence.json": "4b367759388c8e061e138623184ae8a6875dbab22659eefd4e474b141b8868fa",
"scripts/diagnose-tracking.sh": "c1993ca91cee596a21db1e4f86ba224be85c37495c783f9c3b21872aff210765",
"scripts/inspect-tracking-sequence.m": "0b94fc92669acf4d36861ccb25077b767189b8a72647e0d304627dbda5d052eb",
"docs/analysis/tracking-flicker-20261022/current-clip-summary.json": "043ba688c606fb9e56449fd02108b34789fd41c4d9a2552cd07f4d3bf78a00f0"
},
"limits": [
"Static reachable/default-configuration branches do not prove runtime branch execution",
"Same-frame raw cache, hold and out-of-order behavior were isolated using our actual current implementation",
"No APP rebuild, install, package or article publication for this analysis",
"User source decoded only locally; no source frames or complete real landmark arrays uploaded",
"User current FCP clip confirmed, all469 offline samples have full76; earlier missing-point synthetic case is not established as this clip cause",
"Only scalar clip summary retained; full scalar per-frame diagnostics remain in ignored build",
"Approximate timeline offset is not a substitute for verified source in-point/retiming",
"Reference was already running; only process executable identity read. Native UI read/reveal/raise only, no effect/timeline edits; no target launch, debugger or geometry trace."
],
"currentClipSummary": "current-clip-summary.json"
}
文件差异过大,未显示。 加载差异
@@ -0,0 +1,14 @@
{
"status": "verified",
"repository": "https://github.com/dayumeida/FCP-BeautyGirl.git",
"anonymousClone": true,
"commit": "a0c5357efd87a63dbee6269d9335e6aa3ba78799",
"trackedFilesByteBlobAndModeCompared": 284,
"strictFullFsck": true,
"syntheticDiagnosticRebuiltAndReproduced": true,
"productSourceUnchangedFromRelease": "v1.3.2",
"lfs": false,
"submodules": false,
"temporaryCloneRemoved": true,
"mediaBoundary": "User source clip not included; clip scalar summary and static/synthetic evidence retained"
}
@@ -0,0 +1,195 @@
# 2.4.0 region / pin branch review
2026-10-23. Independent read-only review of the existing AIR and local arm64
slice. No shared REA operation, target execution, product edit, Git operation,
or user image/landmark extraction was performed.
## The default-path correction
**The inspected main encoders and both direct fallback callers select mode 0
(pin), not region. Fragment buffer 8 is sampleFlipY, not faceThinMode.**
AIR file: `build/vface-analysis/new-face-thin.air.txt`, SHA-256
`1d5bd8d5a3917afbb3b4fe1c40b44480760614102685ac826e84ad2e30a450f9`.
Arm64 slice: `build/tracking-audit/MSE18V2-arm64`, SHA-256
`afc71d0d21fd3c1ca95ca745ddafcd729e36ad95f17d8638009f2e45d9b7e8df`.
AIR metadata lines 4126–4135 give the actual Metal binding indices:
| Fragment buffer | Name | AIR parameter |
| --- | --- | --- |
| 0 | facePoints | `%3` |
| 1 | faceCount | `%4` |
| 2 | faceThinStrength | `%5` |
| 4 | faceThinMode | `%6` |
| 7 | faceShapeParams | `%7` |
| 8 | sampleFlipY | `%8` |
| 9 | warpFlipY | `%9` |
| 10 | sourceSampleRect | `%10` |
This is not merely a name inference: lines 447–465 load `%6`, test it `>0`,
and choose region. Lines 569–583 load `%8` and perform the final sample Y flip.
The independent native instruction check used Xcode llvm-objdump on that exact
slice; addresses below are original virtual addresses. The ObjC stub
`0x100081d00` is `setFragmentBytes:length:atIndex:` in the saved REA pseudocode.
| Native path | Source of buffer 4 data | Binding instructions | Result |
| --- | --- | --- | --- |
| `FUN_100030084` | `0x1000300d0: str wzr,[sp,#0x70]` | `0x100030524 add x2,sp,#0x70`; `0x100030530 mov w4,#4`; call `0x100030534` | mode 0 |
| `FUN_100030840` | `0x100030888: str wzr,[sp,#0x70]` | `0x100030bf4 add x2,sp,#0x70`; `0x100030c00 mov w4,#4`; call `0x100030c04` | mode 0 |
| `FUN_10003127c`, called at `0x100027a34` | caller `0x1000279f8 str wzr,[sp,#0x18]` | callee `0x10003156c add x2,x29,#0x28`; `0x100031578 mov w4,#4`; call `0x10003157c` | mode 0 |
| `FUN_10003127c`, called at `0x10002be14` | `0x10002bd04 str wzr,[sp,#0xbc]`; `0x10002bdb4 ldr w9,[sp,#0xbc]`; `0x10002bde0 str w9,[sp,#0x18]` | same callee binding | mode 0 |
| inline fallback in `FUN_10002b8a4` | same zero at `0x10002bd04` | `0x10002bf74 add x2,sp,#0xbc`; `0x10002bf80 mov w4,#4`; call `0x10002bf84` | mode 0 |
The callee creates a 0x110-byte frame and sets fp=sp+0x100, so its fp+0x28
is the caller's sp+0x18. The whole decoded executable text contains exactly
the two direct `bl 0x10003127c` calls listed above. This bounds the conclusion:
the inspected default/fallback paths do not establish any fixed region=1 path;
this is not an exhaustive exclusion of arbitrary indirect calls or runtime
buffer replacement.
Saved provider corroboration in `renderpath-evidence.json`:
- `0x100030084`: `ev_f6e9622db324900aa5bc7d21598097df4abfb2f1270503751af959c61d7c8405`.
- `0x100030840`: `ev_30343763e68d261f018fb476b50a8695ca677d85cbcea9eda709c4e46c89ada6`.
- `0x10003127c`: `ev_537b8f52a769c64a08223b18f0fb2fd1a0b1daa0f44ffeb9b5c132513a9bcaf8`.
- inline fallback `0x10002b8a4`: `ev_4a2bf9e0530d651ae929ef861a2c52bfdf691445def0a16d8bbe07d723ce0c0e`.
## Exact shader branch contract
`faceThinFragmentShader` lines 388–587:
1. `warpFlipY` optionally flips the working UV before deformation (389–402).
2. Source texture width/height are read and clamped to >=1 (427–434).
3. If any shape parameter is active, iterate faces with **stride77** (454).
With mode>0, run region with `clamp(abs(shoulian)*40,0,1)` (458–465), then
run the other shape pins with thin forced to0 (470–474). With mode0, run
`mssFaceShapePinCoordinate` with the original thin parameter (478).
4. If the shape structure is inactive but the separate faceThinStrength is
active, mode>0 uses region directly with clamped overall strength (506);
mode0 calls the old thin pin with `0.025*clamp(strength,0,1)` (510–512).
5. Clamp UV, undo warpFlipY, map sourceSampleRect, clamp again, apply sampleFlipY,
then sample the source (521–583). These are separate coordinate operations.
The active-shape pin helper's thin branch at 3168–3268 uses its incoming thin
ratio directly in the sequential17 pin field. Its pixel-size vector is not
used in that branch. This does not alone prove the CPU supplies no size gain;
CPU parameter generation remains a separate evidence question.
Therefore region should not replace the default field based on buffer8=1.
The current frame's qualified points may be accepted as-is without implying
that its spatial warp must be changed to region.
## Region function, retained as an optional-path analysis
`mssFaceThinRegionCoordinate` is lines 706–2835 (another copy appears at
4369 onward inside the offset-map shader). Its inputs are working UV, readonly
point array, face offset, strength, and source texture size. There is **no time,
previous frame, previous point set, persistent buffer write, texture history,
atomic, or other temporal input**. All stores in this function initialize local
index arrays. It is a pure spatial function of the supplied points/parameters.
Use `S=(sourceTextureWidth,sourceTextureHeight)`; the point buffer stores UVs.
The function loads `P76` as center (747–750), obtains contour bounds from all
17 points59..75 (714–793), and computes `W/H` as contour spans multiplied by S
(757–772). It returns identity for W<=1 or H<=1 (773–776).
Query `p=uv*S` at810. Radius, distance, axis normalization, and most amplitude
caps are calculated in **pixel space**. The contour center and all point
positions must be multiplied componentwise by S before such operations.
Displacements are divided componentwise by S to return to UV. Substituting UV
Euclidean distance for pixel Euclidean distance changes the aspect behavior.
The main axis is normalized `P67*S - P76*S`, falling back to (0,1); its
perpendicular is `(axis.y,-axis.x)` (863–894). This is based on chin and center,
not a fixed vertical image direction, and follows head roll.
Strength `s=clamp(input,0,1)` is nonlinearly mapped:
`u=mix(s,s*s*(3-2*s),.28)`, `a=.105*u*(1.08-.08*u)` (795–805).
Aspect and asymmetric left/right center-to-contour spans modulate the field
(812–841). These are spatial shape adaptations, not temporal smoothing.
The output is a combination of:
- An analytic side contraction with head-local horizontal/vertical smooth
envelopes and analytic face support (862–1117).
- A neighbor-aware17-contour weighted field (1133–1638), with protected center
anchors, smooth radial/directional support, weight-sum normalization and a
pixel amplitude cap.
- Another17-contour field with strength-dependent neighboring-point averaging
(`mix(Pi,(Pprev+2*Pi+Pnext)/4,alpha)`,1683–1699); contour index distance from
chin67 affects weights (1704 onward); contributions are accumulated before
normalization rather than sequentially updating the query (1905–1939).
- Additional lower-face/chin components using65..69, including a three-point
chin mean and roll-aware local envelopes (1954–2765).
Final group caps include `(0.04+.066*s)*W` and `(0.064+.058*s)*W`
(2785–2814). The final face envelope multiplies the total by
`.32+.68*analyticMask`, then returns `clamp(uv-totalUVDisplacement,0,1)`
(2815–2829). This is an inverse sampling field.
These features can reduce sensitivity to an isolated spatial point perturbation
if this mode is selected; that is an inference about the mathematics, not a
measured temporal improvement or evidence that the default path uses it.
## Protection formulas
Below `smooth(a,b,x)` means smoothstep with clamped input. All dimensions and
distances here are in pixels, and decimal constants are decoded AIR float
constants (small float rounding retained in the original text).
Let `C=P76*S`, `D=normalize(P67*S-C)`, `T=(D.y,-D.x)`.
Analytic face mask, lines2839–2916:
```
q = p-C-.12*H*D
x = dot(q,T)/max(.5*W,1)
y = dot(q,D)/max(.7*H,1)
r = sqrt(x*x+y*y)
mask = (1-smooth(.88,1.03,r)) * smooth(-.92,-.72,y)
* (1-smooth(1.02,1.16,y))
```
Temple factor, lines3047–3149:
```
x = abs(dot(p-C,T))/max(.52*W,1)
y = dot(p-C,D)/max(.60*H,1)
k = smooth(-.28,-.02,y) * (1-smooth(.42,.72,y))
factor = clamp(1
-.62*smooth(.50,.80,x)*(1-smooth(-.46,-.14,y))*(1-.86*k)
-.20*smooth(.34,.70,x)*(1-smooth(-.72,-.34,y))*(1-.70*k), .30,1)
```
Anchor factor, lines2993–3043: indices47/48/49/52/76; radius
`R=max(12,.28*min(W,H))`; take the minimum
`smooth(.34*R,R,distance(p,Pi*S))`, then clamp to [.12,1].
Chin/mouth factor, lines2929–2977: indices47/48/49/52; radius
`R=max(10,.16*min(W,H))`; take the minimum
`smooth(.38*R,R,distance(p,Pi*S))`, then clamp to [.24,1].
The floors protect without hard disabling the whole field. Several region
subcomponents additionally interpolate these factors by their local support,
so merely multiplying an old17-pin warp by these factors is not equivalent.
## Smallest justified alignment path
For the inspected default: keep the pin family and validate the actual CPU
ratios, point layout/UV orientation, operation order and optional offset-map
processing. Do not insert region or stronger point smoothing as an asserted
fix for the user's clip. The large eye control and any offset-map spatial
filter need their own evidence; this review has not established their exact
runtime contribution.
Only if a separate reachable region-mode path is later established should its
field be reconstructed. A faithful implementation needs the pure coordinate
function, all four protections, pixel-space geometry, contour accumulation and
caps, followed by the remaining shape pins with thin=0. Evaluate that same
coordinate map for RGB and every mask. A generated float2 offset texture plus
one inverse sampler is a viable integration boundary; a reduced grid introduces
an approximation and needs measured UV error. Temporal state belongs upstream.
No region=1 runtime path or target output has been observed by this audit.
@@ -0,0 +1,453 @@
{
"cases" : [
{
"eye" : 0,
"height" : 256,
"maximum_displacement_pixels" : 0,
"maximum_independent_error_pixels" : 0,
"maximum_target_error_pixels" : 0,
"name" : "pose0 size0 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 256
},
{
"eye" : 0.25,
"height" : 256,
"maximum_displacement_pixels" : 0.35382923640349639,
"maximum_independent_error_pixels" : 0.0026882437460426967,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose0 size0 eye-only",
"target_rms_error_pixels" : 2.1921713206428187e-05,
"thin" : 0,
"width" : 256
},
{
"eye" : 0,
"height" : 256,
"maximum_displacement_pixels" : 7.0728000415791215,
"maximum_independent_error_pixels" : 0.002755077444486212,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose0 size0 thin-only",
"target_rms_error_pixels" : 0.00011702992366181097,
"thin" : 1,
"width" : 256
},
{
"eye" : 0.25,
"height" : 256,
"maximum_displacement_pixels" : 7.0728000415791215,
"maximum_independent_error_pixels" : 0.0027413680285147649,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose0 size0 thin+eye",
"target_rms_error_pixels" : 0.0001240079365079365,
"thin" : 1,
"width" : 256
},
{
"eye" : 0,
"height" : 216,
"maximum_displacement_pixels" : 2.3980817331903381e-14,
"maximum_independent_error_pixels" : 9.830249857363302e-06,
"maximum_target_error_pixels" : 0,
"name" : "pose0 size1 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 384
},
{
"eye" : 0.25,
"height" : 216,
"maximum_displacement_pixels" : 0.53075330293307399,
"maximum_independent_error_pixels" : 0.0027120972635748626,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose0 size1 eye-only",
"target_rms_error_pixels" : 7.0133057212432167e-05,
"thin" : 0,
"width" : 384
},
{
"eye" : 0,
"height" : 216,
"maximum_displacement_pixels" : 8.2005901709704148,
"maximum_independent_error_pixels" : 0.0027728968676576145,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose0 size1 thin-only",
"target_rms_error_pixels" : 0.00023521707359494627,
"thin" : 1,
"width" : 384
},
{
"eye" : 0.25,
"height" : 216,
"maximum_displacement_pixels" : 8.2005901709704148,
"maximum_independent_error_pixels" : 0.0027728968676576145,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose0 size1 thin+eye",
"target_rms_error_pixels" : 0.00024352479987655608,
"thin" : 1,
"width" : 384
},
{
"eye" : 0,
"height" : 320,
"maximum_displacement_pixels" : 1.7763568394002505e-14,
"maximum_independent_error_pixels" : 8.5299224090530028e-06,
"maximum_target_error_pixels" : 0,
"name" : "pose0 size2 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 192
},
{
"eye" : 0.25,
"height" : 320,
"maximum_displacement_pixels" : 0.26537789376221338,
"maximum_independent_error_pixels" : 0.0026286369668807358,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose0 size2 eye-only",
"target_rms_error_pixels" : 5.0712301697452399e-05,
"thin" : 0,
"width" : 192
},
{
"eye" : 0,
"height" : 320,
"maximum_displacement_pixels" : 8.8528177374135399,
"maximum_independent_error_pixels" : 0.0027577518479714133,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose0 size2 thin-only",
"target_rms_error_pixels" : 0.0002095422906842435,
"thin" : 1,
"width" : 192
},
{
"eye" : 0.25,
"height" : 320,
"maximum_displacement_pixels" : 8.8528177374135399,
"maximum_independent_error_pixels" : 0.0027577518479714133,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose0 size2 thin+eye",
"target_rms_error_pixels" : 0.00021076686089680123,
"thin" : 1,
"width" : 192
},
{
"eye" : 0,
"height" : 256,
"maximum_displacement_pixels" : 0,
"maximum_independent_error_pixels" : 0,
"maximum_target_error_pixels" : 0,
"name" : "pose1 size0 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 256
},
{
"eye" : 0.90354866293532343,
"height" : 256,
"maximum_displacement_pixels" : 1.4980655360450705,
"maximum_independent_error_pixels" : 0.002736966310374967,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose1 size0 eye-only",
"target_rms_error_pixels" : 3.7969521062487261e-05,
"thin" : 0,
"width" : 256
},
{
"eye" : 0,
"height" : 256,
"maximum_displacement_pixels" : 8.2983365210307376,
"maximum_independent_error_pixels" : 0.0027466016331799312,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose1 size0 thin-only",
"target_rms_error_pixels" : 0.00012969060431145763,
"thin" : 1,
"width" : 256
},
{
"eye" : 0.90354866293532343,
"height" : 256,
"maximum_displacement_pixels" : 8.2983365210307376,
"maximum_independent_error_pixels" : 0.0027466016331799312,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose1 size0 thin+eye",
"target_rms_error_pixels" : 0.0001249729933702032,
"thin" : 1,
"width" : 256
},
{
"eye" : 0,
"height" : 216,
"maximum_displacement_pixels" : 2.3980817331903381e-14,
"maximum_independent_error_pixels" : 9.830249857363302e-06,
"maximum_target_error_pixels" : 0,
"name" : "pose1 size1 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 384
},
{
"eye" : 0.90354866293532343,
"height" : 216,
"maximum_displacement_pixels" : 2.1744460803927361,
"maximum_independent_error_pixels" : 0.002720070336115162,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose1 size1 eye-only",
"target_rms_error_pixels" : 9.2332938119698104e-05,
"thin" : 0,
"width" : 384
},
{
"eye" : 0,
"height" : 216,
"maximum_displacement_pixels" : 10.38677748064452,
"maximum_independent_error_pixels" : 0.0027597851282901023,
"maximum_target_error_pixels" : 0.0055219139903783798,
"name" : "pose1 size1 thin-only",
"target_rms_error_pixels" : 0.00026077041763909807,
"thin" : 1,
"width" : 384
},
{
"eye" : 0.90354866293532343,
"height" : 216,
"maximum_displacement_pixels" : 10.38677748064452,
"maximum_independent_error_pixels" : 0.0027597851282901023,
"maximum_target_error_pixels" : 0.0055219139903783798,
"name" : "pose1 size1 thin+eye",
"target_rms_error_pixels" : 0.00026150911830191969,
"thin" : 1,
"width" : 384
},
{
"eye" : 0,
"height" : 320,
"maximum_displacement_pixels" : 1.7763568394002505e-14,
"maximum_independent_error_pixels" : 8.5299224090530028e-06,
"maximum_target_error_pixels" : 0,
"name" : "pose1 size2 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 192
},
{
"eye" : 0.90354866293532343,
"height" : 320,
"maximum_displacement_pixels" : 1.2129269895950687,
"maximum_independent_error_pixels" : 0.0027074615946477646,
"maximum_target_error_pixels" : 0.003910064697265625,
"name" : "pose1 size2 eye-only",
"target_rms_error_pixels" : 4.5346296046615942e-05,
"thin" : 0,
"width" : 192
},
{
"eye" : 0,
"height" : 320,
"maximum_displacement_pixels" : 10.144258143830054,
"maximum_independent_error_pixels" : 0.0027638516828663376,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose1 size2 thin-only",
"target_rms_error_pixels" : 0.00022775554094271662,
"thin" : 1,
"width" : 192
},
{
"eye" : 0.90354866293532343,
"height" : 320,
"maximum_displacement_pixels" : 10.144258143830054,
"maximum_independent_error_pixels" : 0.0027638516828663376,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose1 size2 thin+eye",
"target_rms_error_pixels" : 0.00022830552736565732,
"thin" : 1,
"width" : 192
},
{
"eye" : 0,
"height" : 256,
"maximum_displacement_pixels" : 0,
"maximum_independent_error_pixels" : 0,
"maximum_target_error_pixels" : 0,
"name" : "pose2 size0 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 256
},
{
"eye" : 1,
"height" : 256,
"maximum_displacement_pixels" : 1.9006110949587283,
"maximum_independent_error_pixels" : 0.0026860115231048072,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose2 size0 eye-only",
"target_rms_error_pixels" : 4.1011770074785939e-05,
"thin" : 0,
"width" : 256
},
{
"eye" : 0,
"height" : 256,
"maximum_displacement_pixels" : 9.5084281896725376,
"maximum_independent_error_pixels" : 0.0027749358063331774,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose2 size0 thin-only",
"target_rms_error_pixels" : 0.00017673850215450427,
"thin" : 1,
"width" : 256
},
{
"eye" : 1,
"height" : 256,
"maximum_displacement_pixels" : 9.5084281896725376,
"maximum_independent_error_pixels" : 0.0027749358063331774,
"maximum_target_error_pixels" : 0.00390625,
"name" : "pose2 size0 thin+eye",
"target_rms_error_pixels" : 0.00017399820437006796,
"thin" : 1,
"width" : 256
},
{
"eye" : 0,
"height" : 216,
"maximum_displacement_pixels" : 2.3980817331903381e-14,
"maximum_independent_error_pixels" : 9.830249857363302e-06,
"maximum_target_error_pixels" : 0,
"name" : "pose2 size1 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 384
},
{
"eye" : 1,
"height" : 216,
"maximum_displacement_pixels" : 2.65191960410192,
"maximum_independent_error_pixels" : 0.0026956562280248753,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose2 size1 eye-only",
"target_rms_error_pixels" : 0.00010205516806844121,
"thin" : 0,
"width" : 384
},
{
"eye" : 0,
"height" : 216,
"maximum_displacement_pixels" : 12.235777303453091,
"maximum_independent_error_pixels" : 0.002741529812605142,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose2 size1 thin-only",
"target_rms_error_pixels" : 0.0002526036292317462,
"thin" : 1,
"width" : 384
},
{
"eye" : 1,
"height" : 216,
"maximum_displacement_pixels" : 12.235777303453091,
"maximum_independent_error_pixels" : 0.0027522254693324445,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "pose2 size1 thin+eye",
"target_rms_error_pixels" : 0.00025965073459259622,
"thin" : 1,
"width" : 384
},
{
"eye" : 0,
"height" : 320,
"maximum_displacement_pixels" : 1.7763568394002505e-14,
"maximum_independent_error_pixels" : 8.5299224090530028e-06,
"maximum_target_error_pixels" : 0,
"name" : "pose2 size2 passthrough",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 192
},
{
"eye" : 1,
"height" : 320,
"maximum_displacement_pixels" : 1.6564609111123645,
"maximum_independent_error_pixels" : 0.002608603255927031,
"maximum_target_error_pixels" : 0.003910064697265625,
"name" : "pose2 size2 eye-only",
"target_rms_error_pixels" : 3.5839771916069625e-05,
"thin" : 0,
"width" : 192
},
{
"eye" : 0,
"height" : 320,
"maximum_displacement_pixels" : 11.320027734419691,
"maximum_independent_error_pixels" : 0.0027770856999663083,
"maximum_target_error_pixels" : 0.0055225868709385395,
"name" : "pose2 size2 thin-only",
"target_rms_error_pixels" : 0.00022890932538955193,
"thin" : 1,
"width" : 192
},
{
"eye" : 1,
"height" : 320,
"maximum_displacement_pixels" : 11.320027734419691,
"maximum_independent_error_pixels" : 0.0027770856999663083,
"maximum_target_error_pixels" : 0.0055225868709385395,
"name" : "pose2 size2 thin+eye",
"target_rms_error_pixels" : 0.0002305974089591263,
"thin" : 1,
"width" : 192
},
{
"eye" : 1,
"height" : 216,
"maximum_displacement_pixels" : 2.6925237951548051,
"maximum_independent_error_pixels" : 0.0027203383917974236,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "indexed eye centers",
"target_rms_error_pixels" : 8.2559591202410913e-05,
"thin" : 0,
"width" : 384
},
{
"eye" : -0.5,
"height" : 216,
"maximum_displacement_pixels" : 2.3980817331903381e-14,
"maximum_independent_error_pixels" : 9.830249857363302e-06,
"maximum_target_error_pixels" : 0,
"name" : "negative eye strength clamps to zero",
"target_rms_error_pixels" : 0,
"thin" : 0,
"width" : 384
},
{
"eye" : 2,
"height" : 216,
"maximum_displacement_pixels" : 2.6925237951548051,
"maximum_independent_error_pixels" : 0.0027203383917974236,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "high eye strength clamps to one",
"target_rms_error_pixels" : 8.2559591202410913e-05,
"thin" : 0,
"width" : 384
},
{
"eye" : 0.90000000000000002,
"height" : 216,
"maximum_displacement_pixels" : 6.9857867334571617,
"maximum_independent_error_pixels" : 0.0027701099063711732,
"maximum_target_error_pixels" : 0.00391387939453125,
"name" : "overlapping sequential eyes",
"target_rms_error_pixels" : 0.00024035192027544236,
"thin" : 0.69999999999999996,
"width" : 384
}
],
"checks" : 211,
"device" : "Apple M1 Max",
"maximum_independent_error_pixels" : 0.0027770856999663083,
"maximum_target_error_pixels" : 0.0055225868709385395,
"mode_buffer4" : 0,
"sample_flip_y_buffer8" : 0,
"scope" : "Authored synthetic Vision76 geometry; thin and big-eye controls only; no user media or target binaries embedded",
"target_oracle_executed" : 1,
"warp_flip_y_buffer9" : 0
}
@@ -0,0 +1,81 @@
{
"authoredTwoFaceCrossing" : {
"rawIndex0" : "same authored identity A moved by .10",
"rawIndex1" : "different authored identity B moved within .01 of old center",
"selectedRawIndex" : 1
},
"defaultAppearanceOnDetectionLoss" : {
"geometryFacesAfter" : 1,
"geometryFacesBefore" : 1,
"meanColorMaskAfter" : 0,
"meanColorMaskBefore" : 0.39522577568114048,
"meanSkinMaskAfter" : 0,
"meanSkinMaskBefore" : 0.29670316041502076
},
"landmarkWeightsByFrameRate" : [
{
"currentLandmarkWeight" : 0.89999999999999925,
"fps" : 25
},
{
"currentLandmarkWeight" : 0.89999999999999925,
"fps" : 30
},
{
"currentLandmarkWeight" : 0.5,
"fps" : 60
},
{
"currentLandmarkWeight" : 0.25,
"fps" : 120
}
],
"outOfOrderCachedMissingFrame" : {
"initial40OutputFaces" : 0,
"ordered0Then40OutputFaces" : 1,
"replay40AfterAdding0OutputFaces" : 0
},
"pureBBoxTranslationJitter" : {
"fps" : 25,
"frames" : 20,
"maximumOutputVsRawPointError" : 0,
"outputOverRawAmplitude" : 1,
"outputStepNormalized" : 0.0080000000000000071,
"rawStepNormalized" : 0.0080000000000000071
},
"reacquisition" : {
"continuousPoint69x" : 0.60288473677011323,
"rawPoint69x" : 0.60568473677011325,
"reacquiredPoint69x" : 0.60568473677011325
},
"scope" : "Authored synthetic geometry and image only; actual current PGFaceProcessor implementation linked; no target binary or user media.",
"topologyValidMissingValid" : {
"held" : [
false,
false,
false
],
"pixelDifferencesFromUnwarpedInput" : [
{
"changedChannelsAbove1e_6" : 85835,
"maxChannelDifference" : 0.89999997615814209,
"meanChannelDifference" : 0.055795646515334418
},
{
"changedChannelsAbove1e_6" : 0,
"maxChannelDifference" : 0,
"meanChannelDifference" : 0
},
{
"changedChannelsAbove1e_6" : 85835,
"maxChannelDifference" : 0.89999997615814209,
"meanChannelDifference" : 0.055795646515334418
}
],
"points76Counts" : [
76,
0,
76
]
}
}
+110
查看文件
@@ -0,0 +1,110 @@
# 瘦脸跟踪闪烁:1.3.2 与目标 2.4.0 的对齐缺口
2026-10-22。用户实测同一段素材,本插件瘦脸跟踪闪烁,目标插件稳定。本次分析基线为 `b58fec1a86db7a2e474e1cc65a5911447b5906b9`,已安装版仍为 1.3.2(17)。完成源码审查、隔离合成复现、静态二进制对照和当前素材的独立连续帧采样;通过宿主界面读取状态并定位源文件,没有修改参数/片段、产品源码或重新编译 APP。
主要缺口在跨帧几何的有效性判定、坏帧保持与缓存更新。此前恢复单帧瘦脸位移场,并没有完整对齐新版目标的连续跟踪链。以下区分已经复现的本品行为、目标的静态分支,以及尚未定位到用户素材具体帧的部分。
## 目标版本与旧分析的边界
本次目标是用户指定的 `/Users/dayu/Downloads/MSE18V2.app`,版本 **2.4.0 / 20260724150000**。插件通用二进制 SHA-256 为 `577303e0de72e07feb576c882e82382b3f056b023a7e987d990ffb37f6b16109`,arm64 切片为 `afc71d0d21fd3c1ca95ca745ddafcd729e36ad95f17d8638009f2e45d9b7e8df`。切片逐字节核对过本次目标,REA 使用 Ghidra 12.1.4。随后仅读取已运行的系统 FxPlug 目标进程,其2.4.0可执行文件摘要与Downloads目标一致;本次没有启动目标、附加调试器或采集其内部几何。
原有 [auto-follow-report.json](auto-follow-report.json) 分析的是 `/Applications/MSE18V2.app` 的旧版 `1.0.20260617-170517`,通用二进制 SHA 为 `6c5d74bc844a5c5f745718b48ea10ec6635d08aa08a5b6dc6e62653980849f8a`。其中 temporal 混合系数和历史优先级不能直接当成 2.4.0 的默认瘦脸实现。
新版 `renderDestinationImage`(`0x10002179c`)的单主要人脸几何分支直接调用 **`mss_pinStyleFacePoints…`(`0x10003d69c`)**。后者在筛选当前几何后,选择接纳新点或回用上一组有效点,并可向 `trackedFaceBox…`(`0x10003cc78`)请求连续跟踪框。旧 `temporalFacePoints…` 方法仍存在,但当前提供者没有解析到它的直接调用者;这不能排除任意 Objective-C 间接调用,却足以纠正“只按旧 temporal 方法调平滑系数即可对齐新版”的假设。
对应 Evidence:render `ev_2330b66132cd6539b8c66313c8dc43ca6ff5eb3e0881abbbe26857f99de6a83b`;pin-style `ev_58dee36fddf4e4c98ce3d880a0bb98190351d1269c960b3ccc275aef81265e8f`;直接调用关系 `ev_568a93f2165e5dc5046e610f47c23e9cebc33f55b5d12d3ee005a1e99a7191f6`、`ev_a8cf6f3aec6fbcf2b698ab8ec52313e20a6a5e0d636bc922c4293e436861db7e`、`ev_7c53c39d245e0063cab89358ab8948cdd734488460464d5f455674bb80a1515a`。
## 当前时间线素材的连续帧核对
用户补充截图后,在 Final Cut Pro 当前时间线选中片段,通过“文件 → 在访达中显示”确认源视频为 `girl-in-sun-hat-smiling-in-rural-field-2026-10-03-13-54-14-utc.mov`。项目为 1080p25,选中片段从18:18到37:11,瘦脸1.00、大眼0.90355、多人模式关闭。没有修改参数或片段;检查结束返回 FCP。
直接调用当前产品检测器与跟踪器,在独立1920×1080解码上按25fps采样源文件全长18.752秒,共 **469帧**。源帧率为29.97003,因此这里模拟项目的采样节奏,并非逐一处理所有源帧,也不等同于FCP的实时请求/色彩/尺寸路径。
- 469帧全部为单脸且完整76点;空检测、部分点、held、Vision/解码错误均为0,关键点置信度最低0.814。**本段离线检查没有触发上文合成的“缺点使瘦脸归零”路径,也没有触发低于0.55的置信度条件。不能将它直接认定为当前素材的主因。**
- 最大轮廓形状步幅出现在请求源时间5.72秒(实际取到5.7057秒)附近。扣除前后检测框的平移/缩放后,raw轮廓RMS步幅为脸框尺度的6.33%,当前稳定输出仍为5.24%;眼部区域步幅7.11%。同一步检测框尺寸变化7.23%,框中心变化0.01061(全图归一化距离)。
- 变化较大的连续区间包括源5.56–5.76秒,另有0.7–0.92秒与片尾。按照所选片段位置、假设源入点为0且未变速,5.72秒约对应时间线24:11,与AX播放头24:11相近;未导出源入点/重定时,且检视器LCD同时为35:22,不能断言用户截图就是该源帧。
- 数值包含真实头部旋转和表情变化;平移/缩放抵消并未消除这些运动。因此它证明“检测仍成功时,几何变化大部分继续进入变形”,尚未量出其中多少属于错误检测。瘦脸和接近0.90的大眼都开启,大眼实现仍为近似,需要独立排查其视觉贡献。
这次素材的排查优先级应先放在**完整有效帧的框/点变化如何进入瘦脸,以及高强度大眼叠加**,再核对新版目标的连续框、几何质量判定与时间状态。一般性的部分失检、空缓存和蒙版缺口仍成立,但不能拿其他情境的合成失败代替这段素材的逐帧归因。
[当前素材标量摘要](tracking-flicker-20261022/current-clip-summary.json) 保存计数、分布和峰值时间;原视频、截图和完整真实关键点没有上传。通用诊断源码为 `scripts/inspect-tracking-sequence.m`;包含生产 .m 以复用真实静态几何转换和跟踪器,编译时只编译该诊断入口,不再重复链接 PGFaceProcessor.m。
## 已确定的差异
| 环节 | 当前 1.3.2 | 目标 2.4.0 的静态证据 | 对闪烁的意义 |
| --- | --- | --- | --- |
| 部分关键点失效 | 有合法脸框就接纳;当前缺 76 点时删去完整拓扑,且不标为 held | 接纳前要求足量点、轮廓、有限坐标、有效五官布局和置信度;不合格时可回用上一组有效点 | 当前会在部分失效帧直接关闭瘦脸,目标有完整形状保持路径 |
| 框与几何稳定 | 每次重新检测,旧点先映射到当前原始框;没有连续对象框跟踪器 | pin-style 可调用 VNSequenceRequestHandler / VNTrackObjectRequest,坏帧可把旧点映射到可用跟踪框 | 当前框噪声完全进入几何;目标另外维护连续框和上次有效形状 |
| 异常姿态与跳点 | 主要判断眼轴翻转;合法有限 76 点一般直接进入混合 | 额外检查眼/鼻/口/下轮廓结构、前后几何兼容及归一化点位跳变的中位数/P90 | 当前会接受点数足够但位置不可靠的帧 |
| 同帧检测复用 | 每个请求新建 Vision handler;几何缓存要求 raw 也完全相同才命中 | 人脸检测有按帧时间、输入范围和 tile 范围复用的缓存,并有重试分支 | 当前重复请求可能产生不同 raw 后重新计算;未测得其在用户素材中的发生频率 |
| 丢检时的外观支持 | 默认几何可以保持,但皮肤/牙齿/眼睛蒙版仍取 raw 检测 | 本次只完整核对目标的瘦脸主路径,未恢复其全部外观蒙版稳定策略 | 本品已复现几何保持时外观蒙版清零,可能叠加视觉闪烁;不把目标外观路径宣称为已完全对齐 |
pin-style 的接纳分支复制本帧合格点,保持分支使用旧有效点并尝试框重映射。因此**不能据旧报告把“目标平滑更强”或某个固定混合权重当作这次原因**。应优先恢复它筛掉坏几何、保留有效几何的机制。
目标跟踪器还有开关与时间管理:静态初始化检查 `MSE18_DISABLE_FACE_THIN_VISION_TRACKER`,通常未设置时启用;已核对初始化汇编的 OR/EOR 写入。跟踪方法包含同帧复用、时间倒退/间隔后的重置、跟踪结果置信度检查和短期保框分支。这里证明默认配置下的可达控制流,没有记录用户宿主内实际环境变量或运行命中次数,不能称为运行验证,也不能仅添加一个 VNTrackObjectRequest 就声称完整复刻。
## 隔离复现结果
探针直接链接当前 `PGFaceProcessor.m`,用自造 256×256 图与合成关键点调用实际选择器、变形和蒙版实现。没有使用用户画面、真实人脸点阵或目标运行结果。运行 `zsh scripts/diagnose-tracking.sh` 输出 JSON;它是诊断统计,退出码 0 只表示执行完成。
1. **76 → 缺 76 → 76,瘦脸中间一帧完全归零。** 三帧一直有输出脸框,点数为 `76/0/76`,held 全为 false。只开瘦脸时,相对原图的变化通道数为 `85835/0/85835`,中间帧像素完全直通。源码入口:`PGFaceProcessor.m` 208–227、308、532–554、645–646。这证明了一个足以造成闪烁的路径;没有测量它在用户视频中的出现率。
2. **纯脸框抖动衰减为 0%。** 25fps、20 帧,框的 X 方向交替 ±0.004,框内点保持一致;输入和稳定后的点位步幅均为 0.008,输出/输入振幅比 `1.0`。源码 177–186、196、214–217 的重映射原样继承了框平移。目标对各种轻微框噪声的最终响应尚未做数值对照,不能把此数值直接说成两插件的实测差值。
3. **当前点混合在 25/30fps 时采用 90% 新点。** 60fps 为 50%,120fps 为 25%;现有 10ms 用例只检查 30% 新点。掉检后重获还跳过 76 点混合而直接采用 raw。源码 186、211–225、308;现有 `slim_tests.m` 408–410 明确要求重获采用 raw。这解释当前实现为何对扰动敏感,不是目标 2.4.0 权重的证据。
4. **空缓存会保留第一次请求顺序的结果。** 先请求 40ms 空帧,再补入 0ms 有效脸,重播 40ms 仍输出 0 脸;按 0→40ms 请求则输出 1 脸。源码 279 的命中直接返回,插入早期帧不会更新后续结果。宿主锁只串行化到达顺序;并发测试只验证安全和重复一致,没有要求不同到达顺序获得相同几何。尚未记录用户宿主实际请求顺序。
5. **几何保持不等于美容蒙版保持。** 默认路径掉检时几何脸数仍为 1,colorMask 均值从 `0.3952` 变为 `0`,skinMask 从 `0.2967` 变为 `0`。生产入口强制 singleFace=0,走源码 664–666 的 raw 外观支持;单人 held 外观分支并未在该默认路径使用。
6. **多人仍有额外边界。** 默认关联只按防跳区域内距上一框中心最近者选脸;自造交叉可选到陌生人。fastMulti 直接返回 raw,不做当前单主要人脸的保持。此项没有证明用户单人素材发生了换脸,修复时应单列验收。
原 1901 项回归主要证明单帧图像、几何和接口行为。已有跟随测试检查完全空检测、10ms 扰动与缓存重复安全,没有覆盖“76 点短暂不完整”的连续帧,也没有以常用帧率约束抖动幅度。此前每两秒取一帧、共 11 帧的真实视频检查同样不能替代连续序列稳定性验收。
## 建议修复顺序与验证要求
- **先处理部分失效和异常几何。** 在接纳新主脸前检查完整拓扑与五官布局;不合格时进入上一组有效形状的保持/重映射路径。明确区分有效检测时间、输出时间和保持状态,避免仅靠刷新输出时间无限保持。
- **再对齐连续框、重获和时间管理。** 结合新版默认可达跟踪链处理同帧、倒退、跳时、正常运动及重获;从转头、遮挡恢复检查形状是否突然跳回 raw。先逐条核对机制,再决定系数,避免用增加滞后来掩盖坏点。
- **补齐缓存和外观同步。** 明确同帧结果与新增前帧历史的失效关系、源切换隔离及默认蒙版跟随;以顺序/倒序/重复/多线程请求对照输出。
- **用同一段素材做连续帧验收。** 锁定宿主、原始帧率、预览/导出条件和相同单项参数;记录检测有效性、保持原因、框/点扰动与效果开关变化,和目标逐帧对照。单张图正常、自动用例通过都不足以宣布闪烁修复。
当前素材、宿主和可见参数已确认,并完成469帧独立采样;有效点短暂缺失未在该采样中出现。实现缺口与几何变化已定位,目标与本品的原生同帧输出、源入点映射和大眼贡献仍未分离,尚未认定唯一原因。本次不构建新 APP,不更新安装包或文章。
## 保留资料
- [诊断结果](tracking-flicker-20261022/synthetic-results.json)
- [目标版本、源码指纹和范围](tracking-flicker-20261022/manifest.json)
- [选取的完整 REA Evidence 记录](tracking-flicker-20261022/rea-evidence.json),含原始/规范化结果与提供者限制;为互操作研究资料,不包括目标 APP 或 metallib。
- 探针源码 `scripts/diagnose-tracking.m` 与入口 `scripts/diagnose-tracking.sh`。构建依赖与项目一致,需 macOS 的 Core Image/Vision;生成文件位于忽略的 build 下。
Ghidra 对部分 CGRect、CMTime 和返回寄存器的恢复不完整。报告依据渲染调用关系、完整点门槛、接纳/保持内存分支及初始化汇编作结构判断;未声称所有阈值布尔表达式与目标逐条等价。REA 会话已保存并关闭,证据可离线复查。
## 连续帧诊断恢复与复用
在项目根执行(先创建 build 目录):
```sh
mkdir -p build/tracking-diagnostics
xcrun clang -fobjc-arc -fmodules -mmacosx-version-min=13.0 \
-framework Foundation -framework AVFoundation -framework CoreImage \
-framework CoreGraphics -framework CoreMedia -framework Metal \
-framework Vision -framework ImageIO scripts/inspect-tracking-sequence.m \
-o build/tracking-diagnostics/inspect-sequence
build/tracking-diagnostics/inspect-sequence '/绝对路径/源素材.mov' \
build/tracking-diagnostics/sequence.json 25 1920 18.72
```
最后三个参数是采样帧率、解码最长边与诊断时间偏移;18.72只来自本次所选片段位置,分析其他素材按实际设置。程序不导出图片或完整真实点阵,输出含逐帧标量;本次只提交汇总,详细本地数据保持在 build。用户视频和目标二进制均未随仓库分发,重跑具体素材需要自行提供原文件;二者不是产品构建依赖。源码、合成诊断及本次标量/静态证据均随 Git 保存。
分析与连续帧诊断的可恢复节点为 `a0c5357efd87a63dbee6269d9335e6aa3ba78799`,已推送并读回双远端。从 GitHub 匿名独立克隆核对284个文件的字节/blob/执行位、strict/full fsck,并重新构建合成诊断、逐项复现保存的JSON;产品源码与v1.3.2相同,无LFS/子模块。见[恢复回执](tracking-flicker-20261022/recovery.json)。后续main仅补充恢复说明,不改变已安装版或历史标签。
## 2.4.0 对齐实施中(2026-10-22)
用户已要求实施。新增静态证据见 `tracking-flicker-20261022/implementation-evidence.json`。补核对检测长边默认960(0x1000bd1f8),失败/空结果/不足76点时使用RGBA CGImage重试;下巴轴为点66、67、68按1:1.65:1加权减脸框中心,并非眼轴。接纳分支仍为原样复制合格点。新代码已加入对应检测、连续框、质量门槛、下巴轴/中位数/P90保护和保持重映射,反向/模式变化重置对象跟踪,同帧检测和几何复用。
同一草帽素材1920解码、25fps离线重跑469帧:连续框469帧可用,检测/完整76/结构/置信均合格,held为0。输出轮廓步幅P95为0.03344、峰值0.06458;旧输出P95为0.02872、峰值0.05240。两者包含真实动作,不能把更大的输出步幅称为闪烁改善,也不能用合成坏点测试代替此片段的最终视觉验收。实施继续追查关键点后的变形路径;尚未发布或替换已安装版。完整实拍点阵/帧不入仓,仅保留标量摘要。
### 后续变形核对与范围纠正
新shader虽含region算法,但本次确认的主encoder与fallback将buffer4(faceThinMode)写0;buffer8=1是sampleFlipY。不能因region函数存在或buffer8值为1,就称其默认启用。见 `tracking-flicker-20261022/region-mode-review.md` 与 `renderpath-evidence.json`(完整主render指令、encoder汇编及CPU参数)。本轮保留实际可达pin算法,未移植region。
另查明与当前片段叠加相关的两处差异:2.4.0的CPU thin系数为user×0.025,pin shader直接使用,移除本品旧版280像素sizeGain;大眼采用点6/13瞳孔为中心、点0–1/7–8的像素距离为半径,user×0.15与圆形二次权重,先左后右。本品原来使用眼区外框中心、脸框椭圆和smoothstep²,已改为上述明确公式。当前片段大眼约0.90355,不能将其影响全部归于瘦脸跟踪。其余窄脸/颧骨/小头近似与下巴/短脸/下颌既有尺寸增益不在本轮逐像素对齐范围内。
@@ -0,0 +1,17 @@
{
"status": "verified",
"repository": "https://github.com/dayumeida/FCP-BeautyGirl.git",
"anonymousClone": true,
"tag": "v1.3.2",
"commit": "8a70d38b7cd17e0ea530c5226f2be8c2df4308b3",
"trackedFilesByteBlobAndExecutableModeCompared": 271,
"resourceEntries": 13,
"strictFullFsck": true,
"lfs": false,
"submodules": false,
"xcodeProjectRead": true,
"signedProductSourceUnchangedFrom": "767bba8ac11e9bf9268a1e02d1a06d3c120c46aa",
"packageSha256": "20a88dacf68cc8812f5af86cfe94c1c0614414a68a019eaeac7ca9d078165ae1",
"recoveredPackageSignatureTicketAndGatekeeper": true,
"temporaryCloneRemoved": true
}
@@ -0,0 +1,10 @@
{
"remote": "https://dayu.media/git/dayu/codex-global-rules.git",
"before": "dde7e2ee26eae18627eefda9b956a6aca52354ad",
"commit": "4c3085861e26d34d82973483d0aae636b5fc7487",
"remoteRefVerified": true,
"independentCloneVerified": true,
"filesByteAndModeCompared": 11,
"strictFullFsck": true,
"temporaryCloneRemoved": true
}
@@ -31,5 +31,17 @@
"legacyPluginOriginalPathAbsent": true,
"filterUUIDPreserved": "FB14B0E6-59B2-4836-916F-CD0C559E9241",
"templateBytesUnchanged": true,
"nativeOldProjectReopen": "pending_user"
"nativeOldProjectReopen": "pending_user",
"afterPackagingCleanup": {
"buildPackageWorkAbsent": true,
"spotlightIndexedProductPaths": [
{
"path": "/Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app",
"bundleId": "com.dayu.fcpbeautygirl"
}
],
"newFormalPluginRegistered": true,
"legacyFormalPluginNotRegistered": true,
"appSignatureAndTicketRechecked": true
}
}
+63
查看文件
@@ -0,0 +1,63 @@
{
"status": "verified",
"version": "1.3.2",
"releaseTag": "v1.3.2",
"releaseCommit": "8a70d38b7cd17e0ea530c5226f2be8c2df4308b3",
"githubRelease": "https://github.com/dayumeida/FCP-BeautyGirl/releases/tag/v1.3.2",
"dayuRelease": "https://dayu.media/git/dayu/paintfcp-fxplug/releases/tag/v1.3.2",
"dayuReleaseAssetNameAndSizeReadback": true,
"dayuReleaseDownloadByteReadback": false,
"dayuReleaseGateway": "Site login/member gateway retained; not bypassed",
"article": "https://dayu.media/15593/",
"articleId": 15593,
"authorCoverCategoriesTagsAndAccessRulesPreserved": true,
"articleCodeBlocks": 1,
"downloadChecks": [
{
"url": "https://github.com/dayumeida/FCP-BeautyGirl/releases/download/v1.3.2/FCP-BeautyGirl-1.3.2-source.zip",
"name": "FCP-BeautyGirl-1.3.2-source.zip",
"bytes": 9728489,
"sha256": "c75a516192375c45e446b3f0170615773ec2bd63b0430c3441a87282ba8b6834",
"anonymousByteReadback": true
},
{
"url": "https://github.com/dayumeida/FCP-BeautyGirl/releases/download/v1.3.2/FCP-BeautyGirl-1.3.2.pkg",
"name": "FCP-BeautyGirl-1.3.2.pkg",
"bytes": 4889706,
"sha256": "20a88dacf68cc8812f5af86cfe94c1c0614414a68a019eaeac7ca9d078165ae1",
"anonymousByteReadback": true
},
{
"role": "package",
"url": "https://dayu.media/wp-content/uploads/2026/10/1792666128-1202b0d4fd350d5.pkg",
"bytes": 4889706,
"sha256": "20a88dacf68cc8812f5af86cfe94c1c0614414a68a019eaeac7ca9d078165ae1",
"anonymousByteReadback": true
},
{
"role": "source",
"url": "https://dayu.media/wp-content/uploads/2026/10/1792666125-4e4f241a6fe317d.zip",
"bytes": 9728489,
"sha256": "c75a516192375c45e446b3f0170615773ec2bd63b0430c3441a87282ba8b6834",
"anonymousByteReadback": true
}
],
"frontends": [
{
"url": "https://dayu.media/15593/",
"versionAndInstallCommand": true,
"singleCustomDescriptionAndKeywords": true,
"seoTitle": "大鱼人像美化 1.3.2|FCP / Motion 免费开源自动美颜插件",
"anonymousFreeAndLoginPromptVisible": true
},
{
"url": "https://dayu.media/15593/?release_check=1792666175",
"versionAndInstallCommand": true,
"singleCustomDescriptionAndKeywords": true,
"seoTitle": "大鱼人像美化 1.3.2|FCP / Motion 免费开源自动美颜插件",
"anonymousFreeAndLoginPromptVisible": true
}
],
"testOrderPlaced": false,
"nativeOldProjectReopen": "pending_user"
}
@@ -0,0 +1,5 @@
{
"sha": "8a70d38b7cd17e0ea530c5226f2be8c2df4308b3",
"mainAndBranchAndTagVerifiedBothRemotes": true,
"tag": "v1.3.2"
}
@@ -0,0 +1,13 @@
{
"tag": "v1.3.2",
"commit": "8a70d38b7cd17e0ea530c5226f2be8c2df4308b3",
"file": "build/FCP-BeautyGirl-1.3.2-source.zip",
"bytes": 9728489,
"sha256": "c75a516192375c45e446b3f0170615773ec2bd63b0430c3441a87282ba8b6834",
"sourceFileCount": 268,
"excluded": [
"releases/1.2.0/\u5927\u9c7c\u4eba\u50cf\u7f8e\u5316-1.2.0.pkg",
"releases/1.3.1/\u5927\u9c7c\u4eba\u50cf\u7f8e\u5316-1.3.1.pkg",
"releases/1.3.2/FCP-BeautyGirl-1.3.2.pkg"
]
}
@@ -0,0 +1 @@
{"id":"379a432b-ccca-4676-9567-30dc49bb2400","message":"Processing complete","status":"Accepted"}
@@ -0,0 +1 @@
PASS: 211 eye alignment checks; independent max 0.0027770857 px; target executed max 0.0055225869 px
+37
查看文件
@@ -0,0 +1,37 @@
2026-10-23 00:09:08.961 host-tests[58295:9291647] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
Default IOSurface maximum byte difference: 0
Alternate destination tag maximum byte difference: 0
Anatomy default 0/0 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy default 0/1 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy default 1/0 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy default 1/1 max=0 mean=0.0000000 eye=0/0 chin=0 inverted=0
Anatomy big-eye 0/0 max=1 mean=0.0001335 eye=33428/33655 chin=0 inverted=0
Anatomy big-eye 0/1 max=1 mean=0.0001335 eye=33428/33655 chin=0 inverted=0
Anatomy big-eye 1/0 max=1 mean=0.0001335 eye=33428/33655 chin=0 inverted=0
Anatomy big-eye 1/1 max=1 mean=0.0001335 eye=33428/33655 chin=0 inverted=0
Anatomy chin-positive 0/0 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-positive 0/1 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-positive 1/0 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-positive 1/1 max=1 mean=0.0001459 eye=0/0 chin=116812 inverted=0
Anatomy chin-negative 0/0 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Anatomy chin-negative 0/1 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Anatomy chin-negative 1/0 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Anatomy chin-negative 1/1 max=1 mean=0.0000849 eye=0/0 chin=60426 inverted=0
Anatomy short-positive 0/0 max=2 mean=0.0011072 eye=0/0 chin=285953 inverted=0
Anatomy short-positive 0/1 max=2 mean=0.0011072 eye=0/0 chin=285953 inverted=0
Anatomy short-positive 1/0 max=2 mean=0.0011072 eye=0/0 chin=285953 inverted=0
Anatomy short-positive 1/1 max=2 mean=0.0011072 eye=0/0 chin=285953 inverted=0
Anatomy short-negative 0/0 max=2 mean=0.0008583 eye=0/0 chin=225648 inverted=0
Anatomy short-negative 0/1 max=2 mean=0.0008583 eye=0/0 chin=225648 inverted=0
Anatomy short-negative 1/0 max=2 mean=0.0008583 eye=0/0 chin=225648 inverted=0
Anatomy short-negative 1/1 max=2 mean=0.0008583 eye=0/0 chin=225648 inverted=0
Anatomy jaw 0/0 max=1 mean=0.0001974 eye=0/0 chin=12951 inverted=0
Anatomy jaw 0/1 max=1 mean=0.0001974 eye=0/0 chin=12951 inverted=0
Anatomy jaw 1/0 max=1 mean=0.0001974 eye=0/0 chin=12951 inverted=0
Anatomy jaw 1/1 max=1 mean=0.0001974 eye=0/0 chin=12951 inverted=0
Anatomy v-face 0/0 max=1 mean=0.0001240 eye=0/0 chin=39246 inverted=0
Anatomy v-face 0/1 max=1 mean=0.0001240 eye=0/0 chin=39246 inverted=0
Anatomy v-face 1/0 max=1 mean=0.0001240 eye=0/0 chin=39246 inverted=0
Anatomy v-face 1/1 max=1 mean=0.0001240 eye=0/0 chin=39246 inverted=0
Legacy fallback upright comparison: max=1 mean=0.0002766
Host adapter: 370 checks, 0 failures
@@ -0,0 +1,7 @@
Beauty renderer: 57 checks, 0 failures (Metal).
2026-10-23 00:09:05.459 face-tests[58271:9291524] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
Face processing: 123 checks passed
2026-10-23 00:09:21.175 slim-tests[58299:9291683] FBBA: creating VNFaceBBoxAligner from VNFaceDetectorRevision2: VNFaceDetectorRevision2
PASS: 1034 slim alignment checks; maximum UV oracle error 1.09896462e-05
PASS: 87 tracking sequence checks (0 failures); synthetic data only
PASS: 126 eye alignment checks; independent max 0.0027770857 px; target not requested max 0 px
@@ -0,0 +1 @@
Inspector: 227 checks, 0 failures
@@ -0,0 +1,5 @@
........
----------------------------------------------------------------------
Ran 8 tests in 3.237s
OK
+21
查看文件
@@ -0,0 +1,21 @@
2026-10-23 00:09:06.631 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.25,0.75) setter=(0.25,0.75) readback=(0.25,0.75) mode=normalized verified=1
2026-10-23 00:09:06.640 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(80,180) surface=320x240 origin=0
2026-10-23 00:09:07.164 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.8,0.3) setter=(1152,-432) readback=(0.8,0.3) mode=centered-pixels verified=1
2026-10-23 00:09:07.165 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0.7,0.4) objectBounds=(-1920,-1080,3840,2160) canvas=(152,78) surface=320x240 origin=0
2026-10-23 00:09:07.228 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.8,0.3) setter=(0.8,0.3) readback=(0.12345,0.87654) mode=normalized verified=0
2026-10-23 00:09:07.228 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.75,0.25) setter=(0.75,0.25) readback=(0.75,0.25) mode=normalized verified=1
2026-10-23 00:09:07.228 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0.75,0.25) objectBounds=(-100,-50,200,100) canvas=(160,60) surface=320x240 origin=0
2026-10-23 00:09:07.289 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.625,0.375) setter=(0.625,0.375) readback=(0.625,0.375) mode=normalized verified=1
2026-10-23 00:09:07.289 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0.625,0.375) objectBounds=(-100,-50,200,100) canvas=(240,60) surface=320x240 origin=0
2026-10-23 00:09:07.358 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.25,0.75) setter=(0.25,0.75) readback=(0.25,0.75) mode=normalized verified=1
2026-10-23 00:09:07.359 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(144,180) surface=320x240 origin=0
2026-10-23 00:09:07.434 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
2026-10-23 00:09:07.434 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0,1) objectBounds=(-100,-50,200,100) canvas=(100,140) surface=320x240 origin=0
2026-10-23 00:09:07.502 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(nan,nan) surface=320x240 origin=0
2026-10-23 00:09:07.562 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
2026-10-23 00:09:07.562 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(inf,nan) surface=320x240 origin=0
2026-10-23 00:09:07.626 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
2026-10-23 00:09:07.626 osc-tests[58300:9291685] PGFaceOSC coordinates selection=(0.25,0.75) objectBounds=(-100,-50,200,100) canvas=(80,nan) surface=320x240 origin=0
2026-10-23 00:09:07.687 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
2026-10-23 00:09:07.687 osc-tests[58300:9291685] PGFaceOSC point-write desired=(0.5,0.5) setter=(0.5,0.5) readback=(0.5,0.5) mode=normalized verified=1
OSC adapter: 98 checks, 0 failures
@@ -0,0 +1,41 @@
{
"status": "verified",
"version": "1.3.3",
"build": "18",
"package": "releases/1.3.3/FCP-BeautyGirl-1.3.3.pkg",
"bytes": 4905560,
"sha256": "214c109af60bce72046c89ff12b364938b6c00c3a348fa3740dabef661649e0d",
"sourceApp": "/tmp/fcp-beautygirl-release/Build/Products/Release/FCP-BeautyGirl.app",
"sourceCommit": "edf64ace6bc17f4db3b2dba71897997809c1029d",
"motionSource": "/Users/dayu/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸",
"motionFiles": [
{
"file": "大鱼人像美化/Drop Zone.tiff",
"sha256": "a7f8e3a2a431dbb3ce9e777040bb55e701cfe9ab9632e8cbb10359732bc8692e",
"locked": false
},
{
"file": "大鱼人像美化/large.png",
"sha256": "a02ddb255b1ae255c749bb837a759e2dee1c3b8ffb9bdc1167957761bd5a1b97",
"locked": true
},
{
"file": "大鱼人像美化/small.png",
"sha256": "1552b838dd88f60dee0795e20aaca652f9c558ecefc93500e895fbff77c2a4d5",
"locked": true
},
{
"file": "大鱼人像美化/大鱼人像美化.moef",
"sha256": "e9fa97dc14323d0daa7d3d61b06a78ba4000a70ab9f5e67ebffd750d49df1e15",
"locked": false
}
],
"packager": "/Applications/PKG工坊.app/Contents/MacOS/PKG工坊",
"productMigration": "PKG Forge base plus identity-checked backup/rollback block; final package freshly signed, notarized and stapled",
"notarization": {
"id": "3b44edbb-612d-47b2-8b11-e79874b2c635",
"message": "Processing complete",
"status": "Accepted"
},
"realInstallerExecuted": false
}
@@ -0,0 +1,3 @@
/Users/dayu/Documents/PaintFCP/releases/1.3.3/FCP-BeautyGirl-1.3.3.pkg: accepted
source=Notarized Developer ID
origin=Developer ID Installer: zhong luo (B3W25FX5H5)
@@ -0,0 +1,5 @@
{
"id": "3b44edbb-612d-47b2-8b11-e79874b2c635",
"message": "Processing complete",
"status": "Accepted"
}
@@ -0,0 +1,22 @@
Package "FCP-BeautyGirl-1.3.3.pkg":
Status: signed by a developer certificate issued by Apple for distribution
Notarization: trusted by the Apple notary service
Signed with a trusted timestamp on: 2026-10-22 16:11:48 +0000
Certificate Chain:
1. Developer ID Installer: zhong luo (B3W25FX5H5)
Expires: 2027-02-01 22:12:15 +0000
SHA256 Fingerprint:
4D AC CD 04 A5 4C 7E F8 13 13 A2 ED D1 C2 C0 D0 29 EF FA BF 7B 53
5A 43 C8 72 4D C4 0A A3 94 34
------------------------------------------------------------------------
2. Developer ID Certification Authority
Expires: 2027-02-01 22:12:15 +0000
SHA256 Fingerprint:
7A FC 9D 01 A6 2F 03 A2 DE 96 37 93 6D 4A FE 68 09 0D 2D E1 8D 03
F2 9C 88 CF B0 B1 BA 63 58 7F
------------------------------------------------------------------------
3. Apple Root CA
Expires: 2035-02-09 21:40:36 +0000
SHA256 Fingerprint:
B0 B1 73 0E CB C7 FF 45 05 14 2C 49 F1 29 5E 6E DA 6B CA ED 7E 2C
68 C5 BE 91 B5 A1 10 01 F0 24
+5
查看文件
@@ -0,0 +1,5 @@
productsign: using timestamp authority for signature
productsign: signing product with identity "Developer ID Installer: zhong luo (B3W25FX5H5)" from keychain /Users/dayu/Library/Keychains/login.keychain-db
productsign: adding certificate "Developer ID Certification Authority"
productsign: adding certificate "Apple Root CA"
productsign: Wrote signed product archive to /Users/dayu/Documents/PaintFCP/releases/1.3.3/FCP-BeautyGirl-1.3.3.pkg
+3
查看文件
@@ -0,0 +1,3 @@
Processing: /Users/dayu/Documents/PaintFCP/releases/1.3.3/FCP-BeautyGirl-1.3.3.pkg
Processing: /Users/dayu/Documents/PaintFCP/releases/1.3.3/FCP-BeautyGirl-1.3.3.pkg
The staple and validate action worked!
@@ -0,0 +1,36 @@
{
"package": "releases/1.3.3/FCP-BeautyGirl-1.3.3.pkg",
"bytes": 4905560,
"sha256": "214c109af60bce72046c89ff12b364938b6c00c3a348fa3740dabef661649e0d",
"version": "1.3.3",
"build": "18",
"app_files_compared": 34,
"app_byte_and_symlink_equality": true,
"template_files": {
"大鱼人像美化.moef": "e9fa97dc14323d0daa7d3d61b06a78ba4000a70ab9f5e67ebffd750d49df1e15",
"Drop Zone.tiff": "a7f8e3a2a431dbb3ce9e777040bb55e701cfe9ab9632e8cbb10359732bc8692e",
"large.png": "a02ddb255b1ae255c749bb837a759e2dee1c3b8ffb9bdc1167957761bd5a1b97",
"small.png": "1552b838dd88f60dee0795e20aaca652f9c558ecefc93500e895fbff77c2a4d5"
},
"locked_relative_paths": [
"大鱼人像美化/large.png",
"大鱼人像美化/small.png"
],
"script_sha256": {
"preinstall": "b0562f7705c84a2f34e31df67a4f0d429e60083bab122dad99f56a4b266c0174",
"postinstall": "27b663ec2d4c1c465b547b9817cf2262baec0e08f589a7ee408611b951939d66"
},
"isolated_script_tests": [
"fresh install",
"replace old locked thumbnail",
"partial copy failure restores old bytes and locks"
],
"isolation_changes": [
"Console identity/home redirected to task fixture",
"chown replaced with true",
"FxPlug verification skipped; formal app signature and discovery verified separately"
],
"real_installer_execution": false,
"app_and_template_installed": "Prepared signed APP verified; formal APP installation pending host exit; template files matched the specified installed category, not changed by running this PKG",
"fonts": "No editable third-party font dependency; promotional lettering baked into PNG. No optional font ZIP needed."
}
@@ -0,0 +1,36 @@
{
"package": "releases/1.3.3/FCP-BeautyGirl-1.3.3.pkg",
"bytes": 4905560,
"sha256": "214c109af60bce72046c89ff12b364938b6c00c3a348fa3740dabef661649e0d",
"version": "1.3.3",
"build": "18",
"app_files_compared": 34,
"app_byte_and_symlink_equality": true,
"template_files": {
"大鱼人像美化.moef": "e9fa97dc14323d0daa7d3d61b06a78ba4000a70ab9f5e67ebffd750d49df1e15",
"Drop Zone.tiff": "a7f8e3a2a431dbb3ce9e777040bb55e701cfe9ab9632e8cbb10359732bc8692e",
"large.png": "a02ddb255b1ae255c749bb837a759e2dee1c3b8ffb9bdc1167957761bd5a1b97",
"small.png": "1552b838dd88f60dee0795e20aaca652f9c558ecefc93500e895fbff77c2a4d5"
},
"locked_relative_paths": [
"大鱼人像美化/large.png",
"大鱼人像美化/small.png"
],
"script_sha256": {
"preinstall": "b0562f7705c84a2f34e31df67a4f0d429e60083bab122dad99f56a4b266c0174",
"postinstall": "27b663ec2d4c1c465b547b9817cf2262baec0e08f589a7ee408611b951939d66"
},
"isolated_script_tests": [
"fresh install",
"replace old locked thumbnail",
"partial copy failure restores old bytes and locks"
],
"isolation_changes": [
"Console identity/home redirected to task fixture",
"chown replaced with true",
"FxPlug verification skipped; formal app signature and discovery verified separately"
],
"real_installer_execution": false,
"app_and_template_installed": "Prepared signed APP verified; formal APP installation pending host exit; template files matched the specified installed category, not changed by running this PKG",
"fonts": "No editable third-party font dependency; promotional lettering baked into PNG. No optional font ZIP needed."
}
@@ -0,0 +1,11 @@
识别到 2 个安装内容:
- FCP-BeautyGirl.app
类型:FxPlug 插件
目录:/Library/Plug-Ins/FxPlug
状态:已签名并公证,保留隔离属性,自动验证并修复注册;失败时打开 APP 所在目录
- 大鱼磨皮瘦脸
类型:FCP 效果
目录:~/Movies/Motion Templates.localized/Effects.localized
状态:非 APP,保留隔离属性
识别完成,未生成安装包。
+27
查看文件
@@ -0,0 +1,27 @@
识别到 2 个安装内容:
- FCP-BeautyGirl.app
类型:FxPlug 插件
目录:/Library/Plug-Ins/FxPlug
状态:已签名并公证,保留隔离属性,自动验证并修复注册;失败时打开 APP 所在目录
- 大鱼磨皮瘦脸
类型:FCP 效果
目录:~/Movies/Motion Templates.localized/Effects.localized
状态:非 APP,保留隔离属性
完成:/Users/dayu/Documents/PaintFCP/build/package-1.3.3/base/FCP-BeautyGirl-1.3.3.pkg
本次唯一标识:com.dayu.fcppackage.b20261022161120794.p8eaemu3
安装包版本:1.3.3(继承 FCP-BeautyGirl.app)
[正在准备内容]
正在整理 2 个内容项
[正在生成安装包]
正在构建组件包
[正在签名]
正在使用 Developer ID Installer 签名
[正在等待 Apple 公证]
已提交 Apple,公证通常需要几分钟
[正在粘附公证票据]
公证已通过,正在粘附票据
[正在验证结果]
正在检查签名和安装包结构
签名、包结构与公证票据验证通过
@@ -0,0 +1,24 @@
Executable=/private/tmp/fcp-beautygirl-release/Build/Products/Release/FCP-BeautyGirl.app/Contents/MacOS/FCP-BeautyGirl
Identifier=com.dayu.fcpbeautygirl
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=482 flags=0x10000(runtime) hashes=4+7 location=embedded
Hash type=sha256 size=32
CandidateCDHash sha256=ab996568a91ce3b5c3aa419fa410f5fe7cf46a5f
CandidateCDHashFull sha256=ab996568a91ce3b5c3aa419fa410f5fe7cf46a5ff9d5aa6bbd21b9f245a13341
Hash choices=sha256
CMSDigest=ab996568a91ce3b5c3aa419fa410f5fe7cf46a5ff9d5aa6bbd21b9f245a13341
CMSDigestType=2
CDHash=ab996568a91ce3b5c3aa419fa410f5fe7cf46a5f
Signature size=8968
Authority=Developer ID Application: zhong luo (B3W25FX5H5)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=Oct 23, 2026 at 00:10:40
Notarization Ticket=stapled
Info.plist entries=23
TeamIdentifier=B3W25FX5H5
Runtime Version=27.0.0
Sealed Resources version=2 rules=13 files=5
Internal requirements count=1 size=184
Total signatures=1
Chosen signature=1
@@ -0,0 +1,2 @@
Processing: /tmp/fcp-beautygirl-release/Build/Products/Release/FCP-BeautyGirl.app
The validate action worked!
@@ -0,0 +1,29 @@
{
"status": "prepared",
"bundleIdentifier": "com.dayu.fcpbeautygirl",
"installationPath": "/Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app",
"options": {
"waitForHosts": false,
"userTests": true
},
"nativeEffectQA": {
"status": "pending_user",
"performedByScript": false
},
"source": {
"commit": "edf64ace6bc17f4db3b2dba71897997809c1029d",
"trackedWorktreeChanges": []
},
"updatedAt": "2026-10-22T16:11:03.691862+00:00",
"version": "1.3.3",
"build": "18",
"stagingSignature": "Executable=/private/tmp/fcp-beautygirl-release/Build/Products/Release/FCP-BeautyGirl.app/Contents/MacOS/FCP-BeautyGirl\nIdentifier=com.dayu.fcpbeautygirl\nFormat=app bundle with Mach-O universal (x86_64 arm64)\nCodeDirectory v=20500 size=482 flags=0x10000(runtime) hashes=4+7 location=embedded\nHash type=sha256 size=32\nCandidateCDHash sha256=ab996568a91ce3b5c3aa419fa410f5fe7cf46a5f\nCandidateCDHashFull sha256=ab996568a91ce3b5c3aa419fa410f5fe7cf46a5ff9d5aa6bbd21b9f245a13341\nHash choices=sha256\nCMSDigest=ab996568a91ce3b5c3aa419fa410f5fe7cf46a5ff9d5aa6bbd21b9f245a13341\nCMSDigestType=2\nCDHash=ab996568a91ce3b5c3aa419fa410f5fe7cf46a5f\nSignature size=8968\nAuthority=Developer ID Application: zhong luo (B3W25FX5H5)\nAuthority=Developer ID Certification Authority\nAuthority=Apple Root CA\nTimestamp=Oct 23, 2026 at 00:10:40\nInfo.plist entries=23\nTeamIdentifier=B3W25FX5H5\nRuntime Version=27.0.0\nSealed Resources version=2 rules=13 files=5\nInternal requirements count=1 size=184\nTotal signatures=1\nChosen signature=1\n",
"notarization": {
"id": "379a432b-ccca-4676-9567-30dc49bb2400",
"message": "Processing complete",
"status": "Accepted"
},
"signingVerified": true,
"stapleValidated": true,
"stagingPluginUnregistered": true
}
+23
查看文件
@@ -0,0 +1,23 @@
{
"product": "FCP-BeautyGirl",
"version": "1.3.3",
"build": "18",
"normalRegressionChecks": 2122,
"suites": {
"beauty": 57,
"face": 123,
"shape": 1034,
"tracking": 87,
"eyes": 126,
"hostASan": 370,
"oscASan": 98,
"inspectorASan": 227
},
"localOriginalShaderComparison": {
"checks": 211,
"cases": 40,
"maximumCoordinateErrorPixels": 0.0055225868709385395
},
"nativeTimelineFlickerConfirmedResolved": false,
"sourceSha256": "37152e095d5732bf4c8fe1b9553d9a478404e483338539cc6eec0b497732bc40"
}
@@ -0,0 +1 @@
PASS: 87 tracking sequence checks (0 failures); synthetic data only
+17 -1
查看文件
@@ -2,12 +2,16 @@
更新时间:2026-10-22,Asia/Shanghai。项目根目录:`/Users/dayu/Documents/PaintFCP`。
当前正式 APP 为 **1.3.2(17)**,工程、APP、插件服务及应用标识统一为 FCP-BeautyGirl。通用 Release、正式签名、Apple 公证、票据、Gatekeeper 和正式路径注册均已核验,旧 APP 与临时副本已清理。签名源码 `767bba8ac11e9bf9268a1e02d1a06d3c120c46aa`,发行标签 `v1.3.2`。1901 项自动回归、8 项 APP 身份迁移、8 项最终安装脚本身份迁移及模板隔离测试通过。滤镜 UUID、参数编号和归档保持原格式;新身份下的旧工程重开与最终原生视觉仍由用户实测。安装包基础由本机 PKG 工坊 CLI 生成,加入身份迁移后重新签名公证,包含指定已安装的「大鱼磨皮瘦脸」分类四项资源。文章 15593、GitHub 与大鱼 Release 沿用原入口,发布同步结果见本版回执。
当前正式 APP 为 **1.3.2(17)**,工程、APP、插件服务及应用标识统一为 FCP-BeautyGirl。通用 Release、正式签名、Apple 公证、票据、Gatekeeper 和正式路径注册均已核验,旧 APP 与临时副本已清理。签名源码 `767bba8ac11e9bf9268a1e02d1a06d3c120c46aa`,发行标签 `v1.3.2`。1901 项自动回归、8 项 APP 身份迁移、8 项最终安装脚本身份迁移及模板隔离测试通过。滤镜 UUID、参数编号和归档保持原格式;新身份下的旧工程重开与最终原生视觉仍由用户实测。安装包基础由本机 PKG 工坊 CLI 生成,加入身份迁移后重新签名公证,包含指定已安装的「大鱼磨皮瘦脸」分类四项资源。文章 15593、GitHub 与大鱼 Release 已同步 1.3.2,四项包/源码匿名下载及前台 SEO 检查通过。标签完整 SHA `8a70d38b7cd17e0ea530c5226f2be8c2df4308b3`,独立匿名克隆 271 文件和恢复包核验通过;详细边界见本版回执。
代码由 **OpenAI + Codex 全程编写,站长仅参与调试和测试**。文章和仓库须保留这个表述,不使用“站长原创代码”等说法。免费版本无需账号、激活码或付费授权;“自动注册”指系统插件注册。
1.2.0 交接整理前,工作区干净,`main`、`origin/main` 与 `github/main` 均为 `189a5d7634aa69db54330c90f8711b05d8e44b10`。这是文档新增前的基线;最新文档节点以 `git log -1` 和远端引用为准。
## 最新待修问题:瘦脸跟踪闪烁(2026-10-22)
用户实测同一段素材,本插件有闪烁、目标稳定。本次完成源码/合成诊断和 Downloads 2.4.0 的静态复核,未修改产品源码或安装版。新版单主要人脸的渲染路径为 pin-style 质量筛选、条件连续框跟踪和坏帧保持;原 auto-follow-report 的旧 Applications 版本 temporal 系数不能直接沿用。已复现部分76点缺失导致瘦脸归零、框抖动无衰减、首次请求空帧缓存不随补入前帧修正,以及几何保持时默认外观蒙版清零。已确认当前FCP草帽素材(1080p25、瘦脸1、大眼约0.9、多人关闭):独立469帧均单脸/完整76点,没有触发缺点归零;源5.56–5.76秒几何变化较大,5.72秒raw/稳定后轮廓步幅6.33%/5.24%。这些还含真实运动,原生请求与大眼贡献未分离,不宣称已修复。见[跟踪闪烁对齐分析](analysis/跟踪闪烁对齐分析.md),复现入口 `zsh scripts/diagnose-tracking.sh`;这次不是新发行版,无需重发安装包或文章。
## 先读和先查
接手时先查看 `git status --short --branch`、`git remote -v` 和最新提交,确认没有其他聊天的未提交改动。只读核对正式 APP 版本与模板目录,再决定是否需要编译或操作宿主。单纯修改文档不需要重建、重新公证或打开 Motion/FCP。
@@ -286,3 +290,15 @@ git switch --detach v1.2.0
新增稳定 float ID:75 五官保护量、76 非肤色区保护、77 局部微细锐化。旧 bool 30、独立全局磨皮 17 和全局锐化 4 均保留;后两项在更多设置中,不能重新解释其历史数值。PGBeautySettings v2 只追加三个 float,接受明确的 v1/160 字节旧状态。PGInspectorState 仍为 schema 1,缺 75 时从旧 bool 导入;旧 bool 为 true 时不得覆盖已读到的新浮点值。
1636 项检查与离屏面板已验,含 ASan 宿主/OSC/Inspector;本次未在原生宿主中重新操作新滑杆或验收效果视觉。正式插件发现只核验 PluginKit 路径,不把它写作 FCP 或 Motion 实际加载/画面通过。当前无需重新安装、重做封面或更新 1.2.0 安装包;用户反馈实际效果后继续针对性处理。
## 2026-10-23 跟踪稳定链实施(1.3.3 / 18)
当前开发分支 `codex/tracking-stability-240`,正式安装在交付完成前仍为1.3.2。用户已要求按目标2.4.0实施、签名公证后同步双仓与文章。新默认链:960检测+CGImage重试、VN连续框、结构/置信门槛、下巴轴/quantile异常保护、有效形状保持和同步蒙版、同帧复用/异常可重试。瘦脸去旧sizeGain,大眼切到瞳孔/像素眼宽与.15圆形权重。实际核对的Metal主encoder mode为0;region存在不证明默认启用,勿再次按buffer8误判。
本机目标Metal和生产同点数值场40场景最大坐标差约.005523px;469帧草帽离线采样无掉点/quality hold,但还没有原生时间线的前后导出,不能宣称用户闪烁已消除。详情 `docs/analysis/跟踪闪烁对齐分析.md`。同尺寸换源缺少真正来源ID、fastMulti/多人遮挡、窄脸/颧骨/小头近似与其他旧gain仍有边界。
FCP已切到用户另一项剪辑工程,不应强退或改时间线。新增 `deliver.py --prepare-only` 可在不替换正式APP的情况下完成Release签名公证粘票并释放共享锁;之后 `package-release.py --prepared-app` 用精确Accepted/stapled的staging APP和指定安装模板,经本机PKG工坊打包、公证与payload校验。两种打包来源都要求CDHash匹配交付回执及产品源码未变,防止旧APP与新源码混包。用户保存退出后用 `deliver.py --resume-prepared --wait-for-hosts --user-tests` 安装并清理,再回读正式路径、更新公证安装回执与发布状态。不要将准备完成说成已安装。
1.3.3已完成APP与最终PKG签名、公证Accepted和票据/签名/payload验证,产品源码固定在 `edf64ace6bc17f4db3b2dba71897997809c1029d`。暂存APP `/tmp/fcp-beautygirl-release/Build/Products/Release/FCP-BeautyGirl.app`;最终包 `releases/1.3.3/FCP-BeautyGirl-1.3.3.pkg`,SHA256 `214c109af60bce72046c89ff12b364938b6c00c3a348fa3740dabef661649e0d`。完整2122项回归与最终包8项身份迁移测试通过。本机安装仍待用户退出两宿主;保留已公证暂存APP,勿重建或覆盖后再假用prepared回执。
+30
查看文件
@@ -148,3 +148,33 @@ PKG由本机PKG工坊CLI从正式APP与用户指定已安装的「大鱼磨皮
1.3.1已发布至GitHub和大鱼Release,origin/github的main、修复分支与v1.3.1标签已同步回读。GitHub两个附件匿名下载哈希、安装包签名及票据通过;匿名独立标签克隆232文件、fsck和恢复包验证通过。文章15593就地更新为1.3.1,主下载PKG与源码ZIP的线上字节哈希一致,作者/封面/分类/免费及会员规则保留,前台正常和新请求的版本/说明/SEO通过。未登录前台仍显示免费及登录获取入口,未进行真实测试订单。大鱼Release只完成附件上传元数据回读,API令牌访问浏览器下载入口被重定向登录,未宣称该附件字节回读通过;必要PKG本体已随Git提交并在独立克隆中恢复。
## 1.3.2(17)统一产品名称、标识与旧安装迁移
工程、scheme、APP、嵌套插件服务及插件类名统一为 FCP-BeautyGirl / FCPBeautyGirlPlugIn;主 ID `com.dayu.fcpbeautygirl`,插件 ID `com.dayu.fcpbeautygirl.fxplug`。正式签名源码 `767bba8ac11e9bf9268a1e02d1a06d3c120c46aa`,发行标签 v1.3.2 为 `8a70d38b7cd17e0ea530c5226f2be8c2df4308b3`。滤镜 UUID、参数编号和 PGInspectorState 归档未改,渲染源码除名称/日志字符串外与 1.3.1 一致。1901 项自动回归重跑通过;新增 8 项 APP 迁移和 8 项最终 PKG 身份脚本测试,覆盖新装、旧版升级、失败回滚及无关身份保护。原生旧工程重开仍待用户实测,不将静态格式保留等同于原生验收。
通用 Release 使用固定 Developer ID 团队签名,APP 公证 Accepted `559eedf0-048f-4d0e-b013-6a187695f2da`,票据、严格双架构签名、Gatekeeper 和新 ID 的 PluginKit 正式路径通过。旧 ID 经团队和滤镜 UUID 核对后移出注册位置,成功安装后清理旧 APP 备份。唯一正式路径为 `/Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app`;有界目录与现存 Spotlight 产品索引只发现此正式 APP,不声称已扫描全部挂载卷。
PKG 工坊 CLI 基础包包含指定已安装的「大鱼磨皮瘦脸」分类四项文件;加入本产品身份迁移/回滚段后重新签名、公证 Accepted `4a831775-2317-4372-a4cc-102415657ea3` 并粘附票据。最终包 4,889,706 bytes,SHA256 `20a88dacf68cc8812f5af86cfe94c1c0614414a68a019eaeac7ca9d078165ae1`。有效载荷与正式 APP、四项模板一致,模板锁定、更新和失败回滚隔离检查通过;未实际执行本 PKG 安装器。
origin/github 的 main、工作分支和 v1.3.2 标签已推送并回读。匿名独立 GitHub 克隆核对 271 个文件字节/blob/执行位,strict/full fsck、Xcode 工程读取和恢复出的 PKG 签名/票据/Gatekeeper 通过,无 LFS/子模块。GitHub、大鱼 Release 和文章 15593 已更新;GitHub 与文章的包/源码共四项匿名下载哈希一致,正常及新请求前台的版本、安装命令和三项 SEO 核验通过。作者、封面、分类及免费规则保留,未下订单。大鱼 Release 仅回读附件名称和大小,保留登录网关边界。完整回执在 verification/v1.3.2。
## 1.3.2 跟踪闪烁分析(2026-10-22,尚未修复)
用户报告目标稳定而本插件闪烁。新目标2.4.0的静态REA复核发现瘦脸单主要人脸改走 pin-style 筛选与保持,条件可达连续对象框跟踪。现有旧版自动跟随报告不能作为该新版主路径完全对齐的依据。直接链接当前产品源码的合成探针确认:76点完整/缺失/完整使变形通道85835/0/85835;纯框抖动输出/输入振幅比1;先请求40ms空帧、再补0ms有效脸后,缓存40ms仍空,与顺序请求不同;默认外观蒙版在几何保持时可清零。这些均为合成诊断,不是用户素材或目标运行统计。
见 analysis/跟踪闪烁对齐分析.md 和同目录 tracking-flicker-20261022;诊断入口 `zsh scripts/diagnose-tracking.sh`。产品源码与正式1.3.2保持相同,未构建APP、未安装、未重发PKG或文章。已定位当前FCP草帽素材并以项目25fps独立扫描469帧,全部单脸/完整76点、无held/错误,未复现缺点归零;源5.72秒轮廓步幅raw6.33%、稳定后5.24%,仍含真实头动。原生请求与大眼叠加贡献未分离;原1901项回归不代表连续跟踪稳定性已通过。
## 2026-10-23 · 1.3.3 跟踪稳定链实施与数值场
实时检测改为960长边与CGImage重试;加入连续Vision对象框、完整76/五官结构/置信门槛、加权下巴轴保护、中位数/P90跳变筛选与上一组有效形状保持。重复CMTime冻结检测和几何,反向重新开始对象跟踪,检测API错误也进入live保持;保持形状同步四种美容蒙版。fastMulti和显式离线分析保留其独立语义。
瘦脸去掉旧尺寸增益;大眼改为瞳孔中心、眼宽像素圆和0.15强度映射。图像回归57+123+1034、跟踪序列87、宿主适配370 ASan、OSC98 ASan、面板227 ASan、独立眼部/瘦脸数值126,共2122项通过。另一次可选本机目标Metal对照211项、40场景,最大坐标差0.005523像素,见 `analysis/tracking-flicker-20261022/shape-field-oracle.json`。两批存在重叠,不将211再加到2122中。
同一草帽源素材25fps/1920离线检查469帧:完整点/质量筛选均通过,连续框全部可用,held为0。没有原生宿主前后导出对照,不能凭上述检查声称该素材闪烁已解决。FCP当前打开另一项剪辑工程,未改该时间线,也未强退宿主。APP安装与发布将在签名公证及安装包准备完成后继续。
1.3.3(18)通用Release签名源码 `edf64ace6bc17f4db3b2dba71897997809c1029d`。APP公证Accepted `379a432b-ccca-4676-9567-30dc49bb2400`,双架构严格签名和票据验证通过,准备后已注销临时插件注册。最终PKG公证Accepted `3b44edbb-612d-47b2-8b11-e79874b2c635`,签名/票据/Gatekeeper、精确APP payload与四项用户模板资源字节、8项身份迁移和模板隔离测试均通过;未执行真实installer。本机正式安装仍为1.3.2,等待宿主保存退出,不能将准备好的Release说成已安装或原生闪烁验收通过。回执见 `verification/v1.3.3/`。
+2 -2
查看文件
@@ -1,7 +1,7 @@
{
"name": "FCP-BeautyGirl",
"version": "1.3.2",
"build": "17",
"version": "1.3.3",
"build": "18",
"bundleIdentifier": "com.dayu.fcpbeautygirl",
"pluginName": "FCP-BeautyGirl XPC Service.pluginkit",
"pluginBundleIdentifier": "com.dayu.fcpbeautygirl.fxplug",
二进制文件未显示。
+11
查看文件
@@ -0,0 +1,11 @@
# FCP-BeautyGirl 1.3.3(18)
按目标2.4.0的默认pin路径更新实时跟随:960长边检测、图像路径重试、连续Vision对象框、完整点阵/结构/置信筛选、下巴轴翻转及点位跳变保护。异常检测沿可用跟踪框保持上一组有效形状,美容蒙版跟随保持;同帧重绘复用检测/几何,倒退重启对象跟踪。
瘦脸移除旧尺寸放大;大眼改用瞳孔中心、眼宽像素半径和0.15强度映射,替代脸框椭圆近似。保留产品Bundle ID、滤镜UUID、参数编号及旧工程兼容。
包含本机PKG工坊制作的APP与「大鱼磨皮瘦脸」效果模板。免费、无需账号或激活码。签名、公证与包校验记录见同版本验证目录。
验证:合成序列、实际Vision连续框、像素数值场,以及原草帽素材469帧独立检测/跟随采样。静态机制与数值一致不代表原生时间线的闪烁已得到用户确认;宿主中的同段预览/导出、切镜及多人遮挡仍需实际复测。目标APP、metallib、真实人脸点阵和视频不随本仓库分发。
APP与最终PKG均已通过Apple公证并粘附票据;正式包4,905,560字节,SHA-256 `214c109af60bce72046c89ff12b364938b6c00c3a348fa3740dabef661649e0d`。2122项正常回归通过。安装前保存并退出Final Cut Pro和Motion;本次尚未确认原生时间线的闪烁改善。
+44
查看文件
@@ -0,0 +1,44 @@
{
"title": "大鱼人像美化 1.3.3:FCP / Motion 免费开源自动美颜插件",
"status": "publish",
"content": "<!-- wp:paragraph -->\n<p>大鱼人像美化是一款适用于 Final Cut Pro 与 Motion 的免费开源人像插件,把磨皮、美白和脸型调整放进同一块原生卡片面板。插件会识别当前画面中的人脸并自动跟随,无需先点击分析;你可以按素材需要调整强度,拖动滑杆时持续查看效果。</p>\n<!-- /wp:paragraph -->\n<!-- wp:paragraph -->\n<p><strong>代码由 OpenAI + Codex 全程编写,站长仅参与调试和测试。</strong></p>\n<!-- /wp:paragraph -->\n\n<!-- wp:image {\"sizeSlug\":\"full\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"https://dayu.media/wp-content/uploads/2026/10/1792604750-357ca780597c39f.png\" alt=\"大鱼人像美化古风宣传封面,展示自动瘦脸、自动跟随与自动美颜\" /><figcaption class=\"wp-element-caption\">古风宣传插画;面部线条用于示意瘦脸与跟随功能。</figcaption></figure>\n<!-- /wp:image -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">可以做什么</h2>\n<!-- /wp:heading -->\n\n<!-- wp:list {\"className\":\"wp-block-list\"} -->\n<ul class=\"wp-block-list\"><!-- wp:list-item -->\n<li>磨皮:磨皮程度、微细锐化、五官保护和非肤色区保护分别调节;全局磨皮与锐化保留在更多设置。</li>\n<!-- /wp:list-item -->\n\n<!-- wp:list-item -->\n<li>美白:肤色预设、肤色亮度、皮肤色相和牙齿美白。</li>\n<!-- /wp:list-item -->\n\n<!-- wp:list-item -->\n<li>脸型:瘦脸、大眼、短脸、窄脸、下巴、V 脸、下颌骨、颧骨与小头调整。</li>\n<!-- /wp:list-item -->\n\n<!-- wp:list-item -->\n<li>自动跟随:默认跟随主要人脸,也提供多人模式、防跳脸范围与检测框预览。</li>\n<!-- /wp:list-item -->\n\n<!-- wp:list-item -->\n<li>实时预览:滑杆、数值、单项开关和重置集中在同一面板,便于反复对比。</li>\n<!-- /wp:list-item --></ul>\n<!-- /wp:list -->\n\n<!-- wp:paragraph -->\n<p>自动识别负责定位处理区域,效果强度由你控制。建议先从较小的数值开始;多人交叉、严重遮挡和极端侧脸的结果需要结合具体素材检查。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">版本与下载</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>当前版本 <strong>1.3.3(18)</strong>,支持 macOS 13 或更高版本,包含 Apple Silicon 与 Intel 两种架构。下载包包含 FxPlug4 程序和一个配套 FCP 效果模板。APP 与 PKG 均已完成 Developer ID 正式签名、Apple 公证和票据粘附;安装包为所有用户免费资源。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>1.3.3 更新连续人脸框跟踪、异常关键点筛选和坏帧形状保持,同帧重绘复用检测与几何;瘦脸与大眼的局部位移按目标 2.4.0 核对。合成序列、实际 Vision 连续框和同点数值场对照通过,原草帽素材已完成 469 帧独立采样;原生时间线里的闪烁是否改善仍需用同段预览和导出实测,不能把数值一致等同于全部场景稳定。产品名称、应用 ID、滤镜 UUID 和参数编号保持兼容。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">安装</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>安装前先保存工作并退出 Final Cut Pro 和 Motion,安装期间保持关闭。运行本页提供的 PKG,按安装器提示完成安装;它会把程序安装到 <code>/Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app</code>,把配套效果安装到当前登录用户的 Motion Templates 目录,并自动验证和修复系统插件注册。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>本插件免费开源,<strong>无需激活码、付费注册或账号授权</strong>。主 APP 打开时显示说明图,窗口显示三秒后自动退出;宿主中的滤镜由独立服务运行。安装器还会重新锁定两张缩略图,避免被宿主自动替换。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:image {\"sizeSlug\":\"full\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"https://dayu.media/wp-content/uploads/2026/10/1792604743-1c99c2ba53c7614.png\" alt=\"主程序说明页:免费开源,自动注册,无需激活码,启动三秒后自动退出\" /></figure>\n<!-- /wp:image -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">使用</h2>\n<!-- /wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Final Cut Pro:打开<strong>效果</strong>浏览器,找到<strong>大鱼磨皮瘦脸</strong>分类,将<strong>大鱼人像美化</strong>添加到人像片段。Motion:在原生滤镜资源库的<strong>大鱼</strong>分类中选择<strong>大鱼人像美化</strong>。</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>在检查器中按需调节磨皮、美白和脸型。每项左侧的眼睛可暂时停用该项,数值会保留;右侧回转箭头用于重置。人脸会随画面自Line truncated
"excerpt": "1.3.3 对齐连续人脸框、异常点筛选与形状保持,并核对瘦脸和大眼的局部公式。提供免费源码及签名公证安装包,原生时间线仍需结合具体素材实测。",
"seo": {
"title": {
"enabled": true,
"value": "大鱼人像美化 1.3.3|FCP / Motion 免费开源自动美颜插件"
},
"description": {
"enabled": true,
"value": "大鱼人像美化是适用于 Final Cut Pro 和 Motion 的免费开源 FxPlug4 插件。1.3.3 更新连续人脸跟踪、异常点筛选与形状保持,并核对瘦脸及大眼的局部位移。提供 Apple 签名公证安装包和源码,无需激活码,附安装使用说明。"
},
"keywords": {
"enabled": true,
"value": "大鱼人像美化,FCP美颜插件,Motion磨皮,自动瘦脸,自动跟随,FxPlug4,免费开源"
}
},
"resource": {
"cao_info": [
{
"title": "版本",
"desc": "1.3.3(18)"
},
{
"title": "宿主",
"desc": "Final Cut Pro / Motion"
},
{
"title": "系统",
"desc": "macOS 13 或更高版本"
},
{
"title": "架构",
"desc": "Apple Silicon / Intel"
},
{
"title": "授权",
"desc": "免费开源,无需激活码"
}
]
}
}
+8
查看文件
@@ -9,7 +9,10 @@ parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--wait-for-hosts',action='store_true',help='After notarization, retain the delivery lock and wait for Motion/FCP to exit naturally before installing; never control or quit either host.')
parser.add_argument('--user-tests',action='store_true',help='After non-UI installed-app verification, record native effect QA as pending user testing and clean known generated copies without the interactive validation prompt.')
parser.add_argument('--resume-prepared',action='store_true',help='Resume an already Accepted and stapled staged app after checking its recorded signature, version and unchanged product source.')
parser.add_argument('--prepare-only',action='store_true',help='Build, sign, notarize and staple a staged Release, then release the delivery lock without replacing the installed APP or touching running hosts.')
args=parser.parse_args()
class PreparedOnly(Exception):
pass
bundle_id=PRODUCT['bundleIdentifier']
locks=delivery_locks();locks.__enter__()
staging=pathlib.Path('/tmp/fcp-beautygirl-release')
@@ -133,6 +136,9 @@ try:
# old installed version open. This removes only this known staging path.
unregister_generated_plugin(plugin)
save_receipt('prepared',signingVerified=True,stapleValidated=True,stagingPluginUnregistered=True)
if args.prepare_only:
print(json.dumps({'status':'prepared','path':str(app),'notarization':result,'installedAppUnchanged':True}),flush=True)
raise PreparedOnly()
# A live host retains its old XPC/OSC connection. Replacing the bundle while
# that connection exists can deadlock host shutdown or select stale code.
await_host_exit()
@@ -184,6 +190,8 @@ try:
removed=migration.cleanup()+cleanup_known_products()
save_receipt('installed_user_qa_pending' if args.user_tests else 'complete',removedGeneratedPaths=removed)
print('Delivery staging and notarization archive cleaned.',flush=True)
except PreparedOnly:
pass
except BaseException as error:
save_receipt('failed',failure={'type':type(error).__name__,'message':str(error)})
raise
+87
查看文件
@@ -0,0 +1,87 @@
#import <Foundation/Foundation.h>
#import <CoreImage/CoreImage.h>
#import "PGFaceProcessor.h"
#include <math.h>
@interface PGRealtimeFaceTracker : NSObject
- (NSDictionary *)resultForGeometry:(NSArray *)raw settings:(PGBeautySettings)s time:(CMTime)time extent:(CGRect)extent;
@end
@interface PGFaceProcessor (ProbeWarp)
- (CIImage *)warpImage:(CIImage *)image geometry:(NSArray *)geometry settings:(PGBeautySettings)s extent:(CGRect)extent;
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)s error:(NSError **)error;
@end
@interface ProbeFaceProcessor : PGFaceProcessor
@property (nonatomic,copy) NSArray<VNFaceObservation *> *authoredDetections;
@end
@implementation ProbeFaceProcessor
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)s error:(NSError **)error {return self.authoredDetections?:@[];}
@end
#import "../tests/tracking_fixture.h"
static NSDictionary *Geometry(CGRect box,double jitter,BOOL has76) {
NSMutableDictionary *g=[PGTrackingTestGeometry(box,jitter) mutableCopy];
if(!has76)[g removeObjectForKey:@"points76"];
return g;
}
static NSDictionary *Live(PGRealtimeFaceTracker *t,NSArray *g,PGBeautySettings s,int64_t tick){return[t resultForGeometry:g settings:s time:CMTimeMake(tick,1000) extent:CGRectMake(0,0,256,256)];}
static double PointX(NSDictionary *r){return[r[@"output"][0][@"points76"][69][0] doubleValue];}
static NSData *Pixels(CIContext *context,CIImage *image){
CGRect e=image.extent;NSUInteger w=e.size.width,h=e.size.height;NSMutableData *d=[NSMutableData dataWithLength:w*h*4*sizeof(float)];
[context render:image toBitmap:d.mutableBytes rowBytes:w*4*sizeof(float) bounds:e format:kCIFormatRGBAf colorSpace:NULL];return d;
}
static NSDictionary *Difference(NSData *a,NSData *b){
const float *x=a.bytes,*y=b.bytes;NSUInteger n=a.length/sizeof(float),changed=0;double maximum=0,sum=0;
for(NSUInteger i=0;i<n;i++){double v=fabs(x[i]-y[i]);sum+=v;maximum=fmax(maximum,v);if(v>1e-6)changed++;}
return@{@"maxChannelDifference":@(maximum),@"meanChannelDifference":@(sum/n),@"changedChannelsAbove1e_6":@(changed)};
}
static double MeanRed(CIContext *context,CIImage *image){NSData *d=Pixels(context,image);const float *p=d.bytes;NSUInteger n=d.length/(4*sizeof(float));double sum=0;for(NSUInteger i=0;i<n;i++)sum+=p[4*i];return sum/n;}
static CIImage *SyntheticPattern(void){
NSMutableData *d=[NSMutableData dataWithLength:256*256*4*sizeof(float)];float *p=d.mutableBytes;
for(NSUInteger y=0;y<256;y++)for(NSUInteger x=0;x<256;x++){NSUInteger i=4*(y*256+x);p[i]=x/255.;p[i+1]=y/255.;p[i+2]=((x/5+y/5)%2)*.9+.05;p[i+3]=1;}
return[CIImage imageWithBitmapData:d bytesPerRow:256*4*sizeof(float) size:CGSizeMake(256,256) format:kCIFormatRGBAf colorSpace:NULL];
}
int main(void){@autoreleasepool{
PGBeautySettings s=PGBeautyDefaults();s.faceThin=1;
CGRect b=CGRectMake(.15,.15,.7,.65);NSDictionary *good=Geometry(b,0,YES),*missing=Geometry(b,0,NO);
PGRealtimeFaceTracker *topology=[PGRealtimeFaceTracker new];NSMutableArray *counts=[NSMutableArray new],*held=[NSMutableArray new],*diffs=[NSMutableArray new];
PGFaceProcessor *processor=[PGFaceProcessor new];CIContext *context=[CIContext contextWithOptions:nil];CIImage *image=SyntheticPattern();NSData *original=Pixels(context,image);
NSArray *inputs=@[@[good],@[missing],@[good]];
for(NSUInteger i=0;i<inputs.count;i++){
NSDictionary *r=Live(topology,inputs[i],s,i*40);NSArray *output=r[@"output"];
[counts addObject:@([output[0][@"points76"] count])];[held addObject:r[@"held"]];
CIImage *warped=[processor warpImage:image geometry:output settings:s extent:image.extent];
[diffs addObject:Difference(original,Pixels(context,warped))];
}
NSMutableArray *rawX=[NSMutableArray new],*outX=[NSMutableArray new];PGRealtimeFaceTracker *jitter=[PGRealtimeFaceTracker new];
for(NSUInteger i=0;i<20;i++){
double dx=(i%2?.004:-.004);CGRect moved=b;moved.origin.x+=dx;NSDictionary *g=Geometry(moved,0,YES),*r=Live(jitter,@[g],s,i*40);
[rawX addObject:g[@"points76"][69][0]];[outX addObject:@(PointX(r))];
}
double rawAmplitude=0,outAmplitude=0,maxError=0;for(NSUInteger i=1;i<rawX.count;i++){rawAmplitude=fmax(rawAmplitude,fabs([rawX[i] doubleValue]-[rawX[i-1] doubleValue]));outAmplitude=fmax(outAmplitude,fabs([outX[i] doubleValue]-[outX[i-1] doubleValue]));maxError=fmax(maxError,fabs([rawX[i] doubleValue]-[outX[i] doubleValue]));}
PGRealtimeFaceTracker *late=[PGRealtimeFaceTracker new];NSDictionary *lateFirst=Live(late,@[],s,40);Live(late,@[good],s,0);NSDictionary *lateReplay=Live(late,@[],s,40);
PGRealtimeFaceTracker *ordered=[PGRealtimeFaceTracker new];Live(ordered,@[good],s,0);NSDictionary *ordered40=Live(ordered,@[],s,40);
PGRealtimeFaceTracker *reacquired=[PGRealtimeFaceTracker new];Live(reacquired,@[good],s,0);Live(reacquired,@[],s,40);NSDictionary *shift=Geometry(b,.04,YES),*r80=Live(reacquired,@[shift],s,80);
PGRealtimeFaceTracker *continuous=[PGRealtimeFaceTracker new];Live(continuous,@[good],s,0);NSDictionary *continuous80=Live(continuous,@[shift],s,80);
NSMutableArray *weights=[NSMutableArray new];for(NSNumber *fps in @[@25,@30,@60,@120]){
PGRealtimeFaceTracker *t=[PGRealtimeFaceTracker new];Live(t,@[good],s,0);
NSDictionary *r=[t resultForGeometry:@[shift] settings:s time:CMTimeMake(1,fps.intValue) extent:CGRectMake(0,0,256,256)];
double before=[good[@"points76"][69][0] doubleValue],after=[shift[@"points76"][69][0] doubleValue];
[weights addObject:@{@"fps":fps,@"currentLandmarkWeight":@((PointX(r)-before)/(after-before))}];
}
ProbeFaceProcessor *drop=[ProbeFaceProcessor new];PGBeautySettings defaultSettings=PGBeautyDefaults();
drop.authoredDetections=@[[VNFaceObservation faceObservationWithRequestRevision:VNDetectFaceLandmarksRequestRevision3 boundingBox:b roll:nil yaw:nil pitch:nil]];
PGFaceFrame *detected=[drop processImage:image settings:defaultSettings time:kCMTimeZero error:nil];
drop.authoredDetections=@[];PGFaceFrame *lost=[drop processImage:image settings:defaultSettings time:CMTimeMake(1,25) error:nil];
PGRealtimeFaceTracker *crossing=[PGRealtimeFaceTracker new];NSDictionary *initialA=Geometry(CGRectMake(.2,.25,.25,.5),0,YES);
Live(crossing,@[initialA],s,0);NSDictionary *movingA=Geometry(CGRectMake(.30,.25,.25,.5),.04,YES),*strangerB=Geometry(CGRectMake(.21,.25,.25,.5),-.04,YES);
NSDictionary *crossed=Live(crossing,@[movingA,strangerB],s,40);
NSDictionary *report=@{@"scope":@"Authored synthetic geometry and image only; actual current PGFaceProcessor implementation linked; no target binary or user media.",
@"topologyValidMissingValid":@{@"points76Counts":counts,@"held":held,@"pixelDifferencesFromUnwarpedInput":diffs},
@"pureBBoxTranslationJitter":@{@"frames":@20,@"fps":@25,@"rawStepNormalized":@(rawAmplitude),@"outputStepNormalized":@(outAmplitude),@"outputOverRawAmplitude":@(outAmplitude/rawAmplitude),@"maximumOutputVsRawPointError":@(maxError)},
@"outOfOrderCachedMissingFrame":@{@"initial40OutputFaces":@([lateFirst[@"output"] count]),@"replay40AfterAdding0OutputFaces":@([lateReplay[@"output"] count]),@"ordered0Then40OutputFaces":@([ordered40[@"output"] count])},
@"reacquisition":@{@"reacquiredPoint69x":@(PointX(r80)),@"rawPoint69x":shift[@"points76"][69][0],@"continuousPoint69x":@(PointX(continuous80))},
@"defaultAppearanceOnDetectionLoss":@{@"geometryFacesBefore":@(detected.deformationGeometry.count),@"geometryFacesAfter":@(lost.deformationGeometry.count),@"meanColorMaskBefore":@(MeanRed(context,detected.colorMask)),@"meanColorMaskAfter":@(MeanRed(context,lost.colorMask)),@"meanSkinMaskBefore":@(MeanRed(context,detected.skinMask)),@"meanSkinMaskAfter":@(MeanRed(context,lost.skinMask))},
@"authoredTwoFaceCrossing":@{@"rawIndex0":@"same authored identity A moved by .10",@"rawIndex1":@"different authored identity B moved within .01 of old center",@"selectedRawIndex":crossed[@"index"]},
@"landmarkWeightsByFrameRate":weights};
NSData *json=[NSJSONSerialization dataWithJSONObject:report options:NSJSONWritingPrettyPrinted|NSJSONWritingSortedKeys error:nil];fwrite(json.bytes,1,json.length,stdout);fputc('\n',stdout);
}return 0;}
+14
查看文件
@@ -0,0 +1,14 @@
#!/bin/zsh
# Diagnostic statistics for authored inputs; a zero exit is not a stability pass.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
WORK="$ROOT/build/tracking-diagnostics"
mkdir -p "$WORK"
xcrun clang -fobjc-arc -fmodules -Wall -Wextra -Wno-unused-parameter \
-DCI_SILENCE_GL_DEPRECATION -mmacosx-version-min=13.0 \
-I "$ROOT/FCP-BeautyGirl/FCP-BeautyGirl/Plugin" \
-framework Foundation -framework CoreImage -framework CoreGraphics \
-framework CoreMedia -framework Metal -framework Vision -framework ImageIO \
"$ROOT/FCP-BeautyGirl/FCP-BeautyGirl/Plugin/PGFaceProcessor.m" \
"$ROOT/scripts/diagnose-tracking.m" -o "$WORK/tracking-probe"
"$WORK/tracking-probe"
+109
查看文件
@@ -0,0 +1,109 @@
// Offline diagnostics: actual detector/tracker, scalar statistics only.
// No image, full landmark array, host edit, or reference execution is produced.
#import <AVFoundation/AVFoundation.h>
#import "../FCP-BeautyGirl/FCP-BeautyGirl/Plugin/PGFaceProcessor.m"
static double ShapeRMS(NSDictionary *a, NSDictionary *b, NSUInteger first, NSUInteger last) {
NSArray *x=a[@"points76"], *y=b[@"points76"];
if(x.count!=76 || y.count!=76) return NAN;
CGRect old=PGArrayRect(a[@"box"]), current=PGArrayRect(b[@"box"]);
if(!PGValidRect(old)||!PGValidRect(current)) return NAN;
double sum=0;
for(NSUInteger i=first;i<=last;i++) {
CGPoint p=PGRemapPoint(x[i],old,current);
double dx=(p.x-[y[i][0] doubleValue])/current.size.width;
double dy=(p.y-[y[i][1] doubleValue])/current.size.height;
sum+=dx*dx+dy*dy;
}
return sqrt(sum/(last-first+1));
}
static id NumberOrNull(double x) {return isfinite(x)?@(x):NSNull.null;}
static double EyeRMS(NSDictionary *a, NSDictionary *b) {
CGRect old=PGArrayRect(a[@"box"]), current=PGArrayRect(b[@"box"]);
if(!PGValidRect(old)||!PGValidRect(current))return NAN;
double sum=0;NSUInteger n=0;
for(NSString *key in @[@"leftEye",@"rightEye"]) {
NSArray *x=a[key],*y=b[key];if(!x.count||x.count!=y.count)return NAN;
for(NSUInteger i=0;i<x.count;i++) {
CGPoint p=PGRemapPoint(x[i],old,current);
double dx=(p.x-[y[i][0] doubleValue])/current.size.width;
double dy=(p.y-[y[i][1] doubleValue])/current.size.height;
sum+=dx*dx+dy*dy;n++;
}
}
return n?sqrt(sum/n):NAN;
}
int main(int argc,const char *argv[]) {@autoreleasepool {
if(argc!=6) {fprintf(stderr,"usage: inspect-tracking-sequence <video> <output.json> <fps> <decode-longest> <timeline-offset-seconds>\n");return 2;}
NSString *path=@(argv[1]), *output=@(argv[2]);
int fps=atoi(argv[3]), longest=atoi(argv[4]);double offset=atof(argv[5]);
if(fps<1||fps>120||longest<128||longest>4096||!isfinite(offset))return 2;
AVURLAsset *asset=[AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:nil];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
double duration=CMTimeGetSeconds(asset.duration);
AVAssetTrack *video=[[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
if(!video||!isfinite(duration)||duration<=0)return 2;
double nominal=video.nominalFrameRate;
AVAssetImageGenerator *generator=[[AVAssetImageGenerator alloc] initWithAsset:asset];
generator.appliesPreferredTrackTransform=YES;generator.maximumSize=CGSizeMake(longest,longest);
generator.requestedTimeToleranceBefore=kCMTimeZero;generator.requestedTimeToleranceAfter=kCMTimeZero;
PGFaceProcessor *detector=[PGFaceProcessor new];PGRealtimeFaceTracker *tracker=[PGRealtimeFaceTracker new];
PGBeautySettings settings=PGBeautyDefaults();settings.singleFace=0;settings.trackingEnabled=0;
settings.fastMultiFace=0;settings.faceThin=1;settings.faceBigEye=.9035486629353234;
NSMutableArray *rows=[NSMutableArray new];NSDictionary *priorRaw=nil,*priorOutput=nil;
NSUInteger empty=0,partial=0,held=0,low=0,errors=0,multiple=0,invalidOutput=0,duplicateSourceFrames=0;
CMTime previousActual=kCMTimeInvalid;NSUInteger width=0,height=0;
for(int frame=0;frame<(int)ceil(duration*fps);frame++) {@autoreleasepool {
double requested=(double)frame/fps;if(requested>=duration)break;
NSError *error=nil;CMTime actual=kCMTimeInvalid;
CGImageRef cg=[generator copyCGImageAtTime:CMTimeMakeWithSeconds(requested,60000) actualTime:&actual error:&error];
if(!cg){errors++;fprintf(stderr,"decode failed at %.3f: %s\n",requested,error.localizedDescription.UTF8String);continue;}
if(CMTIME_IS_VALID(previousActual)&&CMTimeCompare(previousActual,actual)==0)duplicateSourceFrames++;
previousActual=actual;width=CGImageGetWidth(cg);height=CGImageGetHeight(cg);
CIImage *image=[CIImage imageWithCGImage:cg];
NSArray<VNFaceObservation *> *observations=[detector observationsInImage:image settings:settings error:&error];
if(!observations)errors++;
NSMutableArray *raw=[NSMutableArray new],*faces=[NSMutableArray new];
for(VNFaceObservation *face in observations)if(PGValidRect(face.boundingBox)&&raw.count<16){[raw addObject:PGGeometry(face)];[faces addObject:face];}
CMTime time=CMTimeAdd(CMTimeMake(frame,fps),CMTimeMakeWithSeconds(offset,60000));
NSDictionary *r=[tracker resultForGeometry:raw settings:settings time:time extent:image.extent image:image];
NSInteger index=[r[@"index"] integerValue];NSDictionary *current=(index>=0&&(NSUInteger)index<raw.count)?raw[index]:nil;
VNFaceObservation *face=(index>=0&&(NSUInteger)index<faces.count)?faces[index]:nil;
NSDictionary *stable=[r[@"output"] firstObject];
double confidence=face.landmarks?face.landmarks.confidence:NAN;
if(!raw.count)empty++;if(raw.count>1)multiple++;
if(current&&[current[@"points76"] count]!=76)partial++;
if(stable&&[stable[@"points76"] count]!=76)invalidOutput++;
if([r[@"held"] boolValue])held++;
if(isfinite(confidence)&&confidence<.55)low++;
CGRect old=PGArrayRect(priorRaw[@"box"]),box=PGArrayRect(current[@"box"]);
double centerStep=NAN,sizeStep=NAN;
if(PGValidRect(old)&&PGValidRect(box)){
centerStep=hypot(CGRectGetMidX(old)-CGRectGetMidX(box),CGRectGetMidY(old)-CGRectGetMidY(box));
sizeStep=MAX(fabs(box.size.width/old.size.width-1),fabs(box.size.height/old.size.height-1));
}
NSMutableDictionary *row=[@{@"frame":@(frame),@"requestedSeconds":@(requested),@"actualSeconds":@(CMTimeGetSeconds(actual)),
@"faceCount":@(raw.count),@"points76Count":@([current[@"points76"] count]),@"held":r[@"held"],
@"qualityReason":current?PGGeometryQualityReason(current):@"missingFace",@"resultReason":r[@"reason"]?:@"",@"trackedBoxAvailable":@([r[@"trackedBox"] count]==4),@"landmarkConfidence":NumberOrNull(confidence),@"faceConfidence":NumberOrNull(face?face.confidence:NAN),
@"rawContourShapeStep":NumberOrNull(ShapeRMS(priorRaw,current,59,75)),
@"outputContourShapeStep":NumberOrNull(ShapeRMS(priorOutput,stable,59,75)),
@"rawAllShapeStep":NumberOrNull(ShapeRMS(priorRaw,current,0,75)),
@"outputAllShapeStep":NumberOrNull(ShapeRMS(priorOutput,stable,0,75)),
@"rawEyeShapeStep":NumberOrNull(EyeRMS(priorRaw,current)),
@"bboxCenterStep":NumberOrNull(centerStep),@"bboxRelativeSizeStep":NumberOrNull(sizeStep)} mutableCopy];
[rows addObject:row];priorRaw=current;priorOutput=stable;CGImageRelease(cg);
if(frame%50==0)fprintf(stderr,"inspected %d/%d frames\n",frame,(int)ceil(duration*fps));
}}
#pragma clang diagnostic pop
NSArray *keys=@[@"rawContourShapeStep",@"outputContourShapeStep",@"rawAllShapeStep",@"outputAllShapeStep",@"rawEyeShapeStep",@"bboxCenterStep",@"bboxRelativeSizeStep",@"landmarkConfidence"];
NSMutableDictionary *stats=[NSMutableDictionary new];
for(NSString *key in keys){NSMutableArray *values=[NSMutableArray new];for(NSDictionary *row in rows)if([row[key] isKindOfClass:NSNumber.class])[values addObject:row[key]];[values sortUsingSelector:@selector(compare:)];if(values.count){stats[key]=@{@"n":@(values.count),@"min":values.firstObject,@"median":values[values.count/2],@"p95":values[(NSUInteger)floor((values.count-1)*.95)],@"max":values.lastObject};}}
NSArray *ranked=[rows sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *a,NSDictionary *b){double x=[a[@"rawContourShapeStep"] isKindOfClass:NSNumber.class]?[a[@"rawContourShapeStep"] doubleValue]:-1;double y=[b[@"rawContourShapeStep"] isKindOfClass:NSNumber.class]?[b[@"rawContourShapeStep"] doubleValue]:-1;return x>y?NSOrderedAscending:(x<y?NSOrderedDescending:NSOrderedSame);}];
NSDictionary *summary=@{@"sourceBasename":path.lastPathComponent,@"durationSeconds":@(duration),@"nominalSourceFPS":@(nominal),@"samplingFPS":@(fps),@"decodedSize":@[@(width),@(height)],@"timelineOffsetSeconds":@(offset),@"frames":@(rows.count),@"emptyDetectionFrames":@(empty),@"partial76Frames":@(partial),@"heldFrames":@(held),@"outputWithout76Frames":@(invalidOutput),@"landmarkConfidenceBelow055Frames":@(low),@"multipleFaceFrames":@(multiple),@"decodeOrVisionErrors":@(errors),@"duplicateSourceFramePairs":@(duplicateSourceFrames),@"statistics":stats,@"largestContourChanges":[ranked subarrayWithRange:NSMakeRange(0,MIN(12,ranked.count))],@"scope":@"Independent offline decoding and the current production detector/tracker; not FCP render logs or target execution. Shape steps remove previous bbox translation/scale, but still include actual pose/expression changes."};
NSDictionary *report=@{@"summary":summary,@"scalarFrames":rows};NSData *data=[NSJSONSerialization dataWithJSONObject:report options:NSJSONWritingPrettyPrinted|NSJSONWritingSortedKeys error:nil];
if(!data||![data writeToFile:output atomically:YES])return 1;
NSData *print=[NSJSONSerialization dataWithJSONObject:summary options:NSJSONWritingPrettyPrinted|NSJSONWritingSortedKeys error:nil];fwrite(print.bytes,1,print.length,stdout);fputc('\n',stdout);
return 0;
}}
+32 -6
查看文件
@@ -23,19 +23,45 @@ cli = '/Applications/PKG工坊.app/Contents/MacOS/PKG工坊'
source_template = pathlib.Path('/Users/dayu/Movies/Motion Templates.localized/Effects.localized/大鱼磨皮瘦脸')
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verify-existing', action='store_true', help='Resume verification of the already Accepted, stapled final package without rebuilding or resubmitting it.')
parser.add_argument('--prepared-app', action='store_true', help='Package the exact Accepted/stapled delivery staging APP before host exit, keeping the existing formal installation untouched.')
args = parser.parse_args()
def package_app():
formal = pathlib.Path(PRODUCT['installationPath'])
receipt = json.loads((ROOT / 'build/delivery/delivery-receipt.json').read_text())
hashes = lambda text: [line for line in text.splitlines() if line.startswith('CDHash=')]
source = receipt['source']['commit']
if run(['git', 'diff', source, '--', PRODUCT['sourceRoot'], 'product.json']).strip():
raise RuntimeError('Product source changed since the signed delivery')
if not args.prepared_app:
if receipt.get('status') not in ('installed', 'installed_user_qa_pending', 'complete'):
raise RuntimeError('The recorded delivery is not installed; use --prepared-app for a prepared Release')
signature = subprocess.run(['codesign', '-dvvv', str(formal)], check=True, text=True, capture_output=True).stderr
if not hashes(signature) or hashes(signature) != hashes(receipt.get('installedSignature', '')):
raise RuntimeError('Installed APP signature differs from the recorded delivery source')
return formal
app = pathlib.Path('/tmp/fcp-beautygirl-release/Build/Products/Release') / (PRODUCT['name'] + '.app')
if receipt.get('status') != 'prepared' or receipt.get('notarization', {}).get('status') != 'Accepted':
raise RuntimeError('Prepared delivery is not Accepted and ready')
if receipt.get('version') != version or receipt.get('build') != PRODUCT['build']:
raise RuntimeError('Prepared delivery version mismatch')
signature = subprocess.run(['codesign', '-dvvv', str(app)], check=True, text=True, capture_output=True).stderr
if not hashes(signature) or hashes(signature) != hashes(receipt['stagingSignature']):
raise RuntimeError('Prepared APP signature differs from the Accepted delivery')
run(['xcrun', 'stapler', 'validate', str(app)])
return app
with delivery_locks():
if not args.verify_existing:
if work.exists() or final.exists():
raise SystemExit('Packaging work or final package already exists; inspect before retrying')
work.mkdir(parents=True)
release.mkdir(parents=True, exist_ok=True)
proof.mkdir(parents=True, exist_ok=True)
formal = pathlib.Path(PRODUCT['installationPath'])
formal = package_app()
info = validate_app(formal, PRODUCT['bundleIdentifier'])
assert info['CFBundleShortVersionString'] == version and info['CFBundleVersion'] == PRODUCT['build']
assert installer_identity in run(['security', 'find-identity', '-v', '-p', 'basic', keychain])
work.mkdir(parents=True)
release.mkdir(parents=True, exist_ok=True)
proof.mkdir(parents=True, exist_ok=True)
template = work / 'input/大鱼磨皮瘦脸'
manifest = []
for source in sorted(source_template.rglob('*')):
@@ -76,7 +102,7 @@ with delivery_locks():
else:
if not work.exists() or not final.exists():
raise RuntimeError('No prepared package to verify')
formal = pathlib.Path(PRODUCT['installationPath'])
formal = package_app()
info = validate_app(formal, PRODUCT['bundleIdentifier'])
assert info['CFBundleShortVersionString'] == version and info['CFBundleVersion'] == PRODUCT['build']
result = json.loads((proof / 'pkg-notarization.json').read_text())
@@ -90,7 +116,7 @@ with delivery_locks():
full = work / 'full'
try:
run(['pkgutil', '--expand-full', str(final), str(full)])
run([sys.executable, 'scripts/verify-release-package.py', '--version', version, '--build', PRODUCT['build'], '--expanded', str(full), '--fixture', 'build/pkg-isolation-' + version], proof / 'pkg-verification-output.txt')
run([sys.executable, 'scripts/verify-release-package.py', '--version', version, '--build', PRODUCT['build'], '--expanded', str(full), '--fixture', 'build/pkg-isolation-' + version, '--source-app', str(formal)], proof / 'pkg-verification-output.txt')
env = dict(os.environ, FCP_BEAUTYGIRL_POSTINSTALL=str(full / 'Payload.pkg/Scripts/postinstall'))
run([sys.executable, 'tests/installer_identity_tests.py'], proof / 'installer-tests.txt', env=env)
finally:
可执行文件
+15
查看文件
@@ -0,0 +1,15 @@
#!/bin/zsh
set -euo pipefail
cd "${0:A:h:h}"
eye_test_output=$(mktemp -d /tmp/fcp-beautygirl-eye-tests.XXXXXX)
cleanup_eye_tests() {
python3 -c 'import shutil,sys; shutil.rmtree(sys.argv[1])' "$eye_test_output"
}
trap cleanup_eye_tests EXIT
plugin_dir="$PWD/FCP-BeautyGirl/FCP-BeautyGirl/Plugin"
xcrun clang -fobjc-arc -fmodules -Wall -Wextra -Wno-unused-parameter \
-DCI_SILENCE_GL_DEPRECATION -mmacosx-version-min=13.0 -I "$plugin_dir" \
-framework Foundation -framework CoreImage -framework CoreGraphics \
-framework CoreMedia -framework Metal -framework Vision -framework ImageIO \
"$plugin_dir/PGFaceProcessor.m" tests/eyes_tests.m -o "$eye_test_output/eye-tests"
"$eye_test_output/eye-tests" "$@"
+12
查看文件
@@ -0,0 +1,12 @@
#!/bin/zsh
set -euo pipefail
cd "${0:A:h:h}"
output_dir=$(mktemp -d /tmp/fcp-beautygirl-tracking-tests.XXXXXX)
trap 'rm -rf "$output_dir"' EXIT
# tracking_tests.m imports the implementation; do not link it a second time.
xcrun clang -fobjc-arc -fmodules -Wall -Wextra -Wno-unused-parameter \
-DCI_SILENCE_GL_DEPRECATION -mmacosx-version-min=13.0 \
-framework Foundation -framework CoreImage -framework CoreGraphics \
-framework CoreMedia -framework Metal -framework Vision -framework ImageIO \
tests/tracking_tests.m -o "$output_dir/tracking-tests"
"$output_dir/tracking-tests"
+2
查看文件
@@ -11,3 +11,5 @@ xcrun clang "${common[@]}" "$plugin_dir/PGFaceProcessor.m" tests/face_tests.m -o
"$output_dir/face-tests" "$PWD/tests/fixtures/astronaut.png"
xcrun clang "${common[@]}" "$plugin_dir/PGFaceProcessor.m" tests/slim_tests.m -o "$output_dir/slim-tests"
"$output_dir/slim-tests" "$PWD/tests/fixtures/astronaut.png"
zsh scripts/test-tracking.sh
zsh scripts/test-eyes.sh
+4 -3
查看文件
@@ -9,13 +9,14 @@ parser.add_argument('--version',default=PRODUCT['version'])
parser.add_argument('--build',default=PRODUCT['build'])
parser.add_argument('--expanded',default='build/pkg-expanded')
parser.add_argument('--fixture',default='build/pkg-isolation')
parser.add_argument('--source-app',default=PRODUCT['installationPath'],help='Signed/stapled APP to compare byte for byte against the payload; may be the prepared delivery staging APP.')
args=parser.parse_args()
expanded=root/args.expanded/'Payload.pkg';payload=expanded/'Payload'
formal=Path('/Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app');packed=payload/'Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app'
formal=Path(args.source_app);packed=payload/'Library/Plug-Ins/FxPlug/FCP-BeautyGirl.app'
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
def files(p):
return {str(x.relative_to(p)):('symlink:'+os.readlink(x) if x.is_symlink() else sha(x)) for x in p.rglob('*') if x.is_file() or x.is_symlink()}
assert files(formal)==files(packed),'APP differs from signed installed product'
assert files(formal)==files(packed),'APP differs from the verified signed source product'
subprocess.run(['codesign','--verify','--deep','--strict',str(packed)],check=True)
subprocess.run(['xcrun','stapler','validate',str(packed)],check=True,capture_output=True)
source=root/'Templates/大鱼人像美化';template=next(payload.rglob('大鱼人像美化.moef')).parent
@@ -73,7 +74,7 @@ assert backups and all('/Movies/' not in str(x) for x in backups)
package=root/('releases/'+args.version+'/'+PRODUCT['name']+'-'+args.version+'.pkg')
info=plistlib.loads((packed/'Contents/Info.plist').read_bytes())
assert info['CFBundleShortVersionString']==args.version and info['CFBundleVersion']==args.build
report={'package':str(package.relative_to(root)),'bytes':package.stat().st_size,'sha256':sha(package),'version':args.version,'build':args.build,'app_files_compared':len(files(formal)),'app_byte_and_symlink_equality':True,'template_files':{n:sha(template/n) for n in runtime},'locked_relative_paths':['大鱼人像美化/large.png','大鱼人像美化/small.png'],'script_sha256':{'preinstall':sha(expanded/'Scripts/preinstall'),'postinstall':sha(expanded/'Scripts/postinstall')},'isolated_script_tests':['fresh install','replace old locked thumbnail','partial copy failure restores old bytes and locks'],'isolation_changes':['Console identity/home redirected to task fixture','chown replaced with true','FxPlug verification skipped; formal app signature and discovery verified separately'],'real_installer_execution':False,'app_and_template_installed':'APP installed through signed delivery workflow; formal template files matched the specified installed category, not changed by running this PKG','fonts':'No editable third-party font dependency; promotional lettering baked into PNG. No optional font ZIP needed.'}
report={'package':str(package.relative_to(root)),'bytes':package.stat().st_size,'sha256':sha(package),'version':args.version,'build':args.build,'app_files_compared':len(files(formal)),'app_byte_and_symlink_equality':True,'template_files':{n:sha(template/n) for n in runtime},'locked_relative_paths':['大鱼人像美化/large.png','大鱼人像美化/small.png'],'script_sha256':{'preinstall':sha(expanded/'Scripts/preinstall'),'postinstall':sha(expanded/'Scripts/postinstall')},'isolated_script_tests':['fresh install','replace old locked thumbnail','partial copy failure restores old bytes and locks'],'isolation_changes':['Console identity/home redirected to task fixture','chown replaced with true','FxPlug verification skipped; formal app signature and discovery verified separately'],'real_installer_execution':False,'app_and_template_installed':('APP installed through signed delivery workflow' if formal==Path(PRODUCT['installationPath']) else 'Prepared signed APP verified; formal APP installation pending host exit')+'; template files matched the specified installed category, not changed by running this PKG','fonts':'No editable third-party font dependency; promotional lettering baked into PNG. No optional font ZIP needed.'}
(root/('docs/verification/v'+args.version+'/pkg-verification.json')).write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n');print(json.dumps(report,ensure_ascii=False,indent=2))
# This fixture belongs solely to this verifier; remove its locks before cleanup.
subprocess.run(['chflags','-R','nouchg',str(base)],check=True);shutil.rmtree(base)
+237
查看文件
@@ -0,0 +1,237 @@
#import <Foundation/Foundation.h>
#import <CoreImage/CoreImage.h>
#import <Metal/Metal.h>
#include <simd/simd.h>
#include <math.h>
#import "PGFaceProcessor.h"
#import "tracking_fixture.h"
@interface PGFaceProcessor (EyeTest)
- (CIImage *)warpImage:(CIImage *)image geometry:(NSArray<NSDictionary *> *)geometry settings:(PGBeautySettings)s extent:(CGRect)extent;
@end
static NSUInteger checks;
static double maximumCIError,maximumTargetError;
static NSMutableArray *caseReports;
static void Check(BOOL value,NSString *message) {
checks++;if(!value){fprintf(stderr,"FAIL: %s\n",message.UTF8String);exit(1);}
}
static double Clamp(double x,double a,double b){return fmin(fmax(x,a),b);}
static CGPoint ReadEyePoint(NSArray *a){return CGPointMake([a[0] doubleValue],[a[1] doubleValue]);}
static NSArray *Pair(CGPoint p){return @[@(p.x),@(p.y)];}
static NSDictionary *Geometry(double roll,double scale) {
NSMutableDictionary *g=[PGTrackingTestGeometry(CGRectMake(.12,.10,.76,.78),0) mutableCopy];
NSMutableArray *points=[g[@"points76"] mutableCopy];
for(NSUInteger i=0;i<76;i++) {
CGPoint p=ReadEyePoint(points[i]);double x=(p.x-.5)*scale,y=(p.y-.5)*scale;
points[i]=Pair(CGPointMake(.5+x*cos(roll)-y*sin(roll),.5+x*sin(roll)+y*cos(roll)));
}
g[@"points76"]=points;return g;
}
// Independent double-precision scalar specification from the recorded 2.4.0
// AIR. It does not call the production kernel or production parameter tables.
// Thin uses normalized Y-down distances, while eyes use circular pixel support.
static CGPoint Oracle(CGPoint destination,NSDictionary *g,CGRect e,double thin,double eye,BOOL reverseEyes) {
static const int origin[17]={75,59,74,60,73,61,72,62,71,63,70,64,69,65,68,66,67};
static const int anchor[17]={6,13,47,47,47,47,48,48,48,48,49,49,49,49,52,52,47};
CGPoint points[76];for(NSUInteger i=0;i<76;i++){points[i]=ReadEyePoint(g[@"points76"][i]);points[i].y=1-points[i].y;}
CGPoint q=CGPointMake((destination.x-e.origin.x)/e.size.width,1-(destination.y-e.origin.y)/e.size.height);
double thinAmount=Clamp(thin,0,1)*.025;
if(fabs(thinAmount)>1e-6)for(NSUInteger i=0;i<17;i++) {
CGPoint a=points[origin[i]],b=points[anchor[i]];double r=hypot(a.x-b.x,a.y-b.y);
if(r<=1e-4)continue;
double weight=Clamp(1-hypot(q.x-a.x,q.y-a.y)/r,0,1);
q.x=Clamp(q.x-(b.x-a.x)*thinAmount*weight,0,1);q.y=Clamp(q.y-(b.y-a.y)*thinAmount*weight,0,1);
}
double amount=Clamp(eye,0,1)*.15;
const int center[2]={6,13},radiusA[2]={0,7},radiusB[2]={1,8};
if(amount>1e-6)for(int j=0;j<2;j++) {
int i=reverseEyes?1-j:j;CGPoint c=points[center[i]],a=points[radiusA[i]],b=points[radiusB[i]];
double radius=hypot((a.x-b.x)*e.size.width,(a.y-b.y)*e.size.height);
if(radius<=1e-4)continue;
double dx=q.x-c.x,dy=q.y-c.y,distance=hypot(dx*e.size.width,dy*e.size.height);
if(distance>radius)continue;
double ratio=distance/radius,factor=1-amount*(1-pow(ratio,2));
q.x=c.x+dx*factor;q.y=c.y+dy*factor;
}
return q;
}
static NSData *CoordinateData(CGSize size) {
NSUInteger width=size.width,height=size.height;
NSMutableData *data=[NSMutableData dataWithLength:width*height*4*sizeof(float)];float *p=data.mutableBytes;
for(NSUInteger y=0;y<height;y++)for(NSUInteger x=0;x<width;x++) {
NSUInteger i=(y*width+x)*4;p[i]=(x+.5)/width;p[i+1]=(y+.5)/height;p[i+2]=.25;p[i+3]=1;
}
return data;
}
static CIImage *CoordinateImage(CGRect e) {
return [[CIImage imageWithBitmapData:CoordinateData(e.size) bytesPerRow:(NSUInteger)e.size.width*4*sizeof(float) size:e.size format:kCIFormatRGBAf colorSpace:nil]
imageByApplyingTransform:CGAffineTransformMakeTranslation(e.origin.x,e.origin.y)];
}
static NSData *Pixels(CIContext *context,CIImage *image) {
CGRect e=image.extent;NSUInteger width=e.size.width,height=e.size.height;
NSMutableData *data=[NSMutableData dataWithLength:width*height*4*sizeof(float)];
[context render:image toBitmap:data.mutableBytes rowBytes:width*4*sizeof(float) bounds:e format:kCIFormatRGBAf colorSpace:nil];return data;
}
// Optional local oracle. Only a path provided explicitly by the developer is
// loaded; no vendor binary is included in, copied to, or required by this repo.
// AIR metadata: faceThinMode buffer4, sampleFlipY buffer8, warpFlipY buffer9.
@interface TargetOracle : NSObject
@property(nonatomic,strong)id<MTLDevice> device;
@property(nonatomic,strong)id<MTLRenderPipelineState> pipeline;
@property(nonatomic,strong)id<MTLCommandQueue> queue;
- (instancetype)initWithLibrary:(NSString *)path;
- (NSData *)pixelsForGeometry:(NSDictionary *)geometry extent:(CGRect)e thin:(double)thin eye:(double)eye;
@end
@implementation TargetOracle
- (instancetype)initWithLibrary:(NSString *)path {
self=[super init];if(!self)return nil;
_device=MTLCreateSystemDefaultDevice();NSError *error=nil;
id<MTLLibrary> library=[_device newLibraryWithURL:[NSURL fileURLWithPath:path] error:&error];
Check(library!=nil,[NSString stringWithFormat:@"optional target library loads: %@",error?:@""]);
MTLRenderPipelineDescriptor *pd=[MTLRenderPipelineDescriptor new];
pd.vertexFunction=[library newFunctionWithName:@"vertexShader"];
pd.fragmentFunction=[library newFunctionWithName:@"faceThinFragmentShader"];
pd.colorAttachments[0].pixelFormat=MTLPixelFormatRGBA32Float;
_pipeline=[_device newRenderPipelineStateWithDescriptor:pd error:&error];
Check(_pipeline!=nil,[NSString stringWithFormat:@"optional target coordinate pipeline loads: %@",error?:@""]);
_queue=[_device newCommandQueue];return self;
}
- (NSData *)pixelsForGeometry:(NSDictionary *)g extent:(CGRect)e thin:(double)thin eye:(double)eye {
NSUInteger width=e.size.width,height=e.size.height;
MTLTextureDescriptor *td=[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA32Float width:width height:height mipmapped:NO];
td.storageMode=MTLStorageModeShared;td.usage=MTLTextureUsageShaderRead;
id<MTLTexture> input=[_device newTextureWithDescriptor:td];NSData *gradient=CoordinateData(e.size);
[input replaceRegion:MTLRegionMake2D(0,0,width,height) mipmapLevel:0 withBytes:gradient.bytes bytesPerRow:width*16];
td.usage=MTLTextureUsageRenderTarget;id<MTLTexture> output=[_device newTextureWithDescriptor:td];
typedef struct {vector_float2 position,textureCoordinate;} Vertex;
float w=(float)width,h=(float)height;
Vertex vertices[]={{{-w*.5f,h*.5f},{0,0}},{{-w*.5f,-h*.5f},{0,1}},{{w*.5f,h*.5f},{1,0}},{{w*.5f,-h*.5f},{1,1}}};
vector_uint2 viewport={(uint32_t)width,(uint32_t)height};vector_float2 points[77];
for(NSUInteger i=0;i<76;i++){CGPoint p=ReadEyePoint(g[@"points76"][i]);points[i]=(vector_float2){p.x,1-p.y};}
NSArray *box=g[@"box"];points[76]=(vector_float2){[box[0] doubleValue]+[box[2] doubleValue]*.5,1-([box[1] doubleValue]+[box[3] doubleValue]*.5)};
int count=1,zero=0;float strength=Clamp(thin,0,1);
float shape[10]={Clamp(thin,0,1)*.025,0,0,0,0,0,0,0,0,Clamp(eye,0,1)*.15};
id<MTLCommandBuffer> command=[_queue commandBuffer];MTLRenderPassDescriptor *pass=[MTLRenderPassDescriptor renderPassDescriptor];
pass.colorAttachments[0].texture=output;pass.colorAttachments[0].loadAction=MTLLoadActionClear;pass.colorAttachments[0].storeAction=MTLStoreActionStore;
id<MTLRenderCommandEncoder> encoder=[command renderCommandEncoderWithDescriptor:pass];
[encoder setRenderPipelineState:_pipeline];
[encoder setVertexBytes:vertices length:sizeof(vertices) atIndex:0];[encoder setVertexBytes:&viewport length:sizeof(viewport) atIndex:1];
[encoder setFragmentTexture:input atIndex:0];[encoder setFragmentBytes:points length:sizeof(points) atIndex:0];
[encoder setFragmentBytes:&count length:sizeof(count) atIndex:1];[encoder setFragmentBytes:&strength length:sizeof(strength) atIndex:2];
[encoder setFragmentBytes:&zero length:sizeof(zero) atIndex:4];[encoder setFragmentBytes:shape length:sizeof(shape) atIndex:7];
[encoder setFragmentBytes:&zero length:sizeof(zero) atIndex:8];[encoder setFragmentBytes:&zero length:sizeof(zero) atIndex:9];
vector_float2 sampleRect[2]={{0,0},{1,1}};[encoder setFragmentBytes:sampleRect length:sizeof(sampleRect) atIndex:10];
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];[encoder endEncoding];[command commit];[command waitUntilCompleted];
Check(command.status==MTLCommandBufferStatusCompleted,[NSString stringWithFormat:@"optional target executes: %@",command.error?:@""]);
NSMutableData *data=[NSMutableData dataWithLength:width*height*4*sizeof(float)];
[output getBytes:data.mutableBytes bytesPerRow:width*16 fromRegion:MTLRegionMake2D(0,0,width,height) mipmapLevel:0];return data;
}
@end
static NSData *NumericalCase(CIContext *context,PGFaceProcessor *processor,TargetOracle *target,
CGRect e,NSDictionary *geometry,double thin,double eye,NSString *name) {
PGBeautySettings settings=PGBeautyDefaults();settings.faceThin=thin;settings.faceBigEye=eye;
CIImage *output=[processor warpImage:CoordinateImage(e) geometry:@[geometry] settings:settings extent:e];
Check(output!=nil&&CGRectEqualToRect(output.extent,e),[name stringByAppendingString:@" preserves extent"]);
NSData *data=Pixels(context,output);const float *field=data.bytes;
NSUInteger width=e.size.width,height=e.size.height;double worst=0,move=0,worstTarget=0,sum=0;
for(NSUInteger y=2;y<height-2;y+=3)for(NSUInteger x=2;x<width-2;x+=3) {
CGPoint q=Oracle(CGPointMake(e.origin.x+x+.5,e.origin.y+height-y-.5),geometry,e,thin,eye,NO);
double expectedX=Clamp(q.x,.5/width,1-.5/width),expectedY=Clamp(q.y,.5/height,1-.5/height);
NSUInteger k=(y*width+x)*4;double error=hypot((field[k]-expectedX)*width,(field[k+1]-expectedY)*height);
worst=fmax(worst,error);move=fmax(move,hypot((expectedX-(x+.5)/width)*width,(expectedY-(y+.5)/height)*height));
}
maximumCIError=fmax(maximumCIError,worst);
if(worst>=.035)fprintf(stderr,"%s independent error=%.9g pixels\n",name.UTF8String,worst);
Check(worst<.035,[name stringByAppendingString:@" matches independent thin/eye pixel coordinates"]);
Check((thin<=0&&eye<=0)||move>.005,[name stringByAppendingString:@" exercises active deformation"]);
if(target) {
NSData *reference=[target pixelsForGeometry:geometry extent:e thin:thin eye:eye];const float *ref=reference.bytes;
for(NSUInteger y=2;y<height-2;y++)for(NSUInteger x=2;x<width-2;x++) {
NSUInteger k=(y*width+x)*4;double error=hypot((field[k]-ref[k])*width,(field[k+1]-ref[k+1])*height);
worstTarget=fmax(worstTarget,error);sum+=error*error;
}
maximumTargetError=fmax(maximumTargetError,worstTarget);
if(worstTarget>=.035)fprintf(stderr,"%s target error=%.9g pixels\n",name.UTF8String,worstTarget);
Check(worstTarget<.035,[name stringByAppendingString:@" matches the local original 2.4.0 Metal field"]);
}
[caseReports addObject:@{@"name":name,@"width":@(width),@"height":@(height),@"thin":@(thin),@"eye":@(eye),
@"maximum_independent_error_pixels":@(worst),@"maximum_displacement_pixels":@(move),
@"maximum_target_error_pixels":target?@(worstTarget):NSNull.null,
@"target_rms_error_pixels":target?@(sqrt(sum/((width-4)*(height-4)))):NSNull.null}];
return data;
}
int main(int argc,const char **argv){@autoreleasepool {
NSString *targetPath=nil,*reportPath=nil;
for(int i=1;i<argc;i++) {
if(strcmp(argv[i],"--target-metallib")==0&&i+1<argc)targetPath=@(argv[++i]);
else if(strcmp(argv[i],"--report")==0&&i+1<argc)reportPath=@(argv[++i]);
else {fprintf(stderr,"Usage: eyes-tests [--target-metallib /absolute/default.metallib] [--report output.json]\n");return 2;}
}
caseReports=[NSMutableArray new];
id<MTLDevice> device=MTLCreateSystemDefaultDevice();Check(device!=nil,@"Metal is available for the production Core Image field");
CIContext *context=[CIContext contextWithMTLDevice:device options:@{kCIContextWorkingColorSpace:NSNull.null,kCIContextOutputColorSpace:NSNull.null}];
PGFaceProcessor *processor=[PGFaceProcessor new];TargetOracle *target=targetPath?[[TargetOracle alloc]initWithLibrary:targetPath]:nil;
CGRect extents[]={CGRectMake(0,0,256,256),CGRectMake(0,0,384,216),CGRectMake(-61,35,192,320)};
double rolls[]={0,.31,-.46};double strengths[]={.25,.9035486629353234,1};
for(NSUInteger pose=0;pose<3;pose++)for(NSUInteger size=0;size<3;size++) {
NSDictionary *g=Geometry(rolls[pose],.7+.12*pose);CGRect e=extents[size];
NumericalCase(context,processor,target,e,g,0,0,[NSString stringWithFormat:@"pose%lu size%lu passthrough",pose,size]);
NumericalCase(context,processor,target,e,g,0,strengths[pose],[NSString stringWithFormat:@"pose%lu size%lu eye-only",pose,size]);
NumericalCase(context,processor,target,e,g,1,0,[NSString stringWithFormat:@"pose%lu size%lu thin-only",pose,size]);
NumericalCase(context,processor,target,e,g,1,strengths[pose],[NSString stringWithFormat:@"pose%lu size%lu thin+eye",pose,size]);
}
CGRect e=extents[1];NSDictionary *g=Geometry(.2,.9);
NSData *base=NumericalCase(context,processor,target,e,g,0,1,@"indexed eye centers");
NSMutableDictionary *changed=[g mutableCopy];changed[@"box"]=@[@.02,@.03,@.93,@.91];changed[@"leftEye"]=@[@[@.8,@.1],@[@.9,@.2]];changed[@"rightEye"]=@[@[@.1,@.1],@[@.2,@.2]];
PGBeautySettings onlyEye=PGBeautyDefaults();onlyEye.faceBigEye=1;
Check([base isEqualToData:Pixels(context,[processor warpImage:CoordinateImage(e) geometry:@[changed] settings:onlyEye extent:e])],@"eye field is independent of bbox and eye-region bounds when indexed points exist");
NumericalCase(context,processor,target,e,g,0,-.5,@"negative eye strength clamps to zero");
NSData *clamped=NumericalCase(context,processor,target,e,g,0,2,@"high eye strength clamps to one");
Check([base isEqualToData:clamped],@"unit and excessive eye settings produce identical pixels");
// Every sampled pixel outside both circles remains exactly on the input
// coordinate field. This rejects the previous face-box-sized eye support.
const float *basePixels=base.bytes;NSUInteger unaffected=0;double backgroundError=0;
CGPoint left=ReadEyePoint(g[@"points76"][6]),right=ReadEyePoint(g[@"points76"][13]);
CGPoint la=ReadEyePoint(g[@"points76"][0]),lb=ReadEyePoint(g[@"points76"][1]);
CGPoint ra=ReadEyePoint(g[@"points76"][7]),rb=ReadEyePoint(g[@"points76"][8]);
double lr=hypot((la.x-lb.x)*e.size.width,(la.y-lb.y)*e.size.height);
double rr=hypot((ra.x-rb.x)*e.size.width,(ra.y-rb.y)*e.size.height);
for(NSUInteger y=2;y<(NSUInteger)e.size.height-2;y+=3)for(NSUInteger x=2;x<(NSUInteger)e.size.width-2;x+=3) {
double py=e.size.height-y-.5,px=x+.5;
if(hypot(px-left.x*e.size.width,py-left.y*e.size.height)<=lr+1||hypot(px-right.x*e.size.width,py-right.y*e.size.height)<=rr+1)continue;
NSUInteger k=4*(y*(NSUInteger)e.size.width+x);unaffected++;
backgroundError=fmax(backgroundError,hypot((basePixels[k]-(x+.5)/e.size.width)*e.size.width,(basePixels[k+1]-(y+.5)/e.size.height)*e.size.height));
}
Check(unaffected>1000&&backgroundError<.005,@"eye-only deformation preserves the field outside the two pixel circles");
NSMutableArray *points=[g[@"points76"] mutableCopy];points[0]=points[1];points[7]=points[8];changed=[g mutableCopy];changed[@"points76"]=points;
Check([Pixels(context,CoordinateImage(e)) isEqualToData:Pixels(context,[processor warpImage:CoordinateImage(e) geometry:@[changed] settings:onlyEye extent:e])],@"zero pixel radii leave both eyes unchanged");
if(target) {
NSData *degenerate=[target pixelsForGeometry:changed extent:e thin:0 eye:1];const float *p=degenerate.bytes,*source=CoordinateData(e.size).bytes;
double error=0;for(NSUInteger i=0;i<degenerate.length/sizeof(float);i+=4)error=fmax(error,fabs(p[i]-source[i])+fabs(p[i+1]-source[i+1]));
Check(error<.00004,@"local target also treats zero eye radii as identity");
}
// Force overlap so a reversed eye order cannot accidentally pass the test.
points=[g[@"points76"] mutableCopy];points[6]=@[@.47,@.64];points[13]=@[@.53,@.64];
points[0]=@[@.40,@.64];points[1]=@[@.56,@.64];points[7]=@[@.44,@.64];points[8]=@[@.60,@.64];
changed=[g mutableCopy];changed[@"points76"]=points;
NumericalCase(context,processor,target,e,changed,.7,.9,@"overlapping sequential eyes");
double orderDifference=0;
for(NSUInteger y=2;y<(NSUInteger)e.size.height-2;y+=3)for(NSUInteger x=2;x<(NSUInteger)e.size.width-2;x+=3) {
CGPoint position=CGPointMake(e.origin.x+x+.5,e.origin.y+e.size.height-y-.5);
CGPoint forward=Oracle(position,changed,e,.7,.9,NO),reverse=Oracle(position,changed,e,.7,.9,YES);
orderDifference=fmax(orderDifference,hypot((forward.x-reverse.x)*e.size.width,(forward.y-reverse.y)*e.size.height));
}
Check(orderDifference>.02,@"overlap fixture distinguishes left-to-right sequential sampling");
if(reportPath) {
NSDictionary *report=@{@"scope":@"Authored synthetic Vision76 geometry; thin and big-eye controls only; no user media or target binaries embedded",
@"target_oracle_executed":@(target!=nil),@"mode_buffer4":@0,@"sample_flip_y_buffer8":@0,@"warp_flip_y_buffer9":@0,
@"device":device.name,@"checks":@(checks+1),@"maximum_independent_error_pixels":@(maximumCIError),
@"maximum_target_error_pixels":target?@(maximumTargetError):NSNull.null,@"cases":caseReports};
NSError *error=nil;NSData *json=[NSJSONSerialization dataWithJSONObject:report options:NSJSONWritingPrettyPrinted|NSJSONWritingSortedKeys error:&error];
Check([json writeToFile:reportPath options:NSDataWritingAtomic error:&error],[NSString stringWithFormat:@"write report: %@",error?:@""]);
}
printf("PASS: %lu eye alignment checks; independent max %.8g px; target %s max %.8g px\n",(unsigned long)checks,maximumCIError,target?"executed":"not requested",maximumTargetError);return 0;
}}
+36 -32
查看文件
@@ -5,9 +5,10 @@
#import "PGFaceProcessor.h"
#import <dispatch/dispatch.h>
#include <math.h>
#import "tracking_fixture.h"
// Exercise the independent selector with authored landmark geometry, without
// requiring Vision to manufacture a particular jitter or eye-axis failure.
// requiring Vision to manufacture a particular jitter or invalid topology.
@interface PGRealtimeFaceTracker : NSObject
- (NSDictionary *)resultForGeometry:(NSArray<NSDictionary *> *)raw settings:(PGBeautySettings)settings time:(CMTime)time extent:(CGRect)extent;
- (NSDictionary *)cacheStatistics;
@@ -18,6 +19,7 @@
// tested independently of the separate public-domain NASA portrait fixture.
@interface PGFaceProcessor (TestDetection)
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)settings error:(NSError **)error;
- (NSArray<VNFaceObservation *> *)cachedObservationsInImage:(CIImage *)image settings:(PGBeautySettings)settings time:(CMTime)time error:(NSError **)error;
@end
@interface PGFaceFixture : PGFaceProcessor
@property (nonatomic, copy) NSArray<VNFaceObservation *> *detections;
@@ -28,6 +30,12 @@
self.detectionCount++;
return self.detections ?: @[];
}
// These tests change an injected detector result while reusing one tiny image.
// Keep that fixture boundary explicit; tracking_tests exercises the real frame
// detection cache separately, including same-time changed detector responses.
- (NSArray<VNFaceObservation *> *)cachedObservationsInImage:(CIImage *)image settings:(PGBeautySettings)settings time:(CMTime)time error:(NSError **)error {
return [self observationsInImage:image settings:settings error:error];
}
@end
static NSUInteger PGChecks = 0;
@@ -69,17 +77,17 @@ static CIImage *Gradient(void) {
}
static NSDictionary *Geometry(CGRect box,double jitter) {
NSMutableDictionary *g=[NSMutableDictionary dictionaryWithObject:@[@(box.origin.x),@(box.origin.y),@(box.size.width),@(box.size.height)] forKey:@"box"];
for(NSString *name in @[@"leftEye",@"rightEye",@"leftBrow",@"rightBrow",@"nose",@"lips",@"mouth",@"contour"]) {
double x=[name isEqual:@"rightEye"]?.7:.3;
g[name]=@[@[@(box.origin.x+(x+jitter)*box.size.width),@(box.origin.y+.65*box.size.height)]];
}
return [g copy];
return PGTrackingTestGeometry(box,jitter);
}
static CGRect GeometryBox(NSDictionary *g) {
NSArray *b=g[@"box"];return b.count==4?CGRectMake([b[0] doubleValue],[b[1] doubleValue],[b[2] doubleValue],[b[3] doubleValue]):CGRectZero;
}
static double EyeX(NSDictionary *g) {return [g[@"leftEye"][0][0] doubleValue];}
static BOOL SameCompleteShape(NSDictionary *a,NSDictionary *b) {
NSArray *x=a[@"points76"],*y=b[@"points76"];if(x.count!=76||y.count!=76)return NO;
for(NSUInteger i=0;i<76;i++)for(NSUInteger j=0;j<2;j++)if(fabs([x[i][j] doubleValue]-[y[i][j] doubleValue])>1e-12)return NO;
return YES;
}
static NSDictionary *Live(PGRealtimeFaceTracker *tracker,NSArray *g,PGBeautySettings s,int64_t tick) {
return [tracker resultForGeometry:g settings:s time:CMTimeMake(tick,1000) extent:CGRectMake(0,0,128,128)];
}
@@ -90,7 +98,7 @@ static void RealtimeChecks(CIContext *context,CIImage *image) {
PGFaceFixture *p=[PGFaceFixture new];p.detections=@[left,right];
s.faceSmallHead=.8;
PGFaceFrame *automatic=[p processImage:image settings:s time:kCMTimeZero error:&error];
Check(automatic.deformationGeometry.count==1 && GeometryBox(automatic.deformationGeometry.firstObject).origin.x<.5,@"default realtime geometry chooses one area/center primary without analysis");
Check(automatic.deformationGeometry.count==0,@"bbox-only fixture cannot seed default deformation without complete valid landmarks");
Check(automatic.faces.count==2 && automatic.faces[0]==left && automatic.faces[1]==right,@"default appearance retains both original Vision observations");
Check(Channel(context,automatic.teethMask,97,51,0)>20 && Channel(context,automatic.eyeMask,92,69,0)>20,@"default teeth and eye support covers the non-primary face");
CGRect rightCrop=CGRectMake(84,38,30,52);
@@ -102,18 +110,10 @@ static void RealtimeChecks(CIContext *context,CIImage *image) {
Check([p trackingDataForTarget:0]==nil && [p trackingDataForTarget:1]==nil && [p trackingDataForTarget:2]==nil,@"realtime processing never creates persisted analysis records");
p.detections=@[Face(CGRectMake(.15,.25,.30,.50)),Face(CGRectMake(.43,.005,.56,.99))];
PGFaceFrame *moving=[p processImage:image settings:s time:CMTimeMake(1,25) error:&error];
Check(GeometryBox(moving.deformationGeometry.firstObject).origin.x<.3,@"moving primary stays selected when a much larger stranger appears outside its rectangular guard");
Check(moving.deformationGeometry.count==0 && moving.faces.count==2,@"successive bbox-only observations retain appearance coverage without inventing a shape");
PGFaceFixture *cold=[PGFaceFixture new];cold.detections=p.detections;
PGFaceFrame *coldFrame=[cold processImage:image settings:s time:CMTimeMake(1,25) error:&error];
Check(GeometryBox(coldFrame.deformationGeometry.firstObject).origin.x>.4,@"cold initialization explicitly differs from warm identity association when a new primary dominates");
PGBeautySettings manual=s;manual.singleFace=1;manual.selectionX=.78;manual.selectionY=.5;
p.detections=@[left,right];
PGFaceFrame *chosen=[p processImage:image settings:manual time:CMTimeMake(2,25) error:&error];
Check(chosen.faces.count==1 && chosen.faces.firstObject==right,@"manual selection retains its original observation rather than matching a smoothed box");
Check(GeometryBox(chosen.deformationGeometry.firstObject).origin.x>.5 && Channel(context,chosen.colorMask,26,54,0)==0,@"explicit single-face UX narrows appearance to the selected target");
manual.selectionX=.25;
chosen=[p processImage:image settings:manual time:CMTimeMake(2,25) error:&error];
Check(chosen.faces.firstObject==left,@"manual reselection at the same exact time immediately selects the other face");
Check(coldFrame.deformationGeometry.count==0,@"cold initialization also rejects otherwise valid boxes with missing topology");
PGRealtimeFaceTracker *t=[PGRealtimeFaceTracker new];
NSDictionary *a=Geometry(CGRectMake(.1,.25,.3,.5),0),*b=Geometry(CGRectMake(.66,.3,.2,.4),0);
@@ -126,13 +126,15 @@ static void RealtimeChecks(CIContext *context,CIImage *image) {
Check([r[@"index"] integerValue]==0 && [r[@"referenceKey"] isEqual:@"0/1/0"],@"live association uses the previous frame rather than recomputing the primary score");
NSDictionary *outside=Geometry(CGRectMake(.60,.30,.10,.10),0);
r=Live(t,@[outside],s,80);
Check([r[@"held"] boolValue] && [r[@"output"][0] isEqual:Live(t,@[aMoved,large],s,40)[@"output"][0]],@"no guard candidate retains the previous stable geometry at full strength");
Check([r[@"held"] boolValue] && SameCompleteShape(r[@"output"][0],aMoved),@"no guard candidate retains the previous accepted geometry at full strength");
r=Live(t,@[],s,120);
Check([r[@"held"] boolValue] && [r[@"referenceKey"] isEqual:@"2/25/0"],@"empty detections hold geometry while advancing output time");
for(int64_t tick=160;tick<=2160;tick+=40)r=Live(t,@[],s,tick);
Check([r[@"held"] boolValue] && [r[@"output"] count]==1,@"continuous close held frames can retain geometry beyond .35 seconds");
r=Live(t,@[],s,3000);
Check([r[@"output"] count]==0 && [r[@"referenceKey"] length]==0,@"a distant seek without a nearby past reference cold-starts without held geometry");
Check([r[@"held"] boolValue] && [r[@"acceptedKey"] isEqual:@"1/25/0"],@"forward holds retain the last accepted detection time separately from output time");
[t reset];r=Live(t,@[],s,3000);
Check([r[@"output"] count]==0 && [r[@"referenceKey"] length]==0,@"an explicit reset clears held geometry even at a previously rendered time");
r=Live(t,@[b],s,4000);
Check(![r[@"held"] boolValue] && [r[@"index"] integerValue]==0,@"a far seek with a current face cold-acquires that face");
@@ -154,23 +156,21 @@ static void RealtimeChecks(CIContext *context,CIImage *image) {
NSDictionary *g0=Geometry(box,0),*jitter=Geometry(box,.06);
Live(smooth,@[g0],s,0);
NSDictionary *smoothed=Live(smooth,@[jitter],s,10);
double expected=EyeX(g0)+(EyeX(jitter)-EyeX(g0))*.3;
Check(fabs(EyeX(smoothed[@"output"][0])-expected)<1e-9,@"small-dt landmark smoothing uses a time-aware current weight");
Check(fabs(EyeX(smoothed[@"output"][0])-EyeX(g0))<fabs(EyeX(jitter)-EyeX(g0)),@"landmark jitter amplitude is reduced");
Check([smoothed isEqual:Live(smooth,@[jitter],s,10)] && [[smooth cacheStatistics][@"frames"] unsignedIntegerValue]==2,@"same exact time and raw geometry returns an identical cached result without another smoothing step");
Check(fabs(EyeX(smoothed[@"output"][0])-EyeX(jitter))<1e-9,@"pin-style accepts a qualified current shape without arbitrary temporal blending");
Check([smoothed[@"output"][0][@"points76"] isEqual:jitter[@"points76"]],@"all accepted indexed points remain exactly current rather than partially blended");
Check([smoothed isEqual:Live(smooth,@[jitter],s,10)] && [[smooth cacheStatistics][@"frames"] unsignedIntegerValue]==2,@"same exact frame returns an identical frozen result without another tracking step");
NSDictionary *equivalent=[smooth resultForGeometry:@[jitter] settings:s time:CMTimeMake(1,100) extent:CGRectMake(0,0,128,128)];
Check([equivalent isEqual:smoothed],@"equivalent rational CMTime representations share one realtime result");
NSDictionary *changed=Geometry(box,.08);
NSDictionary *refreshed=Live(smooth,@[changed],s,10);
Check(fabs(EyeX(refreshed[@"output"][0])-(EyeX(g0)+(EyeX(changed)-EyeX(g0))*.3))<1e-9,@"changed raw detections at the same time recompute from the earlier frame rather than compound smoothing");
NSMutableDictionary *flipped=[changed mutableCopy];flipped[@"leftEye"]=changed[@"rightEye"];flipped[@"rightEye"]=changed[@"leftEye"];
NSDictionary *axis=Live(smooth,@[flipped],s,20);
Check(fabs(EyeX(axis[@"output"][0])-EyeX(refreshed[@"output"][0]))<1e-9,@"eye-axis reversal holds remapped old landmarks instead of flipping the warp");
Check([refreshed isEqual:smoothed],@"same-frame changed raw observations reuse the previously committed output");
NSMutableDictionary *partial=[changed mutableCopy];[partial removeObjectForKey:@"points76"];
NSDictionary *heldShape=Live(smooth,@[partial],s,20);
Check([heldShape[@"held"] boolValue] && SameCompleteShape(heldShape[@"output"][0],smoothed[@"output"][0]),@"partial topology preserves the last accepted complete shape");
NSDictionary *translated=Geometry(CGRectOffset(box,.1,0),.08);
NSDictionary *translatedResult=Live(smooth,@[translated],s,60);
Check(fabs(GeometryBox(translatedResult[@"output"][0]).origin.x-.3)<1e-9,@"normal translation uses the current detection box without accumulated box lag");
double mappedEye=EyeX(axis[@"output"][0])+.1;
Check(fabs(EyeX(translatedResult[@"output"][0])-(mappedEye+.9*(EyeX(translated)-mappedEye)))<1e-9,@"old landmarks remap into the current moving box before blending");
Check([translatedResult[@"output"][0][@"points76"] isEqual:translated[@"points76"]],@"qualified translating reacquisition adopts the current complete shape exactly");
Check([smoothed[@"output"] isEqual:Live(smooth,@[jitter],s,10)[@"output"]],@"repeated rewind to a cached time reproduces its original stable geometry");
PGRealtimeFaceTracker *seeds=[PGRealtimeFaceTracker new];
@@ -206,7 +206,7 @@ static void RealtimeChecks(CIContext *context,CIImage *image) {
NSDictionary *past=Live(order,@[a],s,0);
Check([past[@"referenceKey"] length]==0 && [past[@"output"][0] isEqual:a],@"uncached rewind never borrows geometry from a future frame");
r=Live(order,@[Geometry(CGRectMake(.64,.3,.2,.4),0)],s,1040);
Check([r[@"referenceKey"] isEqual:future[@"key"]],@"late older frames do not replace the newer live reference head");
Check([r[@"referenceKey"] isEqual:past[@"key"]],@"an uncached rewind starts a new forward reference instead of borrowing a cached future head");
NSDictionary *nearRewind=Live(order,@[a],s,980);
Check([nearRewind[@"referenceKey"] length]==0,@"uncached small rewind also refuses a closer future reference");
NSDictionary *epoch=[order resultForGeometry:@[] settings:s time:CMTimeMakeWithEpoch(1080,1000,9) extent:CGRectMake(0,0,128,128)];
@@ -323,8 +323,12 @@ int main(int argc, const char *argv[]) {
*controls[i] = 1;
PGFaceFrame *warped = [processor processImage:image settings:changed time:kCMTimeZero error:&error];
BOOL changedPixels=![Pixels(context, warped.image) isEqualToData:base];
Check(warped != nil && !changedPixels && warped.deformationGeometry.count==0,[NSString stringWithFormat:@"default pin-style rejects bbox-only topology for %@",names[i]]);
changed.fastMultiFace=1;
PGFaceFrame *legacyWarp=[processor processImage:image settings:changed time:kCMTimeZero error:&error];
BOOL legacyChangedPixels=![Pixels(context,legacyWarp.image) isEqualToData:base];
BOOL requiresPoints=(i==0 || i==2 || i==4 || i==5 || i==6);
Check(warped != nil && (requiresPoints ? !changedPixels : changedPixels), requiresPoints ? @"recovered thin/V/chin/short/jaw skip a face with no indexed landmarks" : [@"geometry control changes pixels: " stringByAppendingString:names[i]]);
Check(legacyWarp != nil && (requiresPoints ? !legacyChangedPixels : legacyChangedPixels), requiresPoints ? @"fast-multi recovered thin/V/chin/short/jaw skip a face with no indexed landmarks" : [@"fast-multi preserves legacy bbox geometry control: " stringByAppendingString:names[i]]);
Check(CGRectEqualToRect(warped.image.extent, image.extent), @"warp preserves image extent");
}
settings.faceShort = 0.8;
+12 -9
查看文件
@@ -4,6 +4,7 @@
#import <ImageIO/ImageIO.h>
#import "PGFaceProcessor.h"
#include <math.h>
#import "tracking_fixture.h"
@interface PGFaceProcessor (SlimTest)
- (CIImage *)warpImage:(CIImage *)image geometry:(NSArray<NSDictionary *> *)geometry settings:(PGBeautySettings)s extent:(CGRect)extent;
@@ -51,9 +52,7 @@ static CGPoint Oracle(CGPoint pixel,NSDictionary *g,CGRect extent,double thin) {
static const unsigned int anchors[17]={6,13,47,47,47,47,48,48,48,48,49,49,49,49,52,52,47};
NSArray *raw=g[@"points76"];CGPoint p[76];
for(NSUInteger i=0;i<76;i++){p[i]=ReadPoint(raw[i]);p[i].y=1-p[i].y;}
double width=hypot((p[75].x-p[59].x)*extent.size.width,(p[75].y-p[59].y)*extent.size.height);
double height=hypot((p[67].x-(p[74].x+p[60].x)/2)*extent.size.width,(p[67].y-(p[74].y+p[60].y)/2)*extent.size.height);
double gain=Clamp(280/fmax(1,fmax(width,2*height)),1,2.25)*.025*Clamp(thin,0,1);
double gain=.025*Clamp(thin,0,1); // 2.4.0 pin coefficient has no legacy size amplification.
CGPoint q=CGPointMake((pixel.x-extent.origin.x)/extent.size.width,1-(pixel.y-extent.origin.y)/extent.size.height);
for(NSUInteger i=0;i<17;i++) {
CGPoint a=p[origins[i]],b=p[anchors[i]];
@@ -399,15 +398,19 @@ int main(int argc,const char *argv[]) {@autoreleasepool {
Check(![Pixels(c,[p processImage:image settings:other time:kCMTimeZero error:&error].image) isEqualToData:source],@"other controls remain available in legacy archives");
}
PGRealtimeFaceTracker *tracker=[PGRealtimeFaceTracker new];PGBeautySettings live=PGBeautyDefaults();
NSDictionary *a=[tracker resultForGeometry:@[Geometry(0,0)] settings:live time:kCMTimeZero extent:image.extent];
NSDictionary *b=[tracker resultForGeometry:@[Geometry(.2,0)] settings:live time:CMTimeMake(1,25) extent:image.extent];
NSDictionary *liveFront=PGTrackingTestGeometry(CGRectMake(.15,.15,.7,.6),0),*liveMoved=PGTrackingTestGeometry(CGRectMake(.16,.15,.7,.6),.003);
NSDictionary *a=[tracker resultForGeometry:@[liveFront] settings:live time:kCMTimeZero extent:image.extent];
NSDictionary *b=[tracker resultForGeometry:@[liveMoved] settings:live time:CMTimeMake(1,25) extent:image.extent];
Check([a[@"output"][0][@"points76"] count]==76 && [b[@"output"][0][@"points76"] count]==76,@"realtime stabilization retains the complete indexed point layout");
Check([b isEqual:[tracker resultForGeometry:@[Geometry(.2,0)] settings:live time:CMTimeMake(1,25) extent:image.extent]],@"repeated rendering does not smooth the 76-point layout again");
Check([b[@"output"][0][@"points76"] isEqual:liveMoved[@"points76"]],@"qualified realtime points are copied exactly by the pin-style branch");
Check([b isEqual:[tracker resultForGeometry:@[liveMoved] settings:live time:CMTimeMake(1,25) extent:image.extent]],@"repeated rendering does not advance the 76-point sequence again");
NSDictionary *held=[tracker resultForGeometry:@[] settings:live time:CMTimeMake(2,25) extent:image.extent];
Check([held[@"output"][0][@"points76"] isEqual:b[@"output"][0][@"points76"]],@"missing detection holds the complete point layout");
NSDictionary *turned=Geometry(1.4,0);
NSArray *heldPoints=held[@"output"][0][@"points76"],*acceptedPoints=b[@"output"][0][@"points76"];BOOL sameHeld=heldPoints.count==76&&acceptedPoints.count==76;
for(NSUInteger i=0;i<MIN(heldPoints.count,acceptedPoints.count);i++) {CGPoint h=ReadPoint(heldPoints[i]),a=ReadPoint(acceptedPoints[i]);sameHeld=sameHeld&&hypot(h.x-a.x,h.y-a.y)<1e-12;}
Check(sameHeld,@"missing detection holds the complete point layout through affine roundoff");
NSDictionary *turned=PGTrackingTestGeometry(CGRectMake(.16,.15,.7,.6),.02);
NSDictionary *back=[tracker resultForGeometry:@[turned] settings:live time:CMTimeMake(3,25) extent:image.extent];
Check([back[@"output"][0][@"points76"] isEqual:turned[@"points76"]],@"reacquired side-face points do not blend with a held frontal pose");
Check([back[@"output"][0][@"points76"] isEqual:turned[@"points76"]],@"qualified reacquired points do not blend with the held pose");
for(int index=1;index<argc;index++) {@autoreleasepool {
CIImage *portrait=[CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:@(argv[index])] options:@{kCIImageColorSpace:NSNull.null}];
PGFaceProcessor *real=[PGFaceProcessor new];PGBeautySettings rs=PGBeautyDefaults();rs.faceThin=1;
+29
查看文件
@@ -0,0 +1,29 @@
// Entirely authored geometry in the public Vision76 index layout. No image or
// landmark coordinates from a person or the reference plug-in are embedded.
static NSDictionary *PGTrackingTestGeometry(CGRect box, double horizontalShift) {
NSMutableArray *points=[NSMutableArray new];
for(NSUInteger i=0;i<76;i++)[points addObject:@[@.5,@.5]];
const NSUInteger leftEye[]={0,4,5,1,3,2},rightEye[]={7,11,12,8,10,9};
for(NSUInteger i=0;i<6;i++) {
double a=2*M_PI*i/6;
points[leftEye[i]]=@[@(.30+.09*cos(a)),@(.70+.035*sin(a))];
points[rightEye[i]]=@[@(.70+.09*cos(a)),@(.70+.035*sin(a))];
}
points[6]=@[@.30,@.70];points[13]=@[@.70,@.70];
for(NSUInteger i=14;i<20;i++)points[i]=@[@(.20+.04*(i-14)),@(.82+.025*sin(M_PI*(i-14)/5))];
for(NSUInteger i=20;i<26;i++)points[i]=@[@(.60+.04*(i-20)),@(.82+.025*sin(M_PI*(i-20)/5))];
const NSUInteger outer[]={27,28,29,30,31,32,33,34,35,36,37,38,39,26};
for(NSUInteger i=0;i<14;i++){double a=M_PI-2*M_PI*i/14;points[outer[i]]=@[@(.5+.17*cos(a)),@(.33+.055*sin(a))];}
const NSUInteger inner[]={42,40,43,45,41,44};
for(NSUInteger i=0;i<6;i++){double a=M_PI-2*M_PI*i/6;points[inner[i]]=@[@(.5+.10*cos(a)),@(.33+.025*sin(a))];}
points[46]=@[@.5,@.71];points[47]=@[@.5,@.61];points[48]=@[@.5,@.55];points[49]=@[@.5,@.49];
points[50]=@[@.58,@.49];points[51]=@[@.55,@.46];points[52]=@[@.5,@.45];points[53]=@[@.45,@.46];
points[54]=@[@.42,@.49];points[55]=@[@.46,@.51];points[56]=@[@.54,@.51];points[57]=@[@.5,@.46];points[58]=@[@.5,@.47];
for(NSUInteger i=0;i<17;i++){double a=M_PI+M_PI*i/16;points[59+i]=@[@(.5+.46*cos(a)),@(.73+.69*sin(a))];}
for(NSUInteger i=0;i<76;i++)points[i]=@[@(box.origin.x+([points[i][0] doubleValue]+horizontalShift)*box.size.width),@(box.origin.y+[points[i][1] doubleValue]*box.size.height)];
NSMutableDictionary *g=[@{@"box":@[@(box.origin.x),@(box.origin.y),@(box.size.width),@(box.size.height)],@"points76":points,@"landmarkConfidence":@.99} mutableCopy];
NSArray *names=@[@"leftEye",@"rightEye",@"leftBrow",@"rightBrow",@"nose",@"lips",@"mouth",@"contour"];
NSArray *indices=@[@[@0,@4,@5,@1,@3,@2],@[@7,@11,@12,@8,@10,@9],@[@14,@15,@16,@17,@18,@19],@[@20,@21,@22,@23,@24,@25],@[@47,@56,@54,@53,@52,@51,@50,@55],@[@27,@28,@29,@30,@31,@32,@33,@34,@35,@36,@37,@38,@39,@26],@[@42,@40,@43,@45,@41,@44],@[@59,@60,@61,@62,@63,@64,@65,@66,@67,@68,@69,@70,@71,@72,@73,@74,@75]];
for(NSUInteger i=0;i<names.count;i++){NSMutableArray *region=[NSMutableArray new];for(NSNumber *n in indices[i])[region addObject:points[n.unsignedIntegerValue]];g[names[i]]=region;}
return [g copy];
}
+251
查看文件
@@ -0,0 +1,251 @@
// Contract tests for the 2.4.0 pin-style reconstruction. The source is imported
// once so the quality gate and actual tracker can be exercised without a host,
// real portrait, private Vision construction, or reference binary execution.
#import "../FCP-BeautyGirl/FCP-BeautyGirl/Plugin/PGFaceProcessor.m"
#import "tracking_fixture.h"
static NSUInteger checks=0,failures=0;
static void Check(BOOL ok,NSString *message){checks++;if(!ok){failures++;fprintf(stderr,"FAIL: %s\n",message.UTF8String);}}
static BOOL EqualPoints(NSArray *a,NSArray *b,double tolerance){
if(a.count!=76||b.count!=76)return NO;
for(NSUInteger i=0;i<76;i++)for(NSUInteger j=0;j<2;j++)if(fabs([a[i][j] doubleValue]-[b[i][j] doubleValue])>tolerance)return NO;
return YES;
}
static NSDictionary *First(NSDictionary *result){return [result[@"output"] firstObject];}
static NSDictionary *Live(PGRealtimeFaceTracker *t,NSArray *g,PGBeautySettings s,int64_t tick){return[t resultForGeometry:g settings:s time:CMTimeMake(tick,1000) extent:CGRectMake(0,0,256,256)];}
static NSArray *MappedPoints(NSDictionary *g,CGRect box){
CGRect old=PGArrayRect(g[@"box"]);NSMutableArray *a=[NSMutableArray new];
for(NSArray *point in g[@"points76"]){CGPoint p=PGRemapPoint(point,old,box);[a addObject:@[@(p.x),@(p.y)]];}return a;
}
static NSDictionary *Without76(NSDictionary *g){NSMutableDictionary *bad=[g mutableCopy];[bad removeObjectForKey:@"points76"];return bad;}
static NSDictionary *WithPoints(NSDictionary *g,void (^change)(NSMutableArray *)){
NSMutableDictionary *bad=[g mutableCopy];NSMutableArray *p=[g[@"points76"] mutableCopy];change(p);bad[@"points76"]=p;return bad;
}
@interface PGScriptedTracker : PGRealtimeFaceTracker
@property CGRect proposedBox;
@property NSUInteger followCalls;
@property NSUInteger seedCalls;
@end
@implementation PGScriptedTracker
- (instancetype)init{if((self=[super init]))_proposedBox=CGRectNull;return self;}
- (CGRect)trackedBoxForImage:(CIImage *)image time:(CMTime)time seedBox:(CGRect)seed {
if(PGValidRect(seed)){self.seedCalls++;return seed;}
self.followCalls++;return self.proposedBox;
}
@end
@interface PGCountingDetector : PGFaceProcessor
@property (nonatomic,copy) NSArray<VNFaceObservation *> *suppliedFaces;
@property NSUInteger calls;
@property BOOL fail;
@end
@implementation PGCountingDetector
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)s error:(NSError **)error {
self.calls++;if(self.fail){if(error)*error=[NSError errorWithDomain:@"synthetic-detection" code:1 userInfo:nil];return nil;}return self.suppliedFaces?:@[];
}
@end
@interface PGScriptedOutputTracker : PGRealtimeFaceTracker
@property (nonatomic,copy) NSDictionary *scriptedOutput;
@end
@implementation PGScriptedOutputTracker
- (NSDictionary *)resultForGeometry:(NSArray *)raw settings:(PGBeautySettings)s time:(CMTime)time extent:(CGRect)extent image:(CIImage *)image {return self.scriptedOutput;}
@end
// Objective-C message-compatible test doubles, never supplied to Vision. They
// expose only the public read-only observation/region selectors used by the
// production geometry converter, backed entirely by our authored fixture.
@interface PGAuthoredRegion : NSObject
@property (nonatomic,strong) NSData *storage;
@property NSUInteger pointCount;
- (const CGPoint *)normalizedPoints;
@end
@implementation PGAuthoredRegion
- (const CGPoint *)normalizedPoints{return self.storage.bytes;}
@end
@interface PGAuthoredLandmarks : NSObject
@property (nonatomic,strong) PGAuthoredRegion *allPoints,*leftEye,*rightEye,*leftEyebrow,*rightEyebrow,*nose,*outerLips,*innerLips,*faceContour;
@property VNConfidence confidence;
@end
@implementation PGAuthoredLandmarks @end
@interface PGAuthoredObservation : NSObject
@property CGRect boundingBox;
@property (nonatomic,strong) PGAuthoredLandmarks *landmarks;
@property VNConfidence confidence;
@end
@implementation PGAuthoredObservation @end
static PGAuthoredRegion *AuthoredRegion(NSArray *points,CGRect box){
NSMutableData *data=[NSMutableData dataWithLength:points.count*sizeof(CGPoint)];CGPoint *p=data.mutableBytes;
for(NSUInteger i=0;i<points.count;i++)p[i]=PGLocalPoint(CGPointMake([points[i][0] doubleValue],[points[i][1] doubleValue]),box);
PGAuthoredRegion *region=[PGAuthoredRegion new];region.storage=data;region.pointCount=points.count;return region;
}
static PGAuthoredObservation *AuthoredObservation(NSDictionary *g){
CGRect box=PGArrayRect(g[@"box"]);PGAuthoredLandmarks *l=[PGAuthoredLandmarks new];l.confidence=.99;
l.allPoints=AuthoredRegion(g[@"points76"],box);l.leftEye=AuthoredRegion(g[@"leftEye"],box);l.rightEye=AuthoredRegion(g[@"rightEye"],box);
l.leftEyebrow=AuthoredRegion(g[@"leftBrow"],box);l.rightEyebrow=AuthoredRegion(g[@"rightBrow"],box);l.nose=AuthoredRegion(g[@"nose"],box);
l.outerLips=AuthoredRegion(g[@"lips"],box);l.innerLips=AuthoredRegion(g[@"mouth"],box);l.faceContour=AuthoredRegion(g[@"contour"],box);
PGAuthoredObservation *o=[PGAuthoredObservation new];o.boundingBox=box;o.landmarks=l;o.confidence=.99;return o;
}
static void QualityAndAcceptance(void){
CGRect box=CGRectMake(.15,.12,.70,.78);NSDictionary *good=PGTrackingTestGeometry(box,0);
Check(PGGeometryQualityReason(good).length==0,@"authored public-layout geometry passes the recovered quality gate");
NSMutableDictionary *legacy=[good mutableCopy];[legacy removeObjectForKey:@"landmarkConfidence"];
Check(PGGeometryQualityReason(legacy).length==0,@"missing confidence metadata keeps authored/legacy geometry usable");
NSMutableDictionary *low=[good mutableCopy];low[@"landmarkConfidence"]=@.2;
NSMutableDictionary *shortContour=[good mutableCopy];shortContour[@"contour"]=[good[@"contour"] subarrayWithRange:NSMakeRange(0,8)];
NSMutableDictionary *eyes=[good mutableCopy];eyes[@"rightEye"]=eyes[@"leftEye"];
NSMutableDictionary *mouth=[good mutableCopy];mouth[@"lips"]=mouth[@"leftEye"];
NSMutableDictionary *nose=[good mutableCopy];nose[@"nose"]=@[@[@.5,@.99]];
NSArray *badCases=@[Without76(good),WithPoints(good,^(NSMutableArray *p){p[65]=@[@(NAN),@.5];}),WithPoints(good,^(NSMutableArray *p){p[65]=@[@3,@.5];}),low,shortContour,eyes,mouth,nose];
PGBeautySettings s=PGBeautyDefaults();s.faceThin=1;
for(NSUInteger i=0;i<badCases.count;i++){
NSDictionary *bad=badCases[i];
Check(PGGeometryQualityReason(bad).length>0,[NSString stringWithFormat:@"quality rejects malformed case %lu",(unsigned long)i]);
PGRealtimeFaceTracker *t=[PGRealtimeFaceTracker new];Live(t,@[good],s,0);NSDictionary *r=Live(t,@[bad],s,40);
Check([r[@"held"] boolValue] && EqualPoints(First(r)[@"points76"],good[@"points76"],1e-12),[NSString stringWithFormat:@"malformed case %lu holds the last accepted complete shape",(unsigned long)i]);
PGRealtimeFaceTracker *cold=[PGRealtimeFaceTracker new];NSDictionary *initial=Live(cold,@[bad],s,0);
Check([First(initial)[@"points76"] count]==0,[NSString stringWithFormat:@"cold malformed case %lu never invents topology",(unsigned long)i]);
}
for(NSNumber *fps in @[@25,@30,@60,@120]){
PGRealtimeFaceTracker *t=[PGRealtimeFaceTracker new];Live(t,@[good],s,0);NSDictionary *next=PGTrackingTestGeometry(box,.002);
NSDictionary *r=[t resultForGeometry:@[next] settings:s time:CMTimeMake(1,fps.intValue) extent:CGRectMake(0,0,256,256)];
Check(![r[@"held"] boolValue] && EqualPoints(First(r)[@"points76"],next[@"points76"],0),[NSString stringWithFormat:@"accepted %ldfps points are copied exactly without legacy temporal blending",(long)fps.integerValue]);
}
PGRealtimeFaceTracker *t=[PGRealtimeFaceTracker new];Live(t,@[good],s,0);NSDictionary *middle=Live(t,@[Without76(good)],s,40),*back=Live(t,@[good],s,80);
Check([middle[@"held"] boolValue] && [First(middle)[@"points76"] count]==76 && EqualPoints(First(back)[@"points76"],good[@"points76"],0),@"76 to missing76 to76 never disables the geometric field");
NSDictionary *jumpy=PGTrackingTestGeometry(box,.34);
Check(PGGeometryQualityReason(jumpy).length==0,@"large coherent local jump remains structurally valid before temporal compatibility checks");
PGRealtimeFaceTracker *jump=[PGRealtimeFaceTracker new];Live(jump,@[good],s,0);NSDictionary *rejected=Live(jump,@[jumpy],s,40);
Check([rejected[@"held"] boolValue] && EqualPoints(First(rejected)[@"points76"],good[@"points76"],1e-12),@"a large finite median jump cannot poison accepted shape memory");
NSDictionary *axisReverse=WithPoints(good,^(NSMutableArray *p){for(NSUInteger i=66;i<=68;i++)p[i]=@[p[i][0],@(2*CGRectGetMidY(box)-[p[i][1] doubleValue])];});
Check(PGGeometryAxisFlip(axisReverse,good),@"weighted lower-contour to bbox-center axis detects a vertical sign reversal");
NSMutableDictionary *eyeReverse=[good mutableCopy];eyeReverse[@"leftEye"]=good[@"rightEye"];eyeReverse[@"rightEye"]=good[@"leftEye"];
Check(!PGGeometryAxisFlip(eyeReverse,good),@"an isolated eye-order change is not the recovered lower-contour axis test");
NSDictionary *fewOutliers=WithPoints(good,^(NSMutableArray *p){for(NSUInteger i=0;i<6;i++)p[i]=@[@([p[i][0] doubleValue]+.55),p[i][1]];});
NSDictionary *manyOutliers=WithPoints(good,^(NSMutableArray *p){for(NSUInteger i=0;i<12;i++)p[i]=@[@([p[i][0] doubleValue]+.55),p[i][1]];});
Check(PGGeometryTransitionReason(fewOutliers,good).length==0,@"the temporal quantile gate does not treat a few outliers as a large median or P90 jump");
Check(PGGeometryTransitionReason(manyOutliers,good).length>0,@"P90 rejects a substantial outlier group even when the median remains zero");
}
static void SequenceAndCache(void){
PGBeautySettings s=PGBeautyDefaults();s.faceThin=1;CGRect box=CGRectMake(.15,.12,.70,.78);
NSDictionary *good=PGTrackingTestGeometry(box,0),*changed=PGTrackingTestGeometry(box,.003);CIImage *image=[[CIImage imageWithColor:[CIColor colorWithRed:.5 green:.5 blue:.5]] imageByCroppingToRect:CGRectMake(0,0,256,256)];
PGScriptedTracker *t=[PGScriptedTracker new];NSDictionary *first=[t resultForGeometry:@[good] settings:s time:kCMTimeZero extent:image.extent image:image];
Check(t.seedCalls>0,@"first accepted face seeds the continuous object-box tracker");
CGRect tracked=CGRectMake(.17,.13,.66,.74);t.proposedBox=tracked;
NSDictionary *hold=[t resultForGeometry:@[Without76(good)] settings:s time:CMTimeMake(1,25) extent:image.extent image:image];
Check(t.followCalls>0 && [hold[@"held"] boolValue],@"a partial detection asks the continuous sequence tracker for a box");
Check(CGRectEqualToRect(PGArrayRect(First(hold)[@"box"]),tracked) && EqualPoints(First(hold)[@"points76"],MappedPoints(good,tracked),1e-12),@"held complete points follow the tracked box with exact affine remapping");
NSUInteger calls=t.followCalls+t.seedCalls;
NSDictionary *repeat=[t resultForGeometry:@[changed] settings:s time:CMTimeMake(2,50) extent:image.extent image:image];
Check([repeat isEqual:hold] && t.followCalls+t.seedCalls==calls,@"same rational frame freezes its output and does not advance Vision again when raw changes");
t.proposedBox=CGRectNull;NSDictionary *reacquired=[t resultForGeometry:@[changed] settings:s time:CMTimeMake(2,25) extent:image.extent image:image];
Check(![reacquired[@"held"] boolValue] && EqualPoints(First(reacquired)[@"points76"],changed[@"points76"],0),@"a good reacquired detection replaces held shape exactly");
NSDictionary *miss=Live(t,@[],s,120);Check([miss[@"held"] boolValue] && [First(miss)[@"points76"] count]==76,@"nearby complete detection loss preserves complete accepted geometry");
PGRealtimeFaceTracker *cold=[PGRealtimeFaceTracker new];NSDictionary *empty=Live(cold,@[],s,40);Live(cold,@[good],s,0);
Check([empty isEqual:Live(cold,@[changed],s,40)],@"same-frame freeze is stable when an earlier request arrives later");
PGRealtimeFaceTracker *past=[PGRealtimeFaceTracker new];Live(past,@[good],s,1000);NSDictionary *rewind=Live(past,@[],s,960);
Check([rewind[@"output"] count]==0,@"uncached reverse request cannot inherit a future accepted face");
PGRealtimeFaceTracker *scope=[PGRealtimeFaceTracker new];Live(scope,@[good],s,0);
Check([[scope resultForGeometry:@[] settings:s time:CMTimeMakeWithEpoch(40,1000,9) extent:image.extent][@"output"] count]==0,@"CMTime epoch changes isolate accepted geometry");
Check([[scope resultForGeometry:@[] settings:s time:CMTimeMake(40,1000) extent:CGRectMake(10,0,256,256)][@"output"] count]==0,@"image origin changes isolate accepted geometry");
PGBeautySettings multi=s;multi.fastMultiFace=1;NSDictionary *many=Live(scope,@[good,Without76(changed)],multi,40);
Check([many[@"output"] count]==2,@"fast-multi retains its independent legacy raw-geometry contract");
Check([Live(scope,@[],multi,80)[@"output"] count]==0,@"fast-multi cannot inherit the default primary-face hold");
NSDictionary *defaultAgain=Live(scope,@[],s,80);Check([First(defaultAgain)[@"points76"] count]==76,@"fast-multi requests do not overwrite default accepted memory");
[scope reset];Check([Live(scope,@[],s,120)[@"output"] count]==0,@"reset removes both output history and accepted shape state");
Check(EqualPoints(First(first)[@"points76"],good[@"points76"],0),@"the first sequence frame preserves every authored point");
}
// Exercise the real VNSequenceRequestHandler/VNTrackObjectRequest on a textured
// synthetic rectangle. This verifies API wiring; it is not portrait accuracy or
// target parity. No face detector or media file is required.
static CIImage *ObjectFrame(NSUInteger frame){
NSMutableData *d=[NSMutableData dataWithLength:256*256*4];uint8_t *p=d.mutableBytes;NSInteger dx=(NSInteger)frame*2;
for(NSInteger y=0;y<256;y++)for(NSInteger x=0;x<256;x++){
NSUInteger i=4*(y*256+x);NSInteger u=x-76-dx,v=y-51;BOOL inside=u>=0&&u<90&&v>=0&&v<128;
uint8_t texture=inside?(uint8_t)(35+(u*37+v*53+((u/7+v/9)%2)*83)%210):8;
p[i]=texture;p[i+1]=inside?(uint8_t)(50+(u*17+v*13)%180):12;p[i+2]=inside?(uint8_t)(40+(u*11+v*29)%200):16;p[i+3]=255;
}
return[CIImage imageWithBitmapData:d bytesPerRow:1024 size:CGSizeMake(256,256) format:kCIFormatRGBA8 colorSpace:NULL];
}
static double MaskPeak(CIContext *context,CIImage *image){
CGRect extent=image.extent;NSUInteger width=extent.size.width,height=extent.size.height;
NSMutableData *d=[NSMutableData dataWithLength:width*height*4*sizeof(float)];
[context render:image toBitmap:d.mutableBytes rowBytes:width*4*sizeof(float) bounds:extent format:kCIFormatRGBAf colorSpace:NULL];
const float *p=d.bytes;double peak=0;for(NSUInteger i=0;i<width*height;i++)peak=MAX(peak,p[4*i]);return peak;
}
static void DetectionCacheAndHeldMasks(void){
CIImage *image=ObjectFrame(0);PGBeautySettings s=PGBeautyDefaults();PGCountingDetector *p=[PGCountingDetector new];
VNFaceObservation *face=[VNFaceObservation faceObservationWithRequestRevision:VNDetectFaceLandmarksRequestRevision3 boundingBox:CGRectMake(.15,.12,.7,.78) roll:nil yaw:nil pitch:nil];
p.suppliedFaces=@[face];NSArray *first=[p cachedObservationsInImage:image settings:s time:CMTimeMake(1,25) error:nil];p.suppliedFaces=@[];
NSArray *again=[p cachedObservationsInImage:image settings:s time:CMTimeMake(2,50) error:nil];
Check(first.count==1 && [again isEqual:first] && p.calls==1,@"equivalent same-frame requests reuse detection before running Vision again");
s.faceThin=1;Check([[p cachedObservationsInImage:image settings:s time:CMTimeMake(1,25) error:nil] isEqual:first] && p.calls==1,@"shape parameter redraws do not invalidate same-frame face detection");
NSArray *empty=[p cachedObservationsInImage:image settings:s time:CMTimeMake(2,25) error:nil];p.suppliedFaces=@[face];
Check(empty.count==0 && [[p cachedObservationsInImage:image settings:s time:CMTimeMake(2,25) error:nil] count]==0 && p.calls==2,@"successful empty detection is frozen for redraws rather than toggling appearance");
[p cachedObservationsInImage:image settings:s time:CMTimeMakeWithEpoch(2,25,8) error:nil];Check(p.calls==3,@"detection cache distinguishes CMTime epochs");
CIImage *offset=[image imageByApplyingTransform:CGAffineTransformMakeTranslation(10,20)];[p cachedObservationsInImage:offset settings:s time:CMTimeMake(2,25) error:nil];
Check(p.calls==4,@"detection cache distinguishes source extent origin");
s.fastMultiFace=1;[p cachedObservationsInImage:image settings:s time:CMTimeMake(2,25) error:nil];Check(p.calls==5,@"different detection-resolution modes cannot share cached observations");
[p resetTracking];[p cachedObservationsInImage:image settings:s time:CMTimeMake(2,25) error:nil];Check(p.calls==6,@"reset also clears observation cache entries");
PGCountingDetector *retry=[PGCountingDetector new];retry.fail=YES;Check([retry cachedObservationsInImage:image settings:s time:kCMTimeZero error:nil]==nil,@"transient detector error remains an error rather than a successful empty result");
retry.fail=NO;retry.suppliedFaces=@[face];Check([[retry cachedObservationsInImage:image settings:s time:kCMTimeZero error:nil] count]==1 && retry.calls==2,@"failed detections remain retryable at the exact same frame");
PGCountingDetector *bounded=[PGCountingDetector new];for(NSUInteger i=0;i<160;i++)[bounded cachedObservationsInImage:image settings:s time:CMTimeMake(i,25) error:nil];
NSUInteger before=bounded.calls;[bounded cachedObservationsInImage:image settings:s time:CMTimeMake(159,25) error:nil];
Check(bounded.calls==before,@"recent detection remains cached after a long authored sequence");
[bounded cachedObservationsInImage:image settings:s time:kCMTimeZero error:nil];Check(bounded.calls==before+1,@"observation history is bounded and old frames can be recomputed");
// Inject only the tracker output to exercise processImage's real held-mask
// integration. The detector reports no person; all geometry is authored.
PGCountingDetector *maskProcessor=[PGCountingDetector new];PGScriptedOutputTracker *held=[PGScriptedOutputTracker new];
NSDictionary *g=PGTrackingTestGeometry(CGRectMake(.15,.12,.7,.78),0);held.scriptedOutput=@{@"output":@[g],@"held":@YES,@"index":@(-1)};
[maskProcessor setValue:held forKey:@"realtime"];s=PGBeautyDefaults();s.faceThin=1;
PGFaceFrame *frame=[maskProcessor processImage:image settings:s time:kCMTimeZero error:nil];
CIContext *context=[CIContext contextWithOptions:nil];float colorPixel[4]={0},skinPixel[4]={0};CGRect cheek=CGRectMake(88,130,1,1);
[context render:frame.colorMask toBitmap:colorPixel rowBytes:sizeof(colorPixel) bounds:cheek format:kCIFormatRGBAf colorSpace:NULL];
[context render:frame.skinMask toBitmap:skinPixel rowBytes:sizeof(skinPixel) bounds:cheek format:kCIFormatRGBAf colorSpace:NULL];
Check(frame.deformationGeometry.count==1 && colorPixel[0]>.5 && skinPixel[0]>.25,@"default held geometry preserves skin and color support through complete detection loss");
Check(MaskPeak(context,frame.teethMask)>.5 && MaskPeak(context,frame.eyeMask)>.5,@"held default geometry retains nonzero teeth and eye support after the shared warp");
PGCountingDetector *errorProcessor=[PGCountingDetector new];PGScriptedTracker *objectTracker=[PGScriptedTracker new];
[errorProcessor setValue:objectTracker forKey:@"realtime"];errorProcessor.suppliedFaces=(NSArray<VNFaceObservation *> *)@[AuthoredObservation(g)];
PGFaceFrame *valid=[errorProcessor processImage:image settings:s time:kCMTimeZero error:nil];
Check(valid.deformationGeometry.count==1 && [valid.deformationGeometry[0][@"points76"] count]==76,@"public-selector authored observations seed actual processImage accepted geometry");
errorProcessor.fail=YES;objectTracker.proposedBox=PGArrayRect(g[@"box"]);NSError *stageError=nil;
PGFaceFrame *failureHold=[errorProcessor processImage:image settings:s time:CMTimeMake(1,25) error:&stageError];
Check(failureHold!=nil && failureHold.deformationGeometry.count==1 && [failureHold.deformationGeometry[0][@"points76"] count]==76,@"a realtime detector API error returns a frame with the last accepted complete shape");
Check(failureHold!=nil && MaskPeak(context,failureHold.skinMask)>.5 && MaskPeak(context,failureHold.colorMask)>.5 && MaskPeak(context,failureHold.teethMask)>.5 && MaskPeak(context,failureHold.eyeMask)>.5,@"realtime detector errors keep all held beauty masks instead of flashing them off");
NSDictionary *recovered=PGTrackingTestGeometry(PGArrayRect(g[@"box"]),.01);
errorProcessor.fail=NO;errorProcessor.suppliedFaces=(NSArray<VNFaceObservation *> *)@[AuthoredObservation(recovered)];
PGFaceFrame *sameTime=[errorProcessor processImage:image settings:s time:CMTimeMake(1,25) error:nil];
Check(EqualPoints(sameTime.deformationGeometry.firstObject[@"points76"],recovered[@"points76"],1e-12),@"same-time recovery replaces a transient error hold instead of replaying its frozen geometry");
PGCountingDetector *coldError=[PGCountingDetector new];coldError.fail=YES;
[coldError processImage:image settings:s time:kCMTimeZero error:nil];coldError.fail=NO;
coldError.suppliedFaces=(NSArray<VNFaceObservation *> *)@[AuthoredObservation(g)];
PGFaceFrame *acquired=[coldError processImage:image settings:s time:kCMTimeZero error:nil];
Check(acquired.deformationGeometry.count==1&&coldError.calls==2,@"a cold API failure cannot freeze the exact frame without geometry after a successful retry");
errorProcessor.fail=YES;
[[errorProcessor valueForKey:@"observationCache"] removeAllObjects];
PGFaceFrame *cachedFailure=[errorProcessor processImage:image settings:s time:kCMTimeZero error:nil];
Check(cachedFailure.deformationGeometry.count==1&&MaskPeak(context,cachedFailure.skinMask)>.5&&MaskPeak(context,cachedFailure.colorMask)>.5&&MaskPeak(context,cachedFailure.eyeMask)>.5&&MaskPeak(context,cachedFailure.teethMask)>.5,@"an API error after detection-cache eviction preserves masks even when geometry hits an older successful snapshot");
PGBeautySettings offline=s;offline.trackingEnabled=1;stageError=nil;
Check([errorProcessor processImage:image settings:offline time:CMTimeMake(2,25) error:&stageError]==nil && stageError!=nil,@"explicit offline tracking still reports detector errors rather than silently holding live geometry");
}
static void VisionSequence(void){
PGRealtimeFaceTracker *t=[PGRealtimeFaceTracker new];CGRect seed=CGRectMake(76./256,51./256,90./256,128./256);
CGRect start=[t trackedBoxForImage:ObjectFrame(0) time:kCMTimeZero seedBox:seed];Check(PGValidRect(start),@"real sequence tracker accepts an authored textured object seed");
for(NSUInteger i=1;i<=5;i++){
CGRect tracked=[t trackedBoxForImage:ObjectFrame(i) time:CMTimeMake(i,25) seedBox:CGRectNull];
Check(PGValidRect(tracked) && fabs(CGRectGetMidX(tracked)-(CGRectGetMidX(seed)+2.*i/256))<.07 && fabs(CGRectGetMidY(tracked)-CGRectGetMidY(seed))<.07,@"real sequence tracker follows a known synthetic translation without losing the box");
CGRect again=[t trackedBoxForImage:ObjectFrame(i) time:CMTimeMake(i,25) seedBox:CGRectNull];Check(CGRectEqualToRect(tracked,again),@"real sequence same-frame reuse does not advance tracking twice");
}
CGRect rewind=[t trackedBoxForImage:ObjectFrame(3) time:CMTimeMake(3,25) seedBox:CGRectNull];
Check(!PGValidRect(rewind),@"a real sequence rewind cannot return a future tracked box without a fresh seed");
CGRect reseed=CGRectOffset(seed,6./256,0);CGRect restarted=[t trackedBoxForImage:ObjectFrame(3) time:CMTimeMake(3,25) seedBox:reseed];
Check(CGRectEqualToRect(reseed,restarted),@"a rewind with an explicit box restarts the real Vision sequence at that seed");
PGRealtimeFaceTracker *gap=[PGRealtimeFaceTracker new];
[gap trackedBoxForImage:ObjectFrame(0) time:kCMTimeZero seedBox:seed];
Check(!PGValidRect([gap trackedBoxForImage:ObjectFrame(8) time:CMTimeMake(8,25) seedBox:CGRectNull]),@"a gap over .2 seconds invalidates an unseeded continuous object request");
Check(CGRectEqualToRect([gap trackedBoxForImage:ObjectFrame(9) time:CMTimeMake(9,25) seedBox:CGRectNull],seed),@"after gap invalidation the next unseeded frame follows the recovered short lost-box hold path");
}
int main(void){@autoreleasepool{QualityAndAcceptance();SequenceAndCache();DetectionCacheAndHeldMasks();VisionSequence();printf("%s: %lu tracking sequence checks (%lu failures); synthetic data only\n",failures?"FAIL":"PASS",(unsigned long)checks,(unsigned long)failures);return failures?1:0;}}