5.9 定义和布局视图控制器

5.9 定义和布局视图控制器

BeanCounter使用两个视图控制器。第一个控制器允许用户捕获和预览斑点的图像。第二个控制器让用户查看斑点的分类结果,然后保存和分享斑点图像。跳转(Segue)对象让第一个视图控制器实例化第二个视图控制器,并传递一个Blob对象和标签。这类似于我们在第四章的项目哺乳动物脸部的检测与融合中ManyMasks应用的逻辑划分,因此我们能够相关的代码.

5.9.1 捕获和预览斑点

导入我们在第二章’捕获,存储和分享照片’中创建的VideoCamera.h和.m文件的备份.这两个文件定义了VideoCamera类,该类继承于OpenCV的cvVideoCamera,修正了bug并增加了一些功能.
同样,导入我们在第四章哺乳动物脸部的检测与融合创建的CpatureViewController.h和.m文件.这两个文件包含了CpatureViewController类,该类负责捕获和检测.当然,我们新版本的CaptureViewController并不依赖我们脸部的旧模型和脸部检测器.新版本依赖于斑点模型和斑点检测器和一个斑点分类器.此外,还持有者一个用于描述可能的分类结果的标签的字符串列表.用户界面也改变了一点点.编辑CaptureViewController.m,他的导入声明和私有接口如下:

#import <opencv2/core.hpp>
#import <opencv2/imgcodecs/ios.h>
#import <opencv2/imgproc.hpp>

#import "CaptureViewController.h"
#import "BlobClassifier.hpp"
#import "BlobDetector.hpp"
#import "ReviewViewController.h"
#import "VideoCamera.h"

const double DETECT_RESIZE_FACTOR = 0.5;


@interface CaptureViewController.h () <CvVideoCameraDelegate> {
    BlobClassifier *blobClassifier;
    BlobDetector *blobDetector;
    std::vector<Blob> detectedBlobs;
}

@property IBOutlet UIView *backgroundView;

@property IBOutlet UIBarButtonItem *classifyButton;

@property VideoCamera *videoCamera; @property BOOL showMask;

@property NSArray<NSString *> *labelDescriptions;

- (IBAction)onTapToSetPointOfInterest:(UITapGestureRecognizer *)tapGesture;

- (IBAction)onPreviewModeSelected:(UISegmentedControl *)segmentedControl;

- (IBAction)onSwitchCameraButtonPressed;

- (void)refresh;


- (void)processImage:(cv::Mat &)mat;

- (UIImage *)imageFromCapturedMat:(const cv::Mat &)mat;

@end

viewDidLoad方法负责初始化检测器,分类器,描述字符串列表和相机.作为这个过程的一部分,我们从BlobClassiferTraining.plist中加载关于分类器训练集的元数据。(有关此PLIST文件的描述,请参阅本章前面的’配置项目’一节.)iOS SDK使我们能够轻松地将PLIST作为键-值对的字典加载。键是字符串,值可以是其他字典、数组、字符串、数字、布尔值、日期或原始二进制数据。出于BeanCounter的目的,PLIST文件提供了标签描述以及成对的训练图像及其对应的标签。我们从文件中加载每个图像,用图像及其标签构造Blob,并将Blob传递给BlobClassifier的update方法来训练分类器。查看下面viewDidLoad实现中的代码:

@implementation CaptureViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    blobDetector = new BlobDetector();
    blobClassifier = new BlobClassifier();
    
    // Load the blob classifier's configuration from file.
    NSBundle *bundle = [NSBundle mainBundle];
    NSString *configPath = [bundle pathForResource:@"BlobClassifierTraining" ofType:@"plist"];
    NSDictionary *config = [NSDictionary dictionaryWithContentsOfFile:configPath];
    // Remember the descriptions of the blob labels.
    self.labelDescriptions = config[@"labelDescriptions"];
    
    // Create reference blobs and train the blob classifier.
    NSArray *configBlobs = config[@"blobs"];
    for (NSDictionary *configBlob in configBlobs) {
        
        uint32_t label = [configBlob[@"label"] unsignedIntValue];
        NSString *imageFilename = configBlob[@"imageFilename"];
        UIImage *image = [UIImage imageNamed:imageFilename];
        if (image == nil) { NSLog(@"Image not found in resources: %@", imageFilename);
            continue;
        }
        cv::Mat mat;
        UIImageToMat(image, mat);
        cv::cvtColor(mat, mat, cv::COLOR_RGB2BGR);
        Blob blob(mat, label);
        blobClassifier->update(blob);
    }
    
    self.videoCamera = [[VideoCamera alloc] initWithParentView:self.backgroundView];
    self.videoCamera.delegate = self;
    self.videoCamera.defaultAVCaptureSessionPreset =AVCaptureSessionPresetHigh;
    self.videoCamera.defaultFPS = 30;
    self.videoCamera.letterboxPreview = YES;
    self.videoCamera.defaultAVCaptureDevicePosition =AVCaptureDevicePositionBack;
}

BlobClassifier和BlobDetector都是C++动态生成的对象.分类器将占用很大的内存,因为他持有了直方图对象和所有引用图片的关键点描述符.让我们编辑’didReceiveMemoryWarning’和’dealloc’方法来确保这些C++对象被清理掉:

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    if (blobClassifier != NULL) {
        delete blobClassifier;
        blobClassifier = NULL;
    } if (blobDetector != NULL) {
        delete blobDetector;
        blobDetector = NULL;
    }
    
}

- (void)dealloc {
    if (blobClassifier != NULL) {
        delete blobClassifier;
        blobClassifier = NULL;
    } if (blobDetector != NULL) {
        delete blobDetector;
        blobDetector = NULL;
    }
}

当用户在Image和Mask分段控制器中选择了一个选项,我们就设置一个布尔值’showMask’,如下面代码所示:

- (IBAction)onPreviewModeSelected:(UISegmentedControl *)segmentedControl {
    switch (segmentedControl.selectedSegmentIndex) {
        case 0:
            self.showMask = NO;
            break;
        default:
            self.showMask = YES;
            break;
    }
    [self refresh];
}

现在,让我们考虑下BeanCounter是如何处理相机的每一帧的输入的.回调方法’processImage:'以我们常规的修正方向的代码开始.然后,我们传递当前帧数据和vector给BlobDetector的detect方法,该方法将填充vector对象,并在检测到的斑点周围绘制绿色矩形.'Classify’按钮能否启用,取决于当前帧是否检测到了斑点.如果’showMask’属性值为true,我们就显示当前帧的掩码图,否则,用户就将看到原图中,斑点被绿色矩形包围的图像.下面是回调的实现:

- (void)processImage:(cv::Mat &)mat {

    switch (self.videoCamera.defaultAVCaptureVideoOrientation) {
        case AVCaptureVideoOrientationLandscapeLeft:
        case AVCaptureVideoOrientationLandscapeRight:
            // The landscape video is captured upside-down.
            // Rotate it by 180 degrees.
            cv::flip(mat, mat, -1);
            break;
            
        default:
            break;
    }
    
    // Detect and draw any blobs.
    blobDetector->detect(mat, detectedBlobs, DETECT_RESIZE_FACTOR, true);
    
    BOOL didDetectBlobs = (detectedBlobs.size() > 0);
    dispatch_async(dispatch_get_main_queue(), ^{
        self.classifyButton.enabled = didDetectBlobs;
    });
    if (self.showMask) {
        blobDetector->getMask().copyTo(mat);
    }
    
}

CaptureViewController的其它方法和第4章’哺乳动物脸部的检测与融合’的ManyMasks程序中是一样的.
打开Main.storyboard,在场景层次中选择ViewController.在Identity选项卡中将Class的值设置为CpatureViewController.然后,按照下图所示,增加GUI插件作为视图控制器的主视图的子视图.(或者从本书的GitHub仓库中直接下载完整的故事版文件)
在这里插入图片描述
在场景层次图中右键点击CaptureViewController,会显示我们在CaptureViewController.m中定义的可用的输出口和动作.按照下图所示进行连线:
在这里插入图片描述

5.9.2 观察,保存和分享已分类斑点

在项目中导入第4章’哺乳动物脸部的检测与融合’的ManyMasks项目中的ReviewViewController.h和.m文件.针对BeanCounter的需求,我们将编辑这些文件来支持显示标题,该标题描述了分类的结果.首先,编辑ReviewViewController.h的公共接口增加一个NSString属性,如下面代码所示:

@interface ReviewViewController : UIViewController
@property UIImage *image;
@property NSString *caption; 
@end

在ReviewViewController.m的私有接口中添加一个UILabel属性,如下面代码所示:

@interface ReviewViewController ()
@property IBOutlet UIImageView *imageView;
@property IBOutlet UILabel *label;
@property IBOutlet UIActivityIndicatorView *activityIndicatorView;
@property IBOutlet UIToolbar *toolbar;
// ... same methods as in Chapter 4 ...
@end

最后,编辑viewDidLoad方法的实现,将capion属性赋值给label的text的属性:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.imageView.image = self.image;
    self.label.text = self.caption;
}

其余的内容保持不变.特别地,该类仍然支持保存和分享图片.
让我们打开Main.storyborad.从库中拖动一个新的视图控制器到编辑区域.打开新视图控制的Identity检测器,设置Class为ReviewViewController.然后增加合适的插件,参考以下截图(或者直接从本书仓库中下载完整的故事板文件):
在这里插入图片描述
在场景层次图中右击ReviewViewController,打开可用输出口和行为列表,这些在ReviewViewController中都有定义.参考下图进行连接:
在这里插入图片描述

5.9.3 视图控制器之间的跳转

在prepareForSegue函数中,需要提供一个blob对象和分类结果给ReviewViewController.首先,应该停止相机因为我们不希望proccImage:方法在我们访问当前blob的时候在其他线程中更改blob向量.然后,prepareForSegue中,选择最大的blob,并传递给BlobClassifier的classify函数,该函数返回代表分类信息的整型结果.我们根据这个结果找到标签的描述,最后将blob的image和标签的描述传递给ReviewViewController.下面是相关代码:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
	if ([segue.identifier isEqualToString:@"showReviewModally"]) {
        ReviewViewController *reviewViewController = segue.destinationViewController;
        
        // Stop the camera to prevent conflicting access to the blobs.
        [self.videoCamera stop];
        
        // Find the biggest blob.
        
        int biggestBlobIndex = 0;
        for (int i = 0, biggestBlobArea = 0;i < detectedBlobs.size(); i++) {
            Blob &detectedBlob = detectedBlobs[i];
            int blobArea = detectedBlob.getWidth() *detectedBlob.getHeight();
            if (blobArea > biggestBlobArea) {
                biggestBlobIndex = i;
                biggestBlobArea = blobArea;
            }
        }
        Blob &biggestBlob = detectedBlobs[biggestBlobIndex];
        
        // Classify the blob and show the result in the destination // view controller.
        
        blobClassifier->classify(biggestBlob);
        reviewViewController.image = [self imageFromCapturedMat:biggestBlob.getMat()];
        reviewViewController.caption = self.labelDescriptions[biggestBlob.getLabel()];
    }
}

再次打开Main.storyboard,点击并拖动Classify按钮到Review View Controller,创建一个segue跳转对象.segue的Kind是Present Modally,Identifier是"showReviewModally",Transition是FlipHorizontal(或者其它你喜欢的效果).参考以下截图:
在这里插入图片描述

###返回到第五章目录###
###返回到书籍目录###

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值