Implement and test portrait beauty, face geometry and persistent tracking

这个提交包含在:
dayu 于 2026-10-21 17:17:10 +08:00 提交
1 父节点 e2803e727f
当前提交 0310ca83ba
15 个文件已更改
+1203 -42

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

+181
查看文件
@@ -0,0 +1,181 @@
#import <Foundation/Foundation.h>
#import <CoreImage/CoreImage.h>
#import <Metal/Metal.h>
#import "PGBeautyRenderer.h"
#include <math.h>
static const int W=96,H=64;
static int failures=0,checks=0;
static void Check(BOOL pass,NSString *message) {
checks++;
if (!pass) { failures++; fprintf(stderr,"FAIL: %s\n",message.UTF8String); }
}
static CIImage *Image(const float *pixels,CGRect extent) {
NSData *data=[NSData dataWithBytes:pixels length:W*H*4*sizeof(float)];
return [[CIImage imageWithBitmapData:data bytesPerRow:W*4*sizeof(float)
size:CGSizeMake(W,H) format:kCIFormatRGBAf colorSpace:nil]
imageByApplyingTransform:CGAffineTransformMakeTranslation(extent.origin.x,extent.origin.y)];
}
static float *Render(CIContext *context,CIImage *image,CGRect extent) {
float *p=calloc(W*H*4,sizeof(float));
if (image) [context render:image toBitmap:p rowBytes:W*4*sizeof(float)
bounds:extent format:kCIFormatRGBAf colorSpace:nil];
return p;
}
static float Difference(float *a,float *b,int x0,int x1) {
float sum=0;int n=0;
for(int y=8;y<H-8;y++) for(int x=x0;x<x1;x++) for(int c=0;c<3;c++) {
sum+=fabsf(a[(y*W+x)*4+c]-b[(y*W+x)*4+c]); n++;
}
return sum/MAX(1,n);
}
static float Texture(float *a,int x0,int x1) {
float sum=0;int n=0;
for(int y=8;y<H-8;y++) for(int x=x0+1;x<x1;x++) {
sum+=fabsf(a[(y*W+x)*4]-a[(y*W+x-1)*4]);n++;
}
return sum/MAX(1,n);
}
int main(void) { @autoreleasepool {
NSDictionary *options=@{kCIContextWorkingColorSpace:[NSNull null],kCIContextOutputColorSpace:[NSNull null]};
id<MTLDevice> device=MTLCreateSystemDefaultDevice();
CIContext *context=device ? [CIContext contextWithMTLDevice:device options:options] : [CIContext contextWithOptions:options];
PGBeautyRenderer *renderer=[PGBeautyRenderer new];
CGRect extent=CGRectMake(13,-9,W,H);
float *p=calloc(W*H*4,sizeof(float)),*mask=calloc(W*H*4,sizeof(float));
for(int y=0;y<H;y++) for(int x=0;x<W;x++) {
int i=(y*W+x)*4;float noise=((x+y)%2 ? 1 : -1)*0.045;
p[i]=0.56+noise;p[i+1]=0.40+noise;p[i+2]=0.31+noise;p[i+3]=1;
mask[i]=mask[i+1]=mask[i+2]=(x<W/2 ? 1:0);mask[i+3]=1;
}
CIImage *source=Image(p,extent),*half=Image(mask,extent);
CIImage *white=[[CIImage imageWithColor:[CIColor colorWithRed:1 green:1 blue:1 alpha:1]] imageByCroppingToRect:extent];
PGBeautySettings s=PGBeautyDefaults();NSError *error=nil;
CIImage *output=[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:nil error:&error];
Check(output!=nil && error==nil,@"All custom kernels compile and construct a graph");
Check(CGRectEqualToRect(output.extent,extent),@"Nonzero/negative source origin and finite extent survive");
float *base=Render(context,source,extent),*neutral=Render(context,output,extent);
Check(Difference(base,neutral,4,W-4)<0.00001,@"Neutral controls produce identity");free(neutral);
s.midtoneDetail=1;s.maskEdgeProtect=0;s.featureProtection=0;
output=[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:nil error:&error];
float *smooth=Render(context,output,extent);
Check(Texture(smooth,8,W-8)<Texture(base,8,W-8)*0.82,@"Midtone compression reduces fine texture");
s.mix=0;
float *bypass=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:nil error:&error],extent);
Check(Difference(base,bypass,4,W-4)<0.00001,@"Mix zero is exact bypass");free(bypass);
s=PGBeautyDefaults();s.skinBrightness=1;s.maskSoftness=0;s.maskEdgeProtect=0;
float *bright=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
Check(Difference(base,bright,6,40)>0.08,@"Skin brightness has a substantial visible effect");
Check(Difference(base,bright,56,88)<0.00001,@"Skin-only color change respects the supplied mask");
s.invertMask=1;
float *inverse=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
Check(Difference(base,inverse,6,40)<0.00001 && Difference(base,inverse,56,88)>0.08,@"Invert mask selects the complementary region");
free(bright);free(inverse);
s=PGBeautyDefaults();s.showMask=1;s.maskSoftness=0;s.maskEdgeProtect=0;
float *preview=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
Check(preview[(32*W+20)*4]>0.99 && preview[(32*W+72)*4]<0.01,@"Mask preview exposes actual coverage");free(preview);
s=PGBeautyDefaults();s.highPassDetail=1;
float *global=Render(context,[renderer processImage:source settings:s skinMask:nil teethMask:nil eyeMask:nil error:&error],extent);
Check(Texture(global,8,W-8)<Texture(base,8,W-8)*0.72,@"Global high-frequency compression works without a skin mask");free(global);
s=PGBeautyDefaults();s.teethWhitening=1;
float *whiten=Render(context,[renderer processImage:source settings:s skinMask:nil teethMask:half eyeMask:nil error:&error],extent);
Check(whiten[(32*W+20)*4+2]>base[(32*W+20)*4+2]+0.035,@"Teeth whitening neutralizes yellow and lifts blue");
Check(Difference(base,whiten,56,88)<0.00001,@"Teeth whitening is spatially confined");free(whiten);
s=PGBeautyDefaults();s.eyeBrightening=1;
float *eyes=Render(context,[renderer processImage:source settings:s skinMask:nil teethMask:nil eyeMask:half error:&error],extent);
Check(Difference(base,eyes,6,40)>0.04 && Difference(base,eyes,56,88)<0.00001,@"Eye brightening is visible and spatially confined");free(eyes);
for(int preset=1;preset<=6;preset++) {
s=PGBeautyDefaults();s.skinTonePreset=preset;s.maskSoftness=0;s.maskEdgeProtect=0;
float *look=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
Check(Difference(base,look,6,40)>0.002 && Difference(base,look,56,88)<0.00001,
[NSString stringWithFormat:@"Independent skin preset %d changes only skin",preset]);free(look);
}
s=PGBeautyDefaults();s.skinHue=30;s.maskSoftness=0;s.maskEdgeProtect=0;
float *hue=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
Check(Difference(base,hue,6,40)>0.025 && Difference(base,hue,56,88)<0.00001,@"Hue rotates selected skin without changing the rest");free(hue);
s.skinHue=0;s.skinSaturation=-100;
float *desaturated=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
int center=(32*W+20)*4;
Check(fabsf(desaturated[center]-desaturated[center+1])<0.0002 && fabsf(desaturated[center+1]-desaturated[center+2])<0.0002,@"Minus 100 saturation produces neutral gray skin");free(desaturated);
s=PGBeautyDefaults();s.sharpness=1;
float *sharp=Render(context,[renderer processImage:source settings:s skinMask:nil teethMask:nil eyeMask:nil error:&error],extent);
Check(Texture(sharp,8,W-8)>Texture(base,8,W-8)*1.05,@"Sharpening increases local edge contrast");free(sharp);
s=PGBeautyDefaults();s.midtoneDetail=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.featureProtection=1;
float *protected=Render(context,[renderer processImage:source settings:s skinMask:white teethMask:nil eyeMask:half error:&error],extent);
Check(Difference(base,protected,6,40)<0.00001 && Texture(protected,56,88)<Texture(base,56,88)*0.82,@"Feature protection retains eye detail while smoothing nearby skin");free(protected);
s=PGBeautyDefaults();s.showMask=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.maskThreshold=255;
float *strictMask=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
s.maskThreshold=0;
float *looseMask=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
Check(looseMask[(32*W+20)*4]>=strictMask[(32*W+20)*4] && looseMask[(32*W+72)*4]==0,@"Mask threshold keeps zero coverage empty");free(strictMask);free(looseMask);
CIImage *grayMask=[[CIImage imageWithColor:[CIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1]] imageByCroppingToRect:extent];
s.maskThreshold=220;
float *highThreshold=Render(context,[renderer processImage:source settings:s skinMask:grayMask teethMask:nil eyeMask:nil error:&error],extent);
s.maskThreshold=60;
float *lowThreshold=Render(context,[renderer processImage:source settings:s skinMask:grayMask teethMask:nil eyeMask:nil error:&error],extent);
Check(highThreshold[(32*W+20)*4]<0.01 && lowThreshold[(32*W+20)*4]>0.99,@"Mask threshold materially changes intermediate confidence coverage");free(highThreshold);free(lowThreshold);
// Chroma-only speckle should be reduced while its luminance stays stable.
float *colorNoise=calloc(W*H*4,sizeof(float));
for(int y=0;y<H;y++)for(int x=0;x<W;x++){
int i=(y*W+x)*4;float n=((x+y)%2 ? 1:-1)*0.055;
colorNoise[i]=0.56+n;colorNoise[i+1]=0.40-n*0.299/0.587;colorNoise[i+2]=0.31;colorNoise[i+3]=1;
}
CIImage *speckle=Image(colorNoise,extent);s=PGBeautyDefaults();s.skinEvenTone=1;s.maskEdgeProtect=0;
float *evenColor=Render(context,[renderer processImage:speckle settings:s skinMask:white teethMask:nil eyeMask:nil error:&error],extent);
float before=Texture(colorNoise,8,W-8),after=Texture(evenColor,8,W-8);
Check(after<before*0.5,@"Even tone reduces chromatic skin mottling");
float oldY=colorNoise[center]*0.299+colorNoise[center+1]*0.587+colorNoise[center+2]*0.114;
float newY=evenColor[center]*0.299+evenColor[center+1]*0.587+evenColor[center+2]*0.114;
Check(fabsf(oldY-newY)<0.001,@"Even tone preserves local luminance");free(colorNoise);free(evenColor);
// Mask channel selection remains dormant until the preview checkbox is enabled.
CIImage *quarterMask=[[CIImage imageWithColor:[CIColor colorWithRed:0.25 green:0.25 blue:0.25 alpha:1]] imageByCroppingToRect:extent];
CIImage *eyeCoverage=[[CIImage imageWithColor:[CIColor colorWithRed:0.8 green:0.8 blue:0.8 alpha:1]] imageByCroppingToRect:extent];
for(int channel=0;channel<3;channel++){
s=PGBeautyDefaults();s.debugChannel=channel;
float *normal=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:quarterMask eyeMask:eyeCoverage error:&error],extent);
Check(Difference(base,normal,4,W-4)<0.00001,[NSString stringWithFormat:@"Channel %d does not enable preview by itself",channel]);free(normal);
s.showMask=1;s.maskSoftness=0;s.maskEdgeProtect=0;s.featureProtection=0;
for(int invert=0;invert<2;invert++){
s.invertMask=invert;
float *shown=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:quarterMask eyeMask:eyeCoverage error:&error],extent);
float expected=channel==0 ? 1 : (channel==1 ? 0.25 : 0.8);
if(invert)expected=1-expected;
Check(fabsf(shown[(32*W+20)*4]-expected)<0.0002,[NSString stringWithFormat:@"Preview channel %d uses its supplied mask (invert %d)",channel,invert]);free(shown);
}
}
float *looks[7]={0};
for(int preset=0;preset<=6;preset++){
s=PGBeautyDefaults();s.skinTonePreset=preset;s.maskSoftness=0;s.maskEdgeProtect=0;
looks[preset]=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
}
Check(Difference(base,looks[0],4,W-4)<0.00001,@"Original color preset is exact identity");
Check(looks[2][center+2]-looks[2][center]>looks[3][center+2]-looks[3][center]+0.04,@"Cool white and warm white follow popup order");
Check(looks[4][center]-looks[4][center+1]>looks[1][center]-looks[1][center+1],@"Rosy white has stronger rosy chroma than natural white");
float bronzeY=looks[6][center]*0.299+looks[6][center+1]*0.587+looks[6][center+2]*0.114;
float wheatY=looks[5][center]*0.299+looks[5][center+1]*0.587+looks[5][center+2]*0.114;
float sourceY=base[center]*0.299+base[center+1]*0.587+base[center+2]*0.114;
Check(bronzeY<wheatY && bronzeY<sourceY-0.025 && looks[6][center]>looks[6][center+2],@"Bronze is a darker warm brown than wheat");
for(int preset=0;preset<=6;preset++)free(looks[preset]);
// The renderer's optional blend is linear once; a host that also blends must
// pass mix=1 and blend the complete processed image against the original.
s=PGBeautyDefaults();s.skinBrightness=1;s.skinHue=14;s.skinSaturation=20;s.maskSoftness=0;s.maskEdgeProtect=0;
float *fullEffect=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
s.mix=0.5;
float *halfEffect=Render(context,[renderer processImage:source settings:s skinMask:half teethMask:nil eyeMask:nil error:&error],extent);
BOOL linearMix=YES;
for(int i=0;i<W*H*4;i++)if(fabsf(halfEffect[i]-(base[i]+fullEffect[i])*0.5f)>0.0002)linearMix=NO;
Check(linearMix,@"Combined color controls receive exactly one linear global blend");free(fullEffect);free(halfEffect);
// Every operation must preserve transparency rather than creating halos/opaque pixels.
for(int i=0;i<W*H;i++){p[i*4]*=0.4;p[i*4+1]*=0.4;p[i*4+2]*=0.4;p[i*4+3]=0.4;}
CIImage *transparent=Image(p,extent);s=PGBeautyDefaults();s.midtoneDetail=1;s.skinBrightness=1;s.teethWhitening=1;s.eyeBrightening=1;s.skinTonePreset=3;s.highPassDetail=1;
float *alpha=Render(context,[renderer processImage:transparent settings:s skinMask:white teethMask:half eyeMask:half error:&error],extent);
BOOL alphaOK=YES,finite=YES;
for(int i=0;i<W*H;i++){if(fabsf(alpha[i*4+3]-0.4f)>0.0002)alphaOK=NO;for(int c=0;c<4;c++)if(!isfinite(alpha[i*4+c]))finite=NO;}
Check(alphaOK,@"Combined beauty pipeline preserves fractional alpha");Check(finite,@"Combined beauty pipeline produces finite pixels");free(alpha);
// Explicit failure path for unbounded images.
CIImage *invalid=[renderer processImage:[CIImage imageWithColor:[CIColor whiteColor]] settings:s skinMask:nil teethMask:nil eyeMask:nil error:&error];
Check(invalid==nil && error!=nil,@"Unbounded input produces a useful error");
free(base);free(smooth);free(mask);free(p);
printf("Beauty renderer: %d checks, %d failures (%s).\n",checks,failures,device ? "Metal" : "default Core Image");
return failures ? 1:0;
}}
+209
查看文件
@@ -0,0 +1,209 @@
#import <Foundation/Foundation.h>
#import <CoreImage/CoreImage.h>
#import <Vision/Vision.h>
#import <ImageIO/ImageIO.h>
#import "PGFaceProcessor.h"
// Injectable detections let geometry, selection and saved-frame behavior be
// tested independently of the separate public-domain NASA portrait fixture.
@interface PGFaceProcessor (TestDetection)
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)settings error:(NSError **)error;
@end
@interface PGFaceFixture : PGFaceProcessor
@property (nonatomic, copy) NSArray<VNFaceObservation *> *detections;
@property (nonatomic) NSUInteger detectionCount;
@end
@implementation PGFaceFixture
- (NSArray<VNFaceObservation *> *)observationsInImage:(CIImage *)image settings:(PGBeautySettings)settings error:(NSError **)error {
self.detectionCount++;
return self.detections ?: @[];
}
@end
static NSUInteger PGChecks = 0;
static void Check(BOOL condition, NSString *description) {
PGChecks++;
if (!condition) { fprintf(stderr, "FAIL: %s\n", description.UTF8String); exit(1); }
}
static VNFaceObservation *Face(CGRect box) {
return [VNFaceObservation faceObservationWithRequestRevision:VNDetectFaceLandmarksRequestRevision3 boundingBox:box roll:nil yaw:nil pitch:nil];
}
static NSData *Pixels(CIContext *context, CIImage *image) {
CGRect bounds = image.extent;
NSUInteger width = (NSUInteger)bounds.size.width, height = (NSUInteger)bounds.size.height;
NSMutableData *data = [NSMutableData dataWithLength:width * height * 4];
CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
[context render:image toBitmap:data.mutableBytes rowBytes:width * 4 bounds:bounds format:kCIFormatRGBA8 colorSpace:colorSpace];
CGColorSpaceRelease(colorSpace);
return data;
}
static unsigned char Channel(CIContext *context, CIImage *image, double x, double y, NSUInteger channel) {
unsigned char pixel[4] = {0};
CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
[context render:image toBitmap:pixel rowBytes:4 bounds:CGRectMake(x, y, 1, 1) format:kCIFormatRGBA8 colorSpace:space];
CGColorSpaceRelease(space);
return pixel[channel];
}
static CIImage *Gradient(void) {
NSMutableData *data = [NSMutableData dataWithLength:128 * 128 * 4];
unsigned char *p = data.mutableBytes;
for (NSUInteger y = 0; y < 128; y++) for (NSUInteger x = 0; x < 128; x++) {
NSUInteger index = 4 * (y * 128 + x);
p[index] = x * 2; p[index + 1] = y * 2;
p[index + 2] = ((x / 8 + y / 8) % 2) * 220 + 15; p[index + 3] = 255;
}
CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CIImage *image = [CIImage imageWithBitmapData:data bytesPerRow:128 * 4 size:CGSizeMake(128, 128) format:kCIFormatRGBA8 colorSpace:space];
CGColorSpaceRelease(space);
return image;
}
int main(int argc, const char *argv[]) {
@autoreleasepool {
CIContext *context = [CIContext contextWithOptions:@{kCIContextCacheIntermediates:@NO}];
CIImage *image = Gradient();
PGBeautySettings settings = PGBeautyDefaults();
PGFaceFixture *processor = [[PGFaceFixture alloc] init];
VNFaceObservation *left = Face(CGRectMake(0.08, 0.18, 0.36, 0.62));
VNFaceObservation *right = Face(CGRectMake(0.64, 0.23, 0.28, 0.53));
processor.detections = @[left, right];
NSError *error = nil;
PGFaceFrame *frame = [processor processImage:image settings:settings time:kCMTimeZero error:&error];
Check(frame != nil && error == nil, @"kernels initialize and process finite input");
Check(frame.faces.count == 2, @"multi-face mode retains both detections");
Check([Pixels(context, frame.image) isEqualToData:Pixels(context, image)], @"zero geometry controls preserve original pixels");
Check(Channel(context, frame.skinMask, 1, 1, 0) == 0, @"skin mask excludes background");
Check(Channel(context, frame.skinMask, 18, 54, 0) > 150, @"skin mask covers cheek");
Check(Channel(context, frame.eyeMask, 23, 71, 0) > 40, @"eye mask includes estimated eye region");
Check(Channel(context, frame.teethMask, 33, 42, 0) > 20, @"teeth mask includes inner mouth region");
PGBeautySettings unprotected = settings; unprotected.featureProtection = 0;
PGFaceFrame *withoutProtection = [processor processImage:image settings:unprotected time:kCMTimeZero error:&error];
Check(Channel(context, frame.skinMask, 23, 71, 0) < Channel(context, withoutProtection.skinMask, 23, 71, 0), @"feature protection removes eye from skin smoothing");
settings.singleFace = 1; settings.selectionX = 0.78; settings.selectionY = 0.45;
frame = [processor processImage:image settings:settings time:kCMTimeZero error:&error];
Check(frame.faces.count == 1 && frame.faces.firstObject.boundingBox.origin.x > 0.5, @"single-face selection uses normalized bottom-left point");
settings.selectionX = 0.25; settings.selectionY = 0.5;
settings.trackingEnabled = 1; settings.faceJumpGuardRange = 0.5;
CMTime first = CMTimeMake(1, 24), second = CMTimeMake(2, 24);
Check([processor analyzeImage:image settings:settings time:first error:&error], @"analysis stores first precise frame");
processor.detections = @[right];
Check([processor analyzeImage:image settings:settings time:second error:&error], @"analysis stores guarded missing-target frame");
NSUInteger detectionsBeforeRender = processor.detectionCount;
PGFaceFrame *secondFrame = [processor processImage:image settings:settings time:second error:&error];
PGFaceFrame *firstFrame = [processor processImage:image settings:settings time:CMTimeMake(2, 48) error:&error];
Check(secondFrame.faces.count == 0, @"jump guard rejects distant replacement face");
Check(firstFrame.faces.count == 1 && firstFrame.faces.firstObject.boundingBox.origin.x < 0.5, @"reverse render retrieves exact earlier frame");
Check(processor.detectionCount == detectionsBeforeRender, @"saved frame lookup does not re-run detection");
Check(Channel(context, secondFrame.skinMask, 33, 43, 0) == 0, @"rejected target has black mask");
Check([processor analyzeImage:image settings:settings time:CMTimeMake(3, 24) error:&error], @"analysis continues after a lost target");
Check([processor processImage:image settings:settings time:CMTimeMake(3, 24) error:&error].faces.count == 0, @"jump guard keeps last valid identity across a missed frame");
Check([processor processImage:image settings:settings time:CMTimeMake(7, 48) error:&error].faces.count == 0, @"unsaved frame detects current image with deterministic analyzed identity guard");
Check([processor analyzeImage:image settings:settings time:CMTimeMake(3, 1) error:&error], @"analysis tolerates a seek across lost detections");
Check([processor processImage:image settings:settings time:CMTimeMake(3, 1) error:&error].faces.count == 0, @"jump guard does not silently reacquire a stranger after a long gap");
NSData *archive = [processor trackingDataForTarget:0];
Check(archive.length > 0, @"tracking is persistable");
PGFaceFixture *restored = [[PGFaceFixture alloc] init];
[restored setTrackingData:archive forTarget:0];
PGFaceFrame *restoredFrame = [restored processImage:image settings:settings time:first error:&error];
Check(restoredFrame.faces.count == 1 && restored.detectionCount == 0, @"restored target serves exact frame without detections");
Check([Pixels(context, firstFrame.skinMask) isEqualToData:Pixels(context, restoredFrame.skinMask)], @"restored geometry reproduces feature masks");
Check([restored trackingDataForTarget:1] == nil && [restored trackingDataForTarget:2] == nil, @"three target stores are independent");
[restored setTrackingData:archive forTarget:2];
settings.targetIndex = 2;
Check([restored processImage:image settings:settings time:first error:&error].faces.count == 1, @"third target stores and restores separately");
settings.targetIndex = 0;
[restored processImage:image settings:settings time:CMTimeMake(4, 24) error:&error];
Check(restored.detectionCount == 1, @"missing exact time uses stateless detection");
[restored setTrackingData:[@"invalid archive" dataUsingEncoding:NSUTF8StringEncoding] forTarget:0];
Check([restored trackingDataForTarget:0] == nil, @"invalid archive is rejected without exception");
[restored resetTracking];
Check([restored trackingDataForTarget:2] == nil, @"reset clears all targets");
error = nil;
Check(![restored analyzeImage:image settings:settings time:kCMTimeInvalid error:&error] && error != nil, @"analysis rejects invalid times");
processor.detections = @[Face(CGRectMake(0.2, 0.15, 0.6, 0.7))];
settings = PGBeautyDefaults();
NSData *base = Pixels(context, image);
NSString *names[] = {@"thin", @"big eyes", @"short", @"narrow", @"chin", @"V", @"jaw", @"cheekbone", @"small head"};
for (NSUInteger i = 0; i < 9; i++) {
PGBeautySettings changed = settings;
float *controls[] = {&changed.faceThin, &changed.faceBigEye, &changed.faceShort, &changed.faceNarrow, &changed.faceChin, &changed.faceV, &changed.faceJaw, &changed.faceCheekbone, &changed.faceSmallHead};
*controls[i] = 1;
PGFaceFrame *warped = [processor processImage:image settings:changed time:kCMTimeZero error:&error];
Check(warped != nil && ![Pixels(context, warped.image) isEqualToData:base], [@"geometry control changes pixels: " stringByAppendingString:names[i]]);
Check(CGRectEqualToRect(warped.image.extent, image.extent), @"warp preserves image extent");
}
settings.faceShort = 0.8;
NSData *positive = Pixels(context, [processor processImage:image settings:settings time:kCMTimeZero error:&error].image);
settings.faceShort = -0.8;
Check(![positive isEqualToData:Pixels(context, [processor processImage:image settings:settings time:kCMTimeZero error:&error].image)], @"signed short-face control has distinct directions");
settings = PGBeautyDefaults(); settings.showFaceBoxes = 1;
frame = [processor processImage:image settings:settings time:kCMTimeZero error:&error];
Check(Channel(context, frame.overlayImage, 26, 64, 3) > 0, @"face outlines provide separate final-composite overlay");
CIImage *offset = [image imageByApplyingTransform:CGAffineTransformMakeTranslation(30, 40)];
frame = [processor processImage:offset settings:settings time:kCMTimeZero error:&error];
Check(CGRectEqualToRect(frame.image.extent, offset.extent), @"nonzero image origins survive masks and warp");
PGFaceProcessor *vision = [[PGFaceProcessor alloc] init];
CIImage *blank = [[CIImage imageWithColor:CIColor.grayColor] imageByCroppingToRect:CGRectMake(0, 0, 128, 128)];
frame = [vision processImage:blank settings:PGBeautyDefaults() time:kCMTimeZero error:&error];
Check(frame != nil && frame.faces.count == 0, @"real Vision request accepts blank image without false faces");
NSString *fixturePath = argc > 1 ? @(argv[1]) : @"tests/fixtures/astronaut.png";
CIImage *portrait = [CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:fixturePath]];
Check(portrait != nil, @"public-domain NASA portrait fixture opens");
settings = PGBeautyDefaults();
PGFaceFrame *portraitFrame = [vision processImage:portrait settings:settings time:kCMTimeZero error:&error];
Check(portraitFrame.faces.count > 0, @"real Vision detects astronaut portrait");
Check(CGRectEqualToRect(portraitFrame.skinMask.extent, portrait.extent) && CGRectEqualToRect(portraitFrame.teethMask.extent, portrait.extent) && CGRectEqualToRect(portraitFrame.eyeMask.extent, portrait.extent), @"all real portrait masks have finite input extent");
VNFaceObservation *actual = portraitFrame.faces.firstObject;
Check(actual.landmarks.allPoints.pointCount > 30 && actual.landmarks.leftEye.pointCount > 3, @"real Vision returns detailed facial landmarks");
const CGPoint *eyePoints = actual.landmarks.leftEye.normalizedPoints;
CGPoint eye = CGPointZero;
for (NSUInteger i = 0; i < actual.landmarks.leftEye.pointCount; i++) { eye.x += eyePoints[i].x; eye.y += eyePoints[i].y; }
eye.x /= actual.landmarks.leftEye.pointCount; eye.y /= actual.landmarks.leftEye.pointCount;
CGRect actualBox = actual.boundingBox;
eye.x = (actualBox.origin.x + eye.x * actualBox.size.width) * portrait.extent.size.width;
eye.y = (actualBox.origin.y + eye.y * actualBox.size.height) * portrait.extent.size.height;
Check(Channel(context, portraitFrame.eyeMask, floor(eye.x), floor(eye.y), 0) > 80, @"landmark-derived eye mask lands on real eye");
Check(Channel(context, portraitFrame.skinMask, floor(eye.x), floor(eye.y), 0) < 100, @"real facial feature mask excludes eye from smoothing");
settings.faceThin = 0.4; settings.faceBigEye = 0.25; settings.faceChin = 0.15; settings.faceSmallHead = 0.15;
settings.faceSmallHeadRange = 0.6; settings.showFaceBoxes = 1;
PGFaceFrame *beautified = [vision processImage:portrait settings:settings time:kCMTimeZero error:&error];
Check(beautified != nil && ![Pixels(context, beautified.image) isEqualToData:Pixels(context, portrait)], @"real face geometry changes portrait pixels");
Check(abs((int)Channel(context, beautified.image, 5, 300, 0) - (int)Channel(context, portrait, 5, 300, 0)) <= 1, @"face warp preserves distant portrait background pixels");
settings.trackingEnabled = 1; settings.singleFace = 1;
settings.selectionX = CGRectGetMidX(actualBox); settings.selectionY = CGRectGetMidY(actualBox);
Check([vision analyzeImage:portrait settings:settings time:first error:&error], @"real landmarks analyze into persistent geometry");
NSData *realArchive = [vision trackingDataForTarget:0];
PGFaceFrame *analyzed = [vision processImage:portrait settings:settings time:first error:&error];
PGFaceProcessor *realRestored = [[PGFaceProcessor alloc] init];
[realRestored setTrackingData:realArchive forTarget:0];
PGFaceFrame *restoredPortrait = [realRestored processImage:portrait settings:settings time:first error:&error];
Check(restoredPortrait.faces.count == analyzed.faces.count && [Pixels(context, restoredPortrait.image) isEqualToData:Pixels(context, analyzed.image)], @"real face warp survives persistence byte-for-byte");
Check([Pixels(context, restoredPortrait.eyeMask) isEqualToData:Pixels(context, analyzed.eyeMask)], @"real landmark eye mask survives persistence byte-for-byte");
Check([Pixels(context, restoredPortrait.teethMask) isEqualToData:Pixels(context, analyzed.teethMask)], @"real landmark teeth mask survives persistence byte-for-byte");
if (argc > 2) {
CGFloat width = portrait.extent.size.width, height = portrait.extent.size.height;
CIImage *after = [[beautified.overlayImage imageByCompositingOverImage:beautified.image] imageByCroppingToRect:portrait.extent];
CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
CGContextRef canvas = CGBitmapContextCreate(NULL, width * 3, height, 8, width * 3 * 4, space, (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
NSArray *panels = @[portrait, after, portraitFrame.skinMask];
for (NSUInteger panel = 0; panel < panels.count; panel++) {
CGImageRef content = [context createCGImage:panels[panel] fromRect:portrait.extent];
CGContextDrawImage(canvas, CGRectMake(panel * width, 0, width, height), content);
CGImageRelease(content);
}
CGImageRef preview = CGBitmapContextCreateImage(canvas);
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:@(argv[2])], CFSTR("public.png"), 1, NULL);
Check(destination != NULL && preview != NULL, @"portrait comparison preview allocates");
CGImageDestinationAddImage(destination, preview, NULL);
Check(CGImageDestinationFinalize(destination), @"portrait comparison preview writes PNG");
CFRelease(destination); CGImageRelease(preview);
CGContextRelease(canvas); CGColorSpaceRelease(space);
}
printf("Face processing: %lu checks passed\n", (unsigned long)PGChecks);
}
return 0;
}
+6
查看文件
@@ -0,0 +1,6 @@
astronaut.png — NASA photograph of astronaut Eileen Collins.
Obtained from the scikit-image v0.24.0 sample-data distribution:
https://raw.githubusercontent.com/scikit-image/scikit-image/v0.24.0/skimage/data/astronaut.png
The upstream project states that this image has no known copyright restrictions and is in the public domain:
https://scikit-image.org/docs/stable/api/skimage.data.html#skimage.data.astronaut
Used solely as a local image-processing regression fixture; no endorsement implied.
二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 773 KiB