Open shohu opened 4 years ago
did you find any solution on this? i'm having the same problem
I gave up using this library. I decided to try to use CIFaceFeature each sampleBuffer frame. But poor accuracy
I managed to fix this issue; just 4 years after. What a long time it took me solve this. Jokes aside, it actually took a decent time to figure this out:
If you go through the instructions provided in https://github.com/zweigraf/face-landmarking-ios/issues/5 You'll be able to quickly figure out that there's no convertScaleCGRect function in the recent version of this code base.
That's because the author has pushed a "simplified version" of DlibWrapper class, so I had to go through the previous commit history and I found it (the one in May 2016).
First and foremost, replace the entirety of your DlibWrapper.mm file to the following:
//
// DlibWrapper.m
// DisplayLiveSamples
//
// Created by Luis Reisewitz on 16.05.16.
// Copyright Β© 2016 ZweiGraf. All rights reserved.
//
#import "DlibWrapper.h"
#import <UIKit/UIKit.h>
#include <dlib/image_processing.h>
#include <dlib/image_io.h>
@interface DlibWrapper ()
@property (assign) BOOL prepared;
+ (dlib::rectangle)convertScaleCGRect:(CGRect)rect toDlibRectacleWithImageSize:(CGSize)size;
+ (std::vector<dlib::rectangle>)convertCGRectValueArray:(NSArray<NSValue *> *)rects toVectorWithImageSize:(CGSize)size;
@end
@implementation DlibWrapper {
dlib::shape_predictor sp;
}
-(instancetype)init {
self = [super init];
if (self) {
_prepared = NO;
}
return self;
}
- (void)prepare {
NSString *modelFileName = [[NSBundle mainBundle] pathForResource:@"shape_predictor_68_face_landmarks" ofType:@"dat"];
std::string modelFileNameCString = [modelFileName UTF8String];
dlib::deserialize(modelFileNameCString) >> sp;
// FIXME: test this stuff for memory leaks (cpp object destruction)
self.prepared = YES;
}
-(void)doWorkOnSampleBuffer:(CMSampleBufferRef)sampleBuffer inRects:(NSArray<NSValue *> *)rects {
if (!self.prepared) {
[self prepare];
}
dlib::array2d<dlib::bgr_pixel> img;
// MARK: magic
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
char *baseBuffer = (char *)CVPixelBufferGetBaseAddress(imageBuffer);
// set_size expects rows, cols format
img.set_size(height, width);
// copy samplebuffer image data into dlib image format
img.reset();
long position = 0;
while (img.move_next()) {
dlib::bgr_pixel& pixel = img.element();
// assuming bgra format here
long bufferLocation = position * 4; //(row * width + column) * 4;
char b = baseBuffer[bufferLocation];
char g = baseBuffer[bufferLocation + 1];
char r = baseBuffer[bufferLocation + 2];
// we do not need this
// char a = baseBuffer[bufferLocation + 3];
dlib::bgr_pixel newpixel(b, g, r);
pixel = newpixel;
position++;
}
// unlock buffer again until we need it again
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
CGSize imageSize = CGSizeMake(width, height);
// convert the face bounds list to dlib format
std::vector<dlib::rectangle> convertedRectangles = [DlibWrapper convertCGRectValueArray:rects toVectorWithImageSize:imageSize];
// for every detected face
for (unsigned long j = 0; j < convertedRectangles.size(); ++j)
{
dlib::rectangle oneFaceRect = convertedRectangles[j];
// detect all landmarks
dlib::full_object_detection shape = sp(img, oneFaceRect);
// and draw them into the image (samplebuffer)
for (unsigned long k = 0; k < shape.num_parts(); k++) {
dlib::point p = shape.part(k);
draw_solid_circle(img, p, 3, dlib::rgb_pixel(0, 255, 255));
}
}
// lets put everything back where it belongs
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// copy dlib image data back into samplebuffer
img.reset();
position = 0;
while (img.move_next()) {
dlib::bgr_pixel& pixel = img.element();
// assuming bgra format here
long bufferLocation = position * 4; //(row * width + column) * 4;
baseBuffer[bufferLocation] = pixel.blue;
baseBuffer[bufferLocation + 1] = pixel.green;
baseBuffer[bufferLocation + 2] = pixel.red;
// we do not need this
// char a = baseBuffer[bufferLocation + 3];
position++;
}
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}
+ (dlib::rectangle)convertScaleCGRect:(CGRect)rect toDlibRectacleWithImageSize:(CGSize)size {
long right = (1.0 - rect.origin.y ) * size.width;
long left = right - rect.size.height * size.width;
long top = rect.origin.x * size.height;
long bottom = top + rect.size.width * size.height;
dlib::rectangle dlibRect(left, top, right, bottom);
return dlibRect;
}
+ (std::vector<dlib::rectangle>)convertCGRectValueArray:(NSArray<NSValue *> *)rects toVectorWithImageSize:(CGSize)size {
std::vector<dlib::rectangle> myConvertedRects;
for (NSValue *rectValue in rects) {
CGRect singleRect = [rectValue CGRectValue];
dlib::rectangle dlibRect = [DlibWrapper convertScaleCGRect:singleRect toDlibRectacleWithImageSize:size];
myConvertedRects.push_back(dlibRect);
}
return myConvertedRects;
}
@end
I applied the changes made in the comments in issue #5 so that now it supports portrait mode.
Next change you need to make is... Go to SessionHandler and locate the following:
func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
print("DidDropSampleBuffer")
}
BE AWARE! There are TWO captureOutput(s) β you must choose the one which has NO code inside of the function block except for a simple print line. Then you wanna add the following inside the function: connection.videoOrientation = AVCaptureVideoOrientation.portrait
So the final version of SessionHandler's captureOutput function will look like the following:
func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
print("DidDropSampleBuffer")
connection.videoOrientation = AVCaptureVideoOrientation.portrait
}
BOOM. All issues have been resolved. OH BY THE WAY, add session.sessionPreset = AVCaptureSession.Preset.vga640x480
RIGHT BEFORE the session.startRunning()
line in ViewController.swift to enable legacy style video streaming (640x480 instead of 1024 something dimensions) so that there's LESS noise and instability in the landmark data. Lower resolution helps because it lowers the demand for the machine to handle.
Hope that helps, I know maybe I'm a little too late now that the Vision framework/ARKit framework is out, but in the case you're writing C++ code in tandem with Swift and want to import these stuff from C++ side too using Objective-C++ β this is a tutorial for you!
John Seong
Never mind β you're supposed to add connection.videoOrientation = AVCaptureVideoOrientation.portrait
to the OTHER captureOutput NOT the one I indicated above. Sorry.
ANOTHER UPDATE - just replace the whole captureOutput to the following:
// MARK: AVCaptureVideoDataOutputSampleBufferDelegate
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
connection.videoOrientation = AVCaptureVideoOrientation.portrait
if !currentMetadata.isEmpty {
let boundsArray = currentMetadata
.compactMap { $0 as? AVMetadataFaceObject }
.map { NSValue(cgRect: $0.bounds) }
wrapper?.doWork(on: sampleBuffer, inRects: boundsArray)
}
layer.enqueue(sampleBuffer)
}
You also have to tinker with changing the .map part to .map { NSValue(cgRect: $0.bounds) }
.
I can't draw correct circle at Portrait mode. I find shape, but not circle, just line. like this.
How can I draw correct circle for face landmark at Portrait mode?
I only fixed two things.
I checked #5 but I can't understand this problem.
My Environment
MacOS Catalina 10.15.2 Xcode 11.3.1 iPhone7 (iOS 13.3.1 commit version https://github.com/zweigraf/face-landmarking-ios/commit/748f2ea4a2c0b3b09f2f4a36b76c06c84117c2a9