1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/zymITsky-VisionDemo

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

大道如青天, я один не могу выйти

Введение

В предыдущей статье «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 этим не ограничиваются. Вот ещё несколько эффектов:

  1. Сопоставление изображений (эффект из предыдущей статьи).
  2. Обнаружение прямоугольников.
  3. Обнаружение QR-кодов и штрихкодов.
  4. Отслеживание объектов.
  5. Распознавание текста.
  6. Распознавание лиц.
  7. Распознавание черт лица. Поскольку распознавание лиц представляет для меня особый интерес, здесь я кратко расскажу об этой функции. Ниже рассмотрим распознавание и определение черт лица более подробно.

Типы изображений, поддерживаемые Vision

Изучив файл VNRequestHandler.h, мы можем увидеть все инициализирующие функции. Эти функции позволяют понять, какие типы изображений поддерживаются:

  1. CVPixelBufferRef.
  2. CGImageRef.
  3. CIImage.
  4. NSURL.
  5. NSData.

Использование Vision

При использовании Vision необходимо сначала определить, какой эффект вам нужен, а затем выбрать подходящий класс в зависимости от желаемого эффекта. Наконец, нужно реализовать свой собственный эффект.

  1. Вам понадобится RequestHandler, при создании которого необходимо указать подходящий источник ввода и тип изображения.
  2. При создании Request также необходимо выбрать его в соответствии с конкретной ситуацией. Существует несколько типов Request:

!request.jpeg

  1. Свяжите requestHandler с request, а затем получите результат:
[handler performRequests:@[requset] error:&error];
  1. Обработайте результат VNObservation. В массиве results класса VNRequest содержатся результаты VNObservation, которые могут быть разными в зависимости от типа вашего Request.

Отношения наследования между результатами 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 )

Вы можете оставить комментарий после Вход в систему

Введение

Особенности iOS 11: распознавание лиц. Развернуть Свернуть
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/zymITsky-VisionDemo.git
git@api.gitlife.ru:oschina-mirror/zymITsky-VisionDemo.git
oschina-mirror
zymITsky-VisionDemo
zymITsky-VisionDemo
master