Разработчик в камере. Видео

в 12:33, , рубрики: mobile development, апельсины, пчелы, Работа с видео, разработка под iOS, метки: ,

Apple постоянно держит разработчиков в тонусе.
Фронтальные камеры на iPad и iPhone родили новый виток идей у создателей сиюминутных приложений. Я тоже провел небольшое исследование двухкамерных телефонов и приглашаю нажать на кнопку, кому это интересно.

image

Видеозахват в iOS 4.3+ стал простым, как апельсин.


Четыре вызова – и Вы обладатель пикселей, полученных с любой из двух камер iPhone.

Чуть-чуть кода, дизайнерам не читать

Заводим наш HabrahabrView

класс HabrahabrView

@class CaptureSessionManager;

@interface HabrahabrView : UIView <AVCaptureVideoDataOutputSampleBufferDelegate> {
    CaptureSessionManager *captureFront;
    CaptureSessionManager *captureBack;
    UIImageView *face;
}

В теле класса вводим обязательную функцию captureOutput, куда будет приходить 20 раз в секунду картинки с видеокамеры.

функция captureOutput


-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {

    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 
    // Lock the base address of the pixel buffer
    CVPixelBufferLockBaseAddress(imageBuffer, 0); 
    //    Get the number of bytes per row for the pixel buffer
    //    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); 
    // Get the number of bytes per row for the pixel buffer
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
    // Get the pixel buffer width and height
    size_t width = CVPixelBufferGetWidth(imageBuffer); 
    size_t height = CVPixelBufferGetHeight(imageBuffer); 
    unsigned char* pixel = (unsigned char *)CVPixelBufferGetBaseAddress(imageBuffer);
    CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();  
    CGContextRef context=CGBitmapContextCreate(pixel, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little|kCGImageAlphaPremultipliedFirst);  
    CGImageRef image=CGBitmapContextCreateImage(context);  
    CGContextRelease(context);  
    CGColorSpaceRelease(colorSpace);  
    UIImage *resultUIImage=[UIImage imageWithCGImage:image];  
    CGImageRelease(image); 
    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
    [resultUIImage retain];
    [self performSelectorOnMainThread:@selector(cameraCaptureGotFrame:)
                           withObject:resultUIImage waitUntilDone:NO];

}

- (void) cameraCaptureGotFrame:(UIImage*)image
{
    face.image = [self fixOrientation:image];
    // decrement ref count
    [image release];
}

Теперь, когда вся предварительная работа закончена, заводим картинку на экране, куда будет выводится наше видео

Небольшой ImageView 58 на 70

    face = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 58, 70)];
    [self addSubview:face];

Вот так подключаем заднюю камеру

Задняя камера готова

    [self setCaptureBack:[[[CaptureSessionManager alloc] init] autorelease]];
    [[self captureBack] addVideoInput:2  PView:self];
    [[self captureBack] addVideoPreviewLayer];
    [[[self captureBack] captureSession] setSessionPreset:AVCaptureSessionPresetLow];

А вот так переднюю

Передняя камера готова

    [self setCaptureFront:[[[CaptureSessionManager alloc] init] autorelease]];
    [[self captureFront] addVideoInput:1  PView:self];
    [[self captureFront] addVideoPreviewLayer];
    [[[self captureFront] captureSession] setSessionPreset:AVCaptureSessionPresetLow];

Оформим все функции в отдельный класс.

Здесь скучный код, не читайте

#import "CaptureSessionManager.h"

@implementation CaptureSessionManager
@synthesize captureSession;
@synthesize previewLayer;

- (id)init {
	if ((self = [super init])) {
		[self setCaptureSession:[[AVCaptureSession alloc] init]];
	}
	return self;
}

- (void)addVideoPreviewLayer {
	[self setPreviewLayer:[[[AVCaptureVideoPreviewLayer alloc] initWithSession:[self captureSession]] autorelease]];
	[[self previewLayer] setVideoGravity:AVLayerVideoGravityResizeAspectFill];
}

- (void)addVideoInput:(int)camType PView:(HabrahabrView*) habraview {
        NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];  
        AVCaptureDevice *videoDevice = nil;  
        NSInteger side = (camType==1) ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
    for (AVCaptureDevice *device in videoDevices)  
        {  
            if (device.position == side)  
            {  
                videoDevice = device;  
                break;  
            }  
        }  
	if (videoDevice == nil) {
        videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    }
	if (videoDevice) {
		NSError *error;
		AVCaptureDeviceInput *videoIn = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
		if (!error) {
			if ([[self captureSession] canAddInput:videoIn])
				[[self captureSession] addInput:videoIn];
			else
				NSLog(@"Couldn't add video input");	
            // Set the output  
            AVCaptureVideoDataOutput* videoOutput = [[AVCaptureVideoDataOutput alloc] init];  
            // create a queue to run the capture on  
            dispatch_queue_t captureQueue=dispatch_queue_create("catpureQueue", NULL);  
            // setup our delegate  
            [videoOutput setSampleBufferDelegate:habraview queue:captureQueue];  
            // configure the pixel format  
            videoOutput.videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], (id)kCVPixelBufferPixelFormatTypeKey,  
                                         nil];  
            if ([[self captureSession] canAddOutput:videoOutput])
				[[self captureSession] addOutput:videoOutput];
			else
				NSLog(@"Couldn't add video ouput");		
        }
		else
			NSLog(@"Couldn't create video input");
	}
	else
		NSLog(@"Couldn't create video capture device");
}


- (void)dealloc {
	[[self captureSession] stopRunning];
	[previewLayer release], previewLayer = nil;
	[captureSession release], captureSession = nil;
	[super dealloc];
}
@end

Готово.

Работа с камерами

Включаем переднюю камеру

    [[captureFront captureSession] startRunning];

Все работает. Как видно из кода видео можно дергать в трех разрешениях. Я выбираю для скорости самое маленькое AVCaptureSessionPresetLow (примерно 144 на 192 пикселя ). Для наших нужд этого хватает, заодно и фильтр картинки происходит бесплатный.

Как теперь включить заднюю камеру? Остановить переднюю и включить заднюю

    [[captureFront captureSession] stopRunning];
    [[captureBack captureSession] startRunning];

Сразу захотелось включить обе. Увы. Невозможно. Я попытался быстро переключать камеры, но существует задержка примерно в треть секунды, которая ничего, кроме раздражения не вызывает.

Мечту о наложении двух изображении онлайн пришлось убить.

Фруктожорка

Но не убить мечту о каком-нибудь дурацком приложении. Я решил быстро сотворить фруктожорку — изображение с фронтальной камеры реальное, апельсины — виртуальные.

Апельсины по очереди падают на экран. Их надо захватить ртом и съесть. Кто быстрее съест 7 апельсинов — тому приз.

Осталось написать процедуру распознавания рта.
Распознать рот — занятие примитивное, даже я, человек впадающий в тоску от слов ООП, ОпенСиВи, сделал эту функцию быстро. Не используя ни ООП, ни ОпенСиВи.
Подсказка — Вы знаете положение апельсина и пляшете от него.

Ага, воскликнет внимательный читатель — процедура распознавания отлажена исключительно на авторе при освещении его рабочего места. Вы правы, но судя по фотографиям пользователей, приложение успешно работает от Китая до Бразилии, от офисов до квартир.

Конечно, был велик соблазн стащить фотографию пользователя в момент поедания им 7-ого апельсина. Я всегда поддаюсь соблазнам, ведь они могут не повториться. Спокойно! Для любителей нравственности я добавил кнопку — Разрешить отправлять свое фото. По умолчанию — выключена.

Я отправляю небольшую картинку игрока размером 58 на 70 и тайком любуюсь. Попадаются очень забавные фотки. Ржу порой минуты по 3. Есть и симпатичные особы.
Ради Бога, не болтайте про фото, храните корпоративные тайны.

Совсем забыл. Мой рекорд — 12 секунд. Готовлюсь к Олимпиаде.

До встречи в Лондоне, друзья.

Автор: PapaBubaDiop

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js