Align chin deformation with recovered three-pin signed mapping; add anatomical and numeric regressions

这个提交包含在:
dayu 于 2026-10-21 20:49:38 +08:00 提交
1 父节点 1008651f6e
当前提交 16525d62e5
6 个文件已更改
+170 -17

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

+65 -5
查看文件
@@ -25,6 +25,7 @@ static NSDictionary *Geometry(double yaw,double roll) {
for(NSUInteger i=0;i<76;i++)[p addObject:@[@.5,@.5]];
p[6]=@[@.34,@.65];p[13]=@[@.66,@.65];
p[47]=@[@.5,@.60];p[48]=@[@.5,@.52];p[49]=@[@.5,@.45];p[52]=@[@.5,@.42];
p[36]=@[@.54,@.36];p[37]=@[@.5,@.35];p[38]=@[@.46,@.36];
for(NSUInteger i=0;i<17;i++) {
double t=M_PI+M_PI*i/16;
p[59+i]=@[@(.5+.29*cos(t)),@(.65+.43*sin(t))];
@@ -60,6 +61,21 @@ static CGPoint Oracle(CGPoint pixel,NSDictionary *g,CGRect extent,double thin) {
}
return CGPointMake(q.x,1-q.y);
}
static CGPoint ChinOracle(CGPoint pixel,NSDictionary *g,CGRect e,double thin,double chin) {
CGPoint q=Oracle(pixel,g,e,thin);q.y=1-q.y;
NSArray *raw=g[@"points76"];CGPoint points[76];
for(NSUInteger i=0;i<76;i++){points[i]=ReadPoint(raw[i]);points[i].y=1-points[i].y;}
double width=hypot((points[75].x-points[59].x)*e.size.width,(points[75].y-points[59].y)*e.size.height);
double height=2*hypot((points[67].x-(points[74].x+points[60].x)/2)*e.size.width,(points[67].y-(points[74].y+points[60].y)/2)*e.size.height);
double ratio=Clamp(chin,-1,1)*(chin>0?.04:.02)*Clamp(280/fmax(1,fmax(width,height)),1,2.25);
for(NSUInteger i=0;i<3;i++) {
CGPoint a=points[68-i],b=points[38-i];double r=hypot(a.x-b.x,a.y-b.y);
if(r<=.0001)continue;
double w=Clamp(1-hypot(q.x-a.x,q.y-a.y)/r,0,1);
q.x=Clamp(q.x-(b.x-a.x)*ratio*w,0,1);q.y=Clamp(q.y-(b.y-a.y)*ratio*w,0,1);
}
return CGPointMake(q.x,1-q.y);
}
static CIImage *CoordinateImage(CGRect extent) {
NSUInteger w=extent.size.width,h=extent.size.height;
NSMutableData *d=[NSMutableData dataWithLength:w*h*4*sizeof(float)];float *p=d.mutableBytes;
@@ -77,8 +93,8 @@ static NSData *Pixels(CIContext *c,CIImage *image) {
static NSData *Archive(NSDictionary *geometry) {
return [NSPropertyListSerialization dataWithPropertyList:@{@"version":@1,@"frames":@{@"0/1/0":@{@"value":@0,@"scale":@1,@"epoch":@0,@"faces":@[geometry]}}} format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
}
static void NumericCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g,double amount,NSString *name) {
PGBeautySettings s=PGBeautyDefaults();s.faceThin=amount;
static void NumericSettingsCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g,double amount,double chin,NSString *name) {
PGBeautySettings s=PGBeautyDefaults();s.faceThin=amount;s.faceChin=chin;
CIImage *input=CoordinateImage(e),*output=[p warpImage:input geometry:@[g] settings:s extent:e];
Check(output!=nil && CGRectEqualToRect(output.extent,e),[name stringByAppendingString:@" preserves extent"]);
NSData *data=Pixels(c,output);const float *pixels=data.bytes;double worst=0,maxMove=0;
@@ -86,7 +102,7 @@ static void NumericCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g
NSUInteger w=e.size.width,h=e.size.height;
for(NSUInteger y=3;y<h-3;y+=7)for(NSUInteger x=3;x<w-3;x+=7) {
// Bitmap rows are top first, while destCoord / geometry are y up.
CGPoint q=Oracle(CGPointMake(e.origin.x+x+.5,e.origin.y+h-y-.5),g,e,amount);
CGPoint q=ChinOracle(CGPointMake(e.origin.x+x+.5,e.origin.y+h-y-.5),g,e,amount,chin);
double ex=Clamp(q.x,.5/w,1-.5/w),ey=Clamp(1-q.y,.5/h,1-.5/h);
NSUInteger k=(y*w+x)*4;
double difference=fmax(fabs(pixels[k]-ex),fabs(pixels[k+1]-ey));
@@ -95,11 +111,43 @@ static void NumericCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g
}
if(worst>0.00006)fprintf(stderr,"%s oracle max error=%g at %lu,%lu expected=(%g,%g) actual=(%g,%g)\n",name.UTF8String,worst,(unsigned long)worstX,(unsigned long)worstY,worstExpected.x,worstExpected.y,worstActual.x,worstActual.y);
maxOracleError=fmax(maxOracleError,worst);
Check(worst<0.00006,[name stringByAppendingString:@" GPU inverse field matches independent 17-pin oracle"]);
Check(amount==0 || maxMove>.0001,[name stringByAppendingString:@" exercises a nonzero displacement"]);
Check(worst<0.00006,[name stringByAppendingString:@" GPU field matches the independent thin/chin oracle"]);
Check((amount==0 && chin==0) || maxMove>.0001,[name stringByAppendingString:@" exercises a nonzero displacement"]);
NSUInteger corner=4*((h-3)*w+(w-3));
Check(fabs(pixels[corner]-(w-2.5)/w)<1e-6 && fabs(pixels[corner+1]-(h-2.5)/h)<1e-6,[name stringByAppendingString:@" preserves distant background"]);
}
static void NumericCase(CIContext *c,PGFaceProcessor *p,CGRect e,NSDictionary *g,double amount,NSString *name) {
NumericSettingsCase(c,p,e,g,amount,0,name);
}
static CGPoint Displacement(CIContext *c,CIImage *ramp,CIImage *warped,CGPoint uv) {
CGRect e=ramp.extent;CGRect point=CGRectMake(floor(e.origin.x+uv.x*e.size.width),floor(e.origin.y+uv.y*e.size.height),1,1);
float a[4],b[4];
[c render:ramp toBitmap:a rowBytes:sizeof(a) bounds:point format:kCIFormatRGBAf colorSpace:nil];
[c render:warped toBitmap:b rowBytes:sizeof(b) bounds:point format:kCIFormatRGBAf colorSpace:nil];
return CGPointMake((b[0]-a[0])*e.size.width,-(b[1]-a[1])*e.size.height);
}
static void AnatomicalChecks(CIContext *c,PGFaceProcessor *p,NSDictionary *g,CGRect e) {
CIImage *ramp=CoordinateImage(e);NSArray *points=g[@"points76"];
CGPoint chin=ReadPoint(points[67]),lip=ReadPoint(points[37]);
double ax=(lip.x-chin.x)*e.size.width,ay=(lip.y-chin.y)*e.size.height;
for(int sign=-1;sign<=1;sign+=2) {
PGBeautySettings s=PGBeautyDefaults();s.faceChin=sign;
CIImage *out=[p warpImage:ramp geometry:@[g] settings:s extent:e];
CGPoint d=Displacement(c,ramp,out,chin);
Check(hypot(d.x,d.y)>.05,@"chin control moves the detected physical chin landmark");
Check(sign*(d.x*ax+d.y*ay)<0,@"signed chin direction follows chin-to-lip anchor instead of a fixed image Y axis");
BOOL outside=YES,eyesUnchanged=YES;
for(NSNumber *index in @[@6,@13]) {
CGPoint eye=ReadPoint(points[index.unsignedIntegerValue]);
for(NSUInteger i=0;i<3;i++) {
CGPoint a=ReadPoint(points[68-i]),b=ReadPoint(points[38-i]);
if(hypot(eye.x-a.x,eye.y-a.y)<=hypot(a.x-b.x,a.y-b.y)+.003)outside=NO;
}
CGPoint shift=Displacement(c,ramp,out,eye);eyesUnchanged &= hypot(shift.x,shift.y)<.002;
}
Check(outside && eyesUnchanged,@"both physical pupils outside chin supports remain unchanged");
}
}
int main(int argc,const char *argv[]) {@autoreleasepool {
CIContext *c=[CIContext contextWithMTLDevice:MTLCreateSystemDefaultDevice() options:@{kCIContextWorkingColorSpace:NSNull.null,kCIContextOutputColorSpace:NSNull.null}];
@@ -112,6 +160,15 @@ int main(int argc,const char *argv[]) {@autoreleasepool {
NumericCase(c,p,extents[e],Geometry(yaw[y],roll[r]),1,name);
}
NumericCase(c,p,extents[1],Geometry(.6,.2),.5,@"half strength");
for(NSUInteger e=0;e<3;e++)for(NSUInteger y=0;y<3;y++)for(NSUInteger r=0;r<2;r++)for(int sign=-1;sign<=1;sign+=2) {
double angle=y==0?0:(y==1?1.4:-1.4);
NumericSettingsCase(c,p,extents[e],Geometry(angle,r?.45:0),0,sign,
[NSString stringWithFormat:@"chin size%lu yaw%lu roll%lu sign%d",(unsigned long)e,(unsigned long)y,(unsigned long)r,sign]);
}
NumericSettingsCase(c,p,extents[1],Geometry(.4,.2),.8,1,@"thin plus positive chin");
NumericSettingsCase(c,p,extents[2],Geometry(-.6,-.2),1,-1,@"thin plus negative chin");
NumericSettingsCase(c,p,extents[0],Geometry(0,0),0,.5,@"half positive chin");
NumericSettingsCase(c,p,extents[0],Geometry(0,0),0,-.5,@"half negative chin");
CIImage *image=CoordinateImage(extents[1]);NSData *source=Pixels(c,image);
PGBeautySettings s=PGBeautyDefaults();s.faceThin=1;s.trackingEnabled=1;NSError *error=nil;
NSDictionary *g=Geometry(.9,.15);[p setTrackingData:Archive(g) forTarget:0];
@@ -136,6 +193,8 @@ int main(int argc,const char *argv[]) {@autoreleasepool {
[p setTrackingData:Archive(bad) forTarget:0];frame=[p processImage:image settings:s time:kCMTimeZero error:&error];
Check(frame!=nil && frame.deformationGeometry.count==1,@"legacy/malformed indexed points retain the valid face record");
Check([Pixels(c,frame.image) isEqualToData:source],@"legacy/malformed indexed points never trigger a guessed symmetric thin warp");
PGBeautySettings chinOnly=s;chinOnly.faceThin=0;chinOnly.faceChin=1;
Check([Pixels(c,[p processImage:image settings:chinOnly time:kCMTimeZero error:&error].image) isEqualToData:source],@"legacy/malformed indexed points never trigger guessed bbox chin deformation");
PGBeautySettings other=s;other.faceNarrow=.5;
Check(![Pixels(c,[p processImage:image settings:other time:kCMTimeZero error:&error].image) isEqualToData:source],@"other controls remain available in legacy archives");
}
@@ -158,6 +217,7 @@ int main(int argc,const char *argv[]) {@autoreleasepool {
Check([real analyzeImage:portrait settings:rs time:kCMTimeZero error:&error],@"real pose analysis saves the complete geometry");
PGFaceProcessor *copy=[PGFaceProcessor new];[copy setTrackingData:[real trackingDataForTarget:0] forTarget:0];rs.trackingEnabled=1;
Check([Pixels(c,[copy processImage:portrait settings:rs time:kCMTimeZero error:&error].image) isEqualToData:Pixels(c,f.image)],@"real pose replays from persisted 76 points with identical pixels");
AnatomicalChecks(c,real,f.deformationGeometry.firstObject,portrait.extent);
}}
printf("PASS: %lu slim alignment checks; maximum UV oracle error %.9g\n",(unsigned long)checks,maxOracleError);return 0;
}}