大道如青天, я один не могу выйти
В предыдущей статье «iOS Core ML и Vision: первое знакомство» мы получили первоначальное представление о функции vision и в конце оставили вопрос о назначении некоторых функций:
- (instancetype)initWithCIImage:(CIImage *)image options:(NSDictionary<VNImageOption, id> *)options;
- (instancetype)initWithCVPixelBuffer:(CVPixelBufferRef)pixelBuffer options:(NSDictionary<VNImageOption, id> *)options;
После изучения некоторых материалов мы наконец поняли, как эти функции работают.
Вот результат их использования:
!face.gif
Да, всё верно, это результат работы функции initWithCVPixelBuffer. Конечно, возможности vision этим не ограничиваются. Вот ещё несколько эффектов:
Изучив файл VNRequestHandler.h, мы можем увидеть все инициализирующие функции. Эти функции позволяют понять, какие типы изображений поддерживаются:
При использовании Vision необходимо сначала определить, какой эффект вам нужен, а затем выбрать подходящий класс в зависимости от желаемого эффекта. Наконец, нужно реализовать свой собственный эффект.
!request.jpeg
[handler performRequests:@[requset] error:&error];
Отношения наследования между результатами Vision показаны ниже: !Vision результаты наследования.png
После выполнения этих шагов вы можете реализовать желаемый эффект на основе результатов.
Здесь нам нужно использовать VNDetectFaceRectanglesRequest:
requset = [[VNDetectFaceRectanglesRequest alloc] initWithCompletionHandler:completionHandler];
Получив результат, нам нужно обработать координаты:
for (VNFaceObservation *faceObservation in observations) {
//boundingBox
CGRect transFrame = [self convertRect:faceObservation.boundingBox imageSize:image.size];
[rects addObject:[NSValue valueWithCGRect:transFrame]];
}
// Преобразование Rect
- (CGRect)convertRect:(CGRect)boundingBox imageSize:(CGSize)imageSize{
CGFloat w = boundingBox.size.width * imageSize.width;
CGFloat h = boundingBox.size.height * imageSize.height;
CGFloat x = boundingBox.origin.x * imageSize.width;
CGFloat y = imageSize.height * (1 - boundingBox.origin.y - boundingBox.size.height);//- (boundingBox.origin.y * imageSize.height) - h;
return CGRectMake(x, y, w, h);
}
Координаты в возвращаемом значении boundingBox нельзя использовать сразу, их нужно преобразовать, поскольку они относятся к изображению в определённом масштабе. Здесь следует обратить внимание на преобразование координаты y, так как ось y в координатах и ось UIView имеют противоположные направления. Наконец, нарисуем прямоугольник на основе полученных координат:
+ (UIImage *)gl_drawImage:(UIImage *)image withRects:(NSArray *)rects
{
UIImage *newImage = nil;
UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineCap(context,kCGLineCapRound); //边缘样式
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineWidth(context,2); //线宽
CGContextSetAllowsAntialiasing(context,YES); //打开抗锯齿
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
//绘制图片
[image drawInRect:CGRectMake(0, 0,image.size.width, image.size.height)];
CGContextBeginPath(context);
for (int i = 0; i < rects.count; i ++) {
CGRect rect = [rects[i] CGRectValue];
CGPoint sPoints[4];//坐标点
sPoints[0] = CGPointMake(rect.origin.x, rect.origin.y);//坐标1
sPoints[1] = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y);//坐标2
sPoints[2] = CGPointMake(rect.origin.x + rect.size.width,
``` ```
rect.origin.y + rect.size.height);//координата 3
sPoints[3] = CGPointMake(rect.origin.x , rect.origin.y + rect.size.height);
CGContextAddLines(context, sPoints, 4);//добавить линию
CGContextClosePath(context); //закрыть
}
CGContextDrawPath(context, kCGPathFillStroke); //нарисовать путь по координатам
newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
Эффект:
Изображение отсутствует.
Здесь мы используем VNDetectFaceLandmarksRequest:
requset = [[VNDetectFaceLandmarksRequest alloc] initWithCompletionHandler:completionHandler];
Обработка результатов:
for (VNFaceObservation *faceObservation in observations) {
//boundingBox
CGRect transFrame = [self convertRect:faceObservation.boundingBox imageSize:image.size];
[rects addObject:[NSValue valueWithCGRect:transFrame]];
}
pointModel.faceRectPoints = rects;
return pointModel;
}
- (GLDiscernPointModel *)handlerFaceLandMark:(NSArray *)observations image:(UIImage *)image
{
GLDiscernPointModel *pointModel = [[GLDiscernPointModel alloc] init];
NSMutableArray *rects = @[].mutableCopy;
for (VNFaceObservation *faceObservation in observations) {
VNFaceLandmarks2D *faceLandMarks2D = faceObservation.landmarks;
[self getKeysWithClass:[VNFaceLandmarks2D class] block:^(NSString *key) {
if ([key isEqualToString:@"allPoints"]) {
return ;
}
VNFaceLandmarkRegion2D *faceLandMarkRegion2D = [faceLandMarks2D valueForKey:key];
NSMutableArray *sPoints = [[NSMutableArray alloc] initWithCapacity:faceLandMarkRegion2D.pointCount];
for (int i = 0; i < faceLandMarkRegion2D.pointCount; i ++) {
CGPoint point = faceLandMarkRegion2D.normalizedPoints[i];
CGFloat rectWidth = image.size.width * faceObservation.boundingBox.size.width;
CGFloat rectHeight = image.size.height * faceObservation.boundingBox.size.height;
CGPoint p = CGPointMake(point.x * rectWidth + faceObservation.boundingBox.origin.x * image.size.width, faceObservation.boundingBox.origin.y * image.size.height + point.y * rectHeight);
[sPoints addObject:[NSValue valueWithCGPoint:p]];
}
[rects addObject:sPoints];
}];
}
Здесь следует обратить внимание на атрибут landmarks, который является объектом типа VNFaceLandmarks2D и содержит множество объектов VNFaceLandmarkRegion2D, представляющих черты лица, такие как контур лица, левый глаз, нос и т. д. Эти объекты содержат свойство:
@property (readonly, assign, nullable) const CGPoint* normalizedPoints
Это массив, содержащий координаты данной черты лица. Мы можем получить эти координаты следующим образом:
CGPoint point = faceLandMarkRegion2D.normalizedPoints[i];
Конечно, здесь также происходит преобразование координат, как видно из приведенного выше кода. Наконец, мы также рисуем линии, код выглядит следующим образом:
+ (UIImage *)gl_drawImage:(UIImage *)image faceLandMarkPoints:(NSArray *)landMarkPoints
{
UIImage * newImage = image;
for (NSMutableArray *points in landMarkPoints) {
CGPoint sPoints [points.count];
for (int i = 0;i <points.count;i++) {
NSValue *pointValue = points[i];
CGPoint point = pointValue.CGPointValue;
sPoints[i] = point;
}
//рисуем линию
UIGraphicsBeginImageContextWithOptions(newImage.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineCap(context,kCGLineCapRound); //стиль края
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineWidth(context,2); //ширина линии
CGContextSetAllowsAntialiasing(context,YES); //включить сглаживание
// установить поворот
CGContextTranslateCTM(context, 0, newImage.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
``` **Текст запроса:**
rectHeight);
}
sPoints[i] = p;
[pointXs addObject:[NSNumber numberWithFloat:p.x]];
[pointYs addObject:[NSNumber numberWithFloat:p.y]];
}
for (int j = 0; j < rightEyefaceLandMarkRegion2D.pointCount; j ++) {
CGPoint point = rightEyefaceLandMarkRegion2D.normalizedPoints[j];
CGFloat rectWidth = self.view.bounds.size.width * faceObservation.boundingBox.size.width;
CGFloat rectHeight = self.view.bounds.size.height * faceObservation.boundingBox.size.height;
CGFloat boundingBoxY = self.view.bounds.size.height * (1 - faceObservation.boundingBox.origin.y - faceObservation.boundingBox.size.height);
CGPoint p = CGPointZero;
if (position == AVCaptureDevicePositionFront){
CGFloat boundingX = self.view.frame.size.width - faceObservation.boundingBox.origin.x * self.view.bounds.size.width - rectWidth;
p = CGPointMake(point.x * rectWidth + boundingX, boundingBoxY + (1-point.y) * rectHeight);
}else{
p = CGPointMake(point.x * rectWidth + faceObservation.boundingBox.origin.x * self.view.bounds.size.width, boundingBoxY + (1-point.y) * rectHeight);
}
sPoints[leftEyefaceLandMarkRegion2D.pointCount + j] = p;
[pointXs addObject:[NSNumber numberWithFloat:p.x]];
[pointYs addObject:[NSNumber numberWithFloat:p.y]];
}
**Перевод текста на русский язык:**
rectHeight);
}
sPoints[i] = p;
[pointXs addObject:[NSNumber numberWithFloat:p.x]];
[pointYs addObject:[NSNumber numberWithFloat:p.y]];
}
for (int j = 0; j < rightEyefaceLandMarkRegion2D.pointCount; j++) {
CGPoint point = rightEyefaceLandMarkRegion2D.normalizedPoints[j];
CGFloat rectWidth = self.view.bounds.size.width * faceObservation.boundingBox.size.width;
CGFloat rectHeight = self.view.bounds.size.height * faceObservation.boundingBox.size.height;
CGFloat boundingBoxY = self.view.bounds.size.height * (1 - faceObservation.boundingBox.origin.y - faceObservation.boundingBox.size.height);
CGPoint p = CGPointZero;
if (position == AVCaptureDevicePositionFront) {
CGFloat boundingX = self.view.frame.size.width - faceObservation.boundingBox.origin.x * self.view.bounds.size.width - rectWidth;
p = CGPointMake(point.x * rectWidth + boundingX, boundingBoxY + (1 - point.y) * rectHeight);
} else {
p = CGPointMake(point.x * rectWidth + faceObservation.boundingBox.origin.x * self.view.bounds.size.width, boundingBoxY + (1 - point.y) * rectHeight);
}
sPoints[leftEyefaceLandMarkRegion2D.pointCount + j] = p;
[pointXs addObject:[NSNumber numberWithFloat:p.x]];
[pointYs addObject:[NSNumber numberWithFloat:p.y]];
} ```
int value1 = [obj1 floatValue];
int value2 = [obj2 floatValue];
if (value1 > value2) {
return NSOrderedDescending;
} else if (value1 == value2){
return NSOrderedSame;
} else {
return NSOrderedAscending;
}
UIImage *image =[UIImage imageNamed:@"eyes"];
CGFloat imageWidth = [sortPointXs.lastObject floatValue] - [sortPointXs.firstObject floatValue] + 40;
CGFloat imageHeight = (imageWidth * image.size.height)/image.size.width;
self.glassesImageView.frame = CGRectMake([sortPointXs.firstObject floatValue]-20, [sortPointYs.firstObject floatValue]-5, imageWidth, imageHeight);
Здесь происходит динамическое добавление очков. Сначала необходимо получить координаты двух глаз, затем вычислить ширину и высоту очков, после чего соответствующим образом настроить размер очков и динамически добавить их.
В процессе реализации возникла проблема с координатами. В частности, для координаты y было сделано предположение, что это связано с особенностями формирования изображения камерой. Поэтому было принято решение изменить способ вычисления:
CGFloat boundingBoxY = self.view.bounds.size.height * (1 - faceObservation.boundingBox.origin.y - faceObservation.boundingBox.size.height);
p = CGPointMake(point.x * rectWidth + faceObservation.boundingBox.origin.x * self.view.bounds.size.width, boundingBoxY + (1-point.y) * rectHeight);
Для координаты x при использовании передней камеры всё работает нормально, но при переключении на заднюю камеру возникает обратная ситуация. Автор столкнулся с трудностями в понимании этого процесса и продолжает исследования.
Также была добавлена информация о том, что при использовании метода обнаружения изображений потребление памяти и процессора остаётся высоким, и что направление изображения имеет значение.
- (instancetype)initWithCVPixelBuffer:(CVPixelBufferRef)pixelBuffer options:(NSDictionary<VNImageOption, id> *)options;
/*!
@brief initWithCVPixelBuffer:options creates a VNImageRequestHandler to be used for performing requests against the image passed in as buffer.
@param pixelBuffer A CVPixelBuffer containing the image to be used for performing the requests. The content of the buffer cannot be modified for the lifetime of the VNImageRequestHandler.
@param orientation The orientation of the image/buffer based on the EXIF specification. For details see kCGImagePropertyOrientation. The value has to be an integer from 1 to 8. This superceeds every other orientation information.
@param options A dictionary with options specifying auxilary information for the buffer/image like VNImageOptionCameraIntrinsics
*/
- (instancetype)initWithCVPixelBuffer:(CVPixelBufferRef)pixelBuffer orientation:(CGImagePropertyOrientation)orientation options:(NSDictionary<VNImageOption, id> *)options;
Было отмечено, что для корректной работы метода обнаружения необходимо указать ориентацию изображения, иначе результат может быть неверным.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )