Skip to content

Commit

Permalink
Adding facial recognition back in
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlawson committed Nov 4, 2013
1 parent d36b17c commit 1867791
Show file tree
Hide file tree
Showing 11 changed files with 293 additions and 7 deletions.
6 changes: 6 additions & 0 deletions PsyPad.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
5AF0E5C1204C19BECA4857F7 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5AF0E75B36ED26022AF3F4E5 /* MobileCoreServices.framework */; };
5AF0E63465BCA419C9918EE4 /* Staircase.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0EDECD815728F1903E396 /* Staircase.m */; };
5AF0E6552E4BAE482FA33C0F /* ManageUserTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0EDF2375B76A42DA5EF62 /* ManageUserTableViewController.m */; };
5AF0E8592B9AD503599993C7 /* DistanceDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0E590046B35D9DE7819C4 /* DistanceDetector.m */; };
5AF0EA20E93B462604540281 /* TestSequenceFolder.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0E49ABC79866125DC819F /* TestSequenceFolder.m */; };
5AF0EA6A20242EC8C33302E2 /* APIController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0EC3D081D2F2B5BC0573F /* APIController.m */; };
5AF0EB2DD285A8F2E6DF60B8 /* AdminPanelTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0EB2270778FECB039C261 /* AdminPanelTableViewController.m */; };
Expand Down Expand Up @@ -99,6 +100,7 @@
5AF0E3A1AD1029A57F63B68C /* APIController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APIController.h; sourceTree = "<group>"; };
5AF0E49ABC79866125DC819F /* TestSequenceFolder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestSequenceFolder.m; sourceTree = "<group>"; };
5AF0E52A944A57021D9519D3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
5AF0E590046B35D9DE7819C4 /* DistanceDetector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DistanceDetector.m; sourceTree = "<group>"; };
5AF0E61F0BEAD03448FE32BD /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; };
5AF0E64D67CE65EBACC34616 /* TestLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestLog.m; sourceTree = "<group>"; };
5AF0E6679284694FAE8FA987 /* AdminPanelTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AdminPanelTableViewController.h; sourceTree = "<group>"; };
Expand All @@ -117,6 +119,7 @@
5AF0EBDDCF9D7178C5BCA732 /* TestSequenceFolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestSequenceFolder.h; sourceTree = "<group>"; };
5AF0EC2AE3C7CB7009391ED1 /* TestSequenceImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestSequenceImage.h; sourceTree = "<group>"; };
5AF0EC3D081D2F2B5BC0573F /* APIController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APIController.m; sourceTree = "<group>"; };
5AF0ED3AA80AD1B4E5497DEB /* DistanceDetector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DistanceDetector.h; sourceTree = "<group>"; };
5AF0ED62E745BBD70B82E167 /* TestSequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestSequence.h; sourceTree = "<group>"; };
5AF0ED9AEF05FF5F440123DA /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
5AF0EDAFEE8AD326C5F4C401 /* TestSequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestSequence.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -291,6 +294,8 @@
5AF0E29AD0FE381FB2090214 /* Custom Controls */,
5AF0EDECD815728F1903E396 /* Staircase.m */,
5AF0E312DFA8457A95F447AE /* Staircase.h */,
5AF0ED3AA80AD1B4E5497DEB /* DistanceDetector.h */,
5AF0E590046B35D9DE7819C4 /* DistanceDetector.m */,
);
path = Test;
sourceTree = "<group>";
Expand Down Expand Up @@ -714,6 +719,7 @@
63A1ED6017B89C100089C204 /* UserTableViewCell.m in Sources */,
5AF0E0005D2CBAD344805694 /* UIColor+Hex.m in Sources */,
5AF0EA6A20242EC8C33302E2 /* APIController.m in Sources */,
5AF0E8592B9AD503599993C7 /* DistanceDetector.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion PsyPad/Configuration/TestConfigTableViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
@property (weak, nonatomic) IBOutlet UITextField *button4FG;

@property (weak, nonatomic) IBOutlet UISwitch *requireNextSwitch;

@property (weak, nonatomic) IBOutlet UISwitch *attemptFacialRecognition;

@property (weak, nonatomic) IBOutlet UILabel *timeBetweenEachQuestionMeanLabel;
@property (weak, nonatomic) IBOutlet UISlider *timeBetweenEachQuestionMean;
Expand Down
3 changes: 3 additions & 0 deletions PsyPad/Configuration/TestConfigTableViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ - (IBAction)saveChanges:(id)sender
conf.button4_fg = self.button4FG.text;

conf.require_next = @(self.requireNextSwitch.on);
conf.attempt_facial_recognition = @(self.attemptFacialRecognition.on);

conf.time_between_question_mean = @(self.timeBetweenEachQuestionMean.value);
conf.time_between_question_plusminus = @(self.timeBetweenEachQuestionPlusMinus.value);
Expand Down Expand Up @@ -220,6 +221,7 @@ - (void)populateFields
self.button4FG.text = conf.button4_fg;

self.requireNextSwitch.on = conf.require_next.boolValue;
self.attemptFacialRecognition.on = conf.attempt_facial_recognition.boolValue;

self.timeBetweenEachQuestionMean.value = conf.time_between_question_mean.floatValue;
self.timeBetweenEachQuestionMeanLabel.text = [NSString stringWithFormat:@"%.2fs", conf.time_between_question_mean.floatValue];
Expand Down Expand Up @@ -438,6 +440,7 @@ - (void)viewDidUnload
[self setButton3FG:nil];
[self setButton4BG:nil];
[self setButton4FG:nil];
[self setAttemptFacialRecognition:nil];
[self setTimeBetweenEachQuestionMean:nil];
[self setTimeBetweenEachQuestionPlusMinus:nil];
[self setTimeBetweenEachQuestionMeanLabel:nil];
Expand Down
1 change: 1 addition & 0 deletions PsyPad/Database/Model.xcdatamodel/contents
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</entity>
<entity name="TestConfiguration" representedClassName="TestConfiguration" syncable="YES">
<attribute name="animation_frame_rate" attributeType="Integer 16" defaultValueString="50" syncable="YES"/>
<attribute name="attempt_facial_recognition" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="background_colour" attributeType="String" defaultValueString="#000000" syncable="YES"/>
<attribute name="button_text_four" attributeType="String" defaultValueString="Four" syncable="YES"/>
<attribute name="button_text_one" attributeType="String" defaultValueString="One" syncable="YES"/>
Expand Down
1 change: 1 addition & 0 deletions PsyPad/Database/TestConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// position stored with relationship

@property (nonatomic, retain) NSNumber * animation_frame_rate;
@property (nonatomic, retain) NSNumber * attempt_facial_recognition;
@property (nonatomic, retain) NSString * background_colour;
@property (nonatomic, retain) NSString * button_text_four;
@property (nonatomic, retain) NSString * button_text_one;
Expand Down
7 changes: 7 additions & 0 deletions PsyPad/Database/TestConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@implementation TestConfiguration

@dynamic animation_frame_rate;
@dynamic attempt_facial_recognition;
@dynamic background_colour;
@dynamic button_text_four;
@dynamic button_text_one;
Expand Down Expand Up @@ -472,6 +473,11 @@ - (void)loadData:(NSDictionary *)data

self.randomisation_specified_seed =
@([(NSString *)[data objectForKey:@"specified_seed"] intValue]);

if ([(NSString *)[data objectForKey:@"attempt_facial_recognition"] isEqualToString:@"1"])
self.attempt_facial_recognition = @YES;
else
self.attempt_facial_recognition = @NO;
}

- (NSDictionary *)serialise
Expand Down Expand Up @@ -564,6 +570,7 @@ - (NSDictionary *)serialise
[data setObject:self.images_together_presentation_time forKey:@"presentation_time"];
[data setObject:self.randomisation_use_specified_seed forKey:@"use_specified_seed"];
[data setObject:self.randomisation_specified_seed forKey:@"specified_seed"];
[data setObject:self.attempt_facial_recognition forKey:@"attempt_facial_recognition"];

NSLog(@"%@", data);

Expand Down
26 changes: 26 additions & 0 deletions PsyPad/Test/DistanceDetector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// Created by david on 27/02/13.
//
// To change the template use AppCode | Preferences | File Templates.
//

#import <Foundation/Foundation.h>

@class AVCaptureSession;
@class AVCaptureVideoPreviewLayer;
@class AVCaptureStillImageOutput;
@class AVCaptureConnection;
@class TestViewController;

@interface DistanceDetector : NSObject

@property (strong, nonatomic) AVCaptureSession *session;
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;
@property (strong, nonatomic) AVCaptureStillImageOutput *stillImageOutput;
@property (strong, nonatomic) AVCaptureConnection *videoConnection;
@property (strong, nonatomic) UIImage *photo;

- (void)takePhoto:(TestViewController *)delegate question:(int)question;

- (void)done;
@end
209 changes: 209 additions & 0 deletions PsyPad/Test/DistanceDetector.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
//
// Created by david on 27/02/13.
//
// To change the template use AppCode | Preferences | File Templates.
//

#import <CoreMedia/CoreMedia.h>
#import <AVFoundation/AVFoundation.h>
#import "DistanceDetector.h"
#import "TestViewController.h"
#import "AppDelegate.h"

@implementation DistanceDetector

- (id)init
{
if (self = [super init])
{
self.session = [[AVCaptureSession alloc] init];
self.session.sessionPreset = AVCaptureSessionPresetHigh;

UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];

AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
orientation = AVCaptureVideoOrientationLandscapeLeft;
else if (interfaceOrientation == UIInterfaceOrientationLandscapeRight)
orientation = AVCaptureVideoOrientationLandscapeRight;

// Get all cameras in the application and find the frontal camera.
AVCaptureDevice *frontalCamera;
NSArray *allCameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

// Find the frontal camera.
for ( int i = 0; i < allCameras.count; i++ )
{
AVCaptureDevice *camera = [allCameras objectAtIndex:(NSUInteger)i];

if ( camera.position == AVCaptureDevicePositionFront )
{
frontalCamera = camera;
}
}

[frontalCamera lockForConfiguration:nil];

if ([frontalCamera isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus])
{
[frontalCamera setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}

if ([frontalCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
{
[frontalCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}

if ([frontalCamera isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance])
{
[frontalCamera setWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance];
}

[frontalCamera unlockForConfiguration];

NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:frontalCamera error:&error];

if (input)
{
[self.session addInput:input];
[self.session startRunning];

// output

self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
[self.session addOutput:self.stillImageOutput];

self.videoConnection = nil;
for (AVCaptureConnection *connection in self.stillImageOutput.connections)
{
for (AVCaptureInputPort *port in [connection inputPorts])
{
if ([[port mediaType] isEqual:AVMediaTypeVideo] )
{
self.videoConnection = connection;
break;
}
}

if (self.videoConnection) { break; }
}

[self.videoConnection setVideoOrientation:orientation];
[self.videoConnection setVideoMirrored:YES];
}
}

return self;
}

- (void)takePhoto:(TestViewController *)delegate question:(int)question
{
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:self.videoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error)
{
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];

//[imageData writeToURL:[self createNewFile] atomically:YES];

UIImage *image = [[UIImage alloc] initWithData:imageData];
self.photo = image;
NSString *result = [self performDistanceDetection];

//UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);

if (result)
{
[delegate distanceDetectionPerformed:[NSString stringWithFormat:@"Question %d: %@", question, result]];
}

//[self.session removeOutput:stillImageOutput];
}];
}

/*- (NSURL *)createNewFile
{
NSURL *documentsDirectory = [[APP_DELEGATE applicationDocumentsDirectory] URLByAppendingPathComponent:@"Images"];
NSString *templateString = [NSString stringWithFormat:@"%@/XXXXXX", [documentsDirectory path]];
char template[templateString.length + 1];
strcpy(template, [templateString cStringUsingEncoding:NSASCIIStringEncoding]);
char *filename = mktemp(template);
NSString *path = [NSString stringWithCString:filename encoding:NSASCIIStringEncoding];
NSURL *newURL = [NSURL fileURLWithPath:[path stringByAppendingString:@".jpg"]];
[[NSFileManager defaultManager] createFileAtPath:newURL.path contents:nil attributes:nil];
return newURL;
}*/

- (void)done
{
[self.session removeOutput:self.stillImageOutput];
}

- (NSString *)performDistanceDetection
{
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];

#warning accuracy low
NSDictionary *options = [NSDictionary dictionaryWithObject:CIDetectorAccuracyLow forKey:CIDetectorAccuracy];
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace context:nil options:options];

CIImage *image = [CIImage imageWithCGImage:self.photo.CGImage];

int exifOrientation;

enum {
PHOTOS_EXIF_0ROW_TOP_0COL_LEFT = 1, // 1 = 0th row is at the top, and 0th column is on the left (THE DEFAULT).
PHOTOS_EXIF_0ROW_TOP_0COL_RIGHT = 2, // 2 = 0th row is at the top, and 0th column is on the right.
PHOTOS_EXIF_0ROW_BOTTOM_0COL_RIGHT = 3, // 3 = 0th row is at the bottom, and 0th column is on the right.
PHOTOS_EXIF_0ROW_BOTTOM_0COL_LEFT = 4, // 4 = 0th row is at the bottom, and 0th column is on the left.
PHOTOS_EXIF_0ROW_LEFT_0COL_TOP = 5, // 5 = 0th row is on the left, and 0th column is the top.
PHOTOS_EXIF_0ROW_RIGHT_0COL_TOP = 6, // 6 = 0th row is on the right, and 0th column is the top.
PHOTOS_EXIF_0ROW_RIGHT_0COL_BOTTOM = 7, // 7 = 0th row is on the right, and 0th column is the bottom.
PHOTOS_EXIF_0ROW_LEFT_0COL_BOTTOM = 8 // 8 = 0th row is on the left, and 0th column is the bottom.
};

switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeRight: // Device oriented horizontally, home button on the right
exifOrientation = PHOTOS_EXIF_0ROW_BOTTOM_0COL_RIGHT;
break;
case UIInterfaceOrientationLandscapeLeft: // Device oriented horizontally, home button on the left
default:
exifOrientation = PHOTOS_EXIF_0ROW_TOP_0COL_LEFT;
break;
}

NSDictionary *imageOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:exifOrientation] forKey:CIDetectorImageOrientation];

NSArray *features = [detector featuresInImage:image options:imageOptions];

for (CIFaceFeature *feature in features)
{
CGPoint leftEyePosition, rightEyePosition, mouthPosition;

if (feature.hasLeftEyePosition && feature.hasRightEyePosition && feature.hasMouthPosition)
{
leftEyePosition = feature.leftEyePosition;
rightEyePosition = feature.rightEyePosition;
mouthPosition = feature.mouthPosition;

//CGFloat xDist = (rightEyePosition.x - leftEyePosition.x);
//CGFloat yDist = (rightEyePosition.y - leftEyePosition.y);
//CGFloat distance = (CGFloat)sqrt((xDist * xDist) + (yDist * yDist));

//CGFloat calibration_pixel = 142;
//CGFloat calibration_distance = 30;

//return [NSString stringWithFormat:@"Detected distance from camera: %.5fpixels = %.5fcm", distance, (calibration_pixel*calibration_distance)/distance];

return [NSString stringWithFormat:@"Detected facial features: left eye %.5f/%.5f, right eye: %.5f/%.5f, mouth: %.5f/%.5f", leftEyePosition.x, leftEyePosition.y, rightEyePosition.x, rightEyePosition.y, mouthPosition.x, mouthPosition.y];
}
}

return nil;
}

@end
8 changes: 8 additions & 0 deletions PsyPad/Test/TestViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#import "AppConfiguration.h"

#import "UIColor+Hex.h"
#import "DistanceDetector.h"

// Shortcuts for the view size
#define VIEW_HEIGHT self.view.bounds.size.height
Expand Down Expand Up @@ -84,6 +85,8 @@ - (void)viewDidLoad
self.exitButton.hidden = YES;
}

self.distanceDetector = [[DistanceDetector alloc] init];

[[UIScreen mainScreen] setBrightness:1.0];
}

Expand Down Expand Up @@ -464,6 +467,9 @@ - (void)pressTestButton:(id)sender

[self log:@"button_press" info:@"%d (%@)", pressedButton.number, [pressedButton titleForState:UIControlStateNormal]];

if (self.currentConfiguration.attempt_facial_recognition.boolValue)
[self.distanceDetector takePhoto:self question:self.questionNumber];

if (self.currentConfiguration.use_staircase_method.boolValue)
{
bool answerCorrect;
Expand Down Expand Up @@ -905,6 +911,8 @@ - (void)viewDidUnload
{
free(self.seedState);

[self.distanceDetector done];

[self setBeginTestButton:nil];
[self setQuestionLabel:nil];
[self setConfigurationNameLabel:nil];
Expand Down
Loading

0 comments on commit 1867791

Please sign in to comment.