[Xcode 实际操作]七、文件与数据-(20)CoreML机器学习框架:检测和识别图片中的物体
本文将演示机器学习框架的使用,实现对图片中物体的检测和识别。
首先访问苹果开发者网站关于机器学习的网址:
https://developer.apple.com/cn/machine-learning/
点击右侧的滚动条,跳转到模型知识区域。
点击页面最下方的【Learn about working with models】进入机器学习模型页面:
https://developer.apple.com/cn/machine-learning/build-run-models/
点击右侧的垂直滚动条,跳转到模型下载区域。
苹果提供了多个已经完成训练的机器学习模型,
选择【ResNet 50】:从 1000 种类别对象 (如树木、动物、食物、汽车及人物等) 中检测出图像中的主体。
点击下方的【Core】进行下载,https://docs-assets.developer.apple.com/coreml/models/Resnet50.mlmodel
模型下载后将模型拖动到项目中【DemoApp】
在弹出的选项设置窗口中,保持默认的参数设置,然后点击完成【Finish】按钮,确认模型的导入。
在左侧的模型框架区,选择查看模型文件。
从右侧的属性面板可以看出模型的类型、体积、作者、版权声明、描述信息。
从底部的参数可以看出,该模型拥有一个输入参数,和两个输出参数。
在资源文件中导入一张鸟类的图片,将使用机器学习迅雷模型,检测图片中出现的鸟类的名称。
在项目导航区,打开视图控制器的代码文件【ViewController.swift】
1 import UIKit 2 //导入机器学习框架 3 import CoreML 4 5 class ViewController: UIViewController { 6 7 override func viewDidLoad() { 8 super.viewDidLoad() 9 // Do any additional setup after loading the view, typically from a nib. 10 11 //加载项目中指定名称的图片资源 12 let image = UIImage(named: "sample") 13 14 //机器学习模型只可以识别像素缓存格式的图像, 15 //需要将图片的格式进行转换, 16 //首先定义图片格式转换后的宽度和高度 17 let width : CGFloat = 224.0 18 let height : CGFloat = 224.0 19 20 //然后获得一个基于位图的上下文,并设置其为当前的上下文。 21 UIGraphicsBeginImageContext(CGSize(width: width, height: height)) 22 //将从项目中加载的图像,绘制在上下文的指定区域 23 image?.draw(in:CGRect(x: 0, y: 0, width: width, height: height)) 24 //接着从上下文中获得格式转换后的图像 25 let newImage = UIGraphicsGetImageFromCurrentImageContext() 26 //完成图像的格式转换后,关闭当前的上下文。 27 UIGraphicsEndImageContext() 28 29 //添加一个版本兼容性的判断语句 30 if #available(iOS 11.0, *) 31 { 32 //初始化机器学习模型的对象 33 let resnet50 = Resnet50() 34 35 //通过调用机器学习模型的对象的预测方法,对图像中的物体进行识别。 36 //需要注意的是,传入的图片需要是CVPixelBuffer格式, 37 //这里使用一个方法将图片进行格式转换,此方法在下方实现 38 guard let output = try? resnet50.prediction(image:pixelBufferFromImage(image: newImage!)) else 39 { 40 fatalError("Unexpected error.") 41 } 42 43 //在控制台输出识别的结果 44 print(output.classLabel) 45 } 46 } 47 48 //添加一个方法,用来实现图片格式的转换 49 func pixelBufferFromImage(image: UIImage) -> CVPixelBuffer 50 { 51 //初始化一个上下文对象,它将被用来渲染CIImage图像 52 let ciContext = CIContext(options: nil) 53 //通过UIImage对象,初始化一个CIImage对象。 54 let ciImage = CIImage(image: image) 55 //通过上下文对象,将CIImage对象,转换为CGImage类型。 56 //其中extend属性表示该对象在上下文中的区域。 57 let cgImage = ciContext.createCGImage(ciImage!, from: ciImage!.extent) 58 59 //创建一个非安全的可变指针,并给指针分配相应的内存。 60 let umPointer = UnsafeMutablePointer<UnsafeRawPointer>.allocate(capacity: 1) 61 //初始化一个CFNumber格式的数字,该类型位于Core Foundation框架 62 let cfNum = CFNumberCreate(kCFAllocatorDefault, .intType, umPointer) 63 //初始化一个数组对象,它将作为后面的字典对象的值 64 let values: [CFTypeRef] = [kCFBooleanTrue, kCFBooleanTrue, cfNum!] 65 //初始化两个非安全可变指针,作为字典的键和值 66 //键 67 let keysPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1) 68 //值 69 let valuesPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1) 70 71 //初始化一个字符串数组,包含三个键 72 //1.像素缓存图像兼容性 73 //2.像素缓存位图上下文兼容性 74 //3..像素缓存每行的字节数 75 let keys: [CFString] = [kCVPixelBufferCGImageCompatibilityKey,//1.像素缓存图像兼容性 76 kCVPixelBufferCGBitmapContextCompatibilityKey,//2.像素缓存位图上下文兼容性 77 kCVPixelBufferBytesPerRowAlignmentKey]//3..像素缓存每行的字节数 78 //使用上文创建的两个数字, 79 //对键、值两个非安全可变指针进行初始化 80 keysPointer.initialize(to: keys) 81 valuesPointer.initialize(to: values) 82 83 //通过键、值两个指针,以及默认的内存分配方式和键的数量等参数,初始化一个字典对象。 84 //该字典对象将作为配置选项,被用来创建像素缓存 85 let options = CFDictionaryCreate(kCFAllocatorDefault, keysPointer, valuesPointer, keys.count, nil, nil) 86 //新建一个图像缓存变量 87 var pxbuffer: CVPixelBuffer? 88 //然后对输入的缓存变量进行初始化。 89 //参数依次标注 90 var status = CVPixelBufferCreate(kCFAllocatorDefault, //1.内存分配方式 91 cgImage!.width,//2.图像宽度 92 cgImage!.height,//3.图像高度 93 kCVPixelFormatType_32BGRA, //4.像素格式类型 94 options, //5.配置参数 95 &pxbuffer)//6.像素缓存的内存地址 96 //接着锁定像素缓冲区的基址,在使用CPU访问像素数据之前,必须调用该函数 97 status = CVPixelBufferLockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0)) 98 99 //获得像素缓冲区的基址 100 let bufferAddress = CVPixelBufferGetBaseAddress(pxbuffer!) 101 //然后创建一个基于设备的RGB颜色空间。 102 //当在输出设备上显示时,依赖于设备的颜色空间中的颜色, 103 //不会被变换或以其他方式被修改 104 let rgbColorSpace = CGColorSpaceCreateDeviceRGB() 105 //获得像素缓冲区每行的字节数 106 let bytesperrow = CVPixelBufferGetBytesPerRow(pxbuffer!) 107 //通过上文创建的参数,初始化一个二维绘图环境 108 let context = CGContext(data: bufferAddress, 109 width: cgImage!.width, 110 height: cgImage!.height, 111 bitsPerComponent: 8, 112 bytesPerRow: bytesperrow, 113 space: rgbColorSpace, 114 bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue 115 | CGBitmapInfo.byteOrder32Little.rawValue) 116 //重置二维绘图环境的旋转角度为0 117 context?.concatenate(CGAffineTransform(rotationAngle: 0)) 118 //由于当前的二维绘图环境的坐标系统,和设备屏幕的坐标系统不同, 119 //所以在此对二维绘图环境进行上下文翻转 120 context?.concatenate(__CGAffineTransformMake( 1, 0, 0, -1, 0, CGFloat(cgImage!.height) )) 121 122 //将图像绘制在二维绘图环境中,并指定图像的显示区域。 123 context?.draw(cgImage!, 124 in: CGRect(x:0, y:0, 125 width:CGFloat(cgImage!.width), 126 height:CGFloat(cgImage!.height))) 127 //最后解锁橡树缓冲区的基地址。 128 //在使用CPU访问像素数据之后,必须调用该函数。 129 status = CVPixelBufferUnlockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0)) 130 131 //返回处理完成的像素缓存 132 return pxbuffer! 133 } 134 135 override func didReceiveMemoryWarning() { 136 super.didReceiveMemoryWarning() 137 // Dispose of any resources that can be recreated. 138 } 139 }