版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.07.23 星期二 |
前言
iOS 11+
和macOS 10.13+
新出了Vision
框架,提供了人脸识别、物体检测、物体跟踪等技术,它是基于Core ML的。可以说是人工智能的一部分,接下来几篇我们就详细的解析一下Vision框架。感兴趣的看下面几篇文章。
1. Vision框架详细解析(一) —— 基本概览(一)
2. Vision框架详细解析(二) —— 基于Vision的人脸识别(一)
3. Vision框架详细解析(三) —— 基于Vision的人脸识别(二)
4. Vision框架详细解析(四) —— 在iOS中使用Vision和Metal进行照片堆叠(一)
源码
1. Swift
首先看下工程组织结构
下面看下sb中的内容
接着就看下代码了
1. RecordButton.swift
import UIKit
@IBDesignable
class RecordButton: UIButton {
var progress: CGFloat = 0.0 {
didSet {
DispatchQueue.main.async {
self.setNeedsDisplay()
}
}
}
override func draw(_ rect: CGRect) {
// General Declarations
let context = UIGraphicsGetCurrentContext()!
// Resize to Target Frame
context.saveGState()
context.translateBy(x: bounds.minX, y: bounds.minY)
context.scaleBy(x: bounds.width / 218, y: bounds.height / 218)
// Color Declarations
let red = UIColor(red: 0.949, green: 0.212, blue: 0.227, alpha: 1.000)
let white = UIColor(red: 0.996, green: 1.000, blue: 1.000, alpha: 1.000)
// Variable Declarations
let expression: CGFloat = -progress * 360
// Button Drawing
let buttonPath = UIBezierPath(ovalIn: CGRect(x: 26, y: 26, width: 166, height: 166))
red.setFill()
buttonPath.fill()
// Ring Background Drawing
let ringBackgroundPath = UIBezierPath(ovalIn: CGRect(x: 8.5, y: 8.5, width: 200, height: 200))
white.setStroke()
ringBackgroundPath.lineWidth = 19
ringBackgroundPath.lineCapStyle = .round
ringBackgroundPath.stroke()
// Progress Ring Drawing
let progressRingRect = CGRect(x: 8.5, y: 8.5, width: 200, height: 200)
let progressRingPath = UIBezierPath()
progressRingPath.addArc(withCenter: CGPoint(x: progressRingRect.midX, y: progressRingRect.midY), radius: progressRingRect.width / 2, startAngle: -90 * CGFloat.pi/180, endAngle: -(expression + 90) * CGFloat.pi/180, clockwise: true)
red.setStroke()
progressRingPath.lineWidth = 19
progressRingPath.lineCapStyle = .round
progressRingPath.stroke()
context.restoreGState()
}
func resetProgress() {
progress = 0.0
}
}
2. AverageStacking.metal
#include <metal_stdlib>
using namespace metal;
#include <CoreImage/CoreImage.h>
extern "C" { namespace coreimage {
float4 avgStacking(sample_t currentStack, sample_t newImage, float stackCount) {
float4 avg = ((currentStack * stackCount) + newImage) / (stackCount + 1.0);
avg = float4(avg.rgb, 1);
return avg;
}
}}
3. AverageStackingFilter.swift
import CoreImage
class AverageStackingFilter: CIFilter {
let kernel: CIBlendKernel
var inputCurrentStack: CIImage?
var inputNewImage: CIImage?
var inputStackCount = 1.0
override init() {
guard let url = Bundle.main.url(forResource: "default", withExtension: "metallib") else {
fatalError("Check your build settings.")
}
do {
let data = try Data(contentsOf: url)
kernel = try CIBlendKernel(functionName: "avgStacking", fromMetalLibraryData: data)
} catch {
print(error.localizedDescription)
fatalError("Make sure the function names match")
}
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func outputImage() -> CIImage? {
guard
let inputCurrentStack = inputCurrentStack,
let inputNewImage = inputNewImage
else {
return nil
}
return kernel.apply(extent: inputCurrentStack.extent, arguments: [inputCurrentStack, inputNewImage, inputStackCount])
}
}
4. CIImageExtension.swift
import CoreImage
extension CIImage {
func cgImage() -> CGImage? {
if cgImage != nil {
return cgImage
}
return CIContext().createCGImage(self, from: extent)
}
}
5. CameraViewController.swift
import AVFoundation
import UIKit
class CameraViewController: UIViewController {
@IBOutlet var previewView: UIView!
@IBOutlet var containerView: UIView!
@IBOutlet var combinedImageView: UIImageView!
@IBOutlet var recordButton: RecordButton!
var previewLayer: AVCaptureVideoPreviewLayer!
let session = AVCaptureSession()
var saver: ImageSaver?
let imageProcessor = ImageProcessor()
var isRecording = false
let maxFrameCount = 20
override func viewDidLoad() {
super.viewDidLoad()
containerView.isHidden = true
configureCaptureSession()
session.startRunning()
}
}
// MARK: - Configuration Methods
extension CameraViewController {
func configureCaptureSession() {
guard let camera = AVCaptureDevice.default(for: .video) else {
fatalError("No video camera available")
}
do {
let cameraInput = try AVCaptureDeviceInput(device: camera)
session.addInput(cameraInput)
try camera.lockForConfiguration()
camera.activeVideoMinFrameDuration = CMTime(value: 1, timescale: 5)
camera.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: 5)
camera.unlockForConfiguration()
} catch {
fatalError(error.localizedDescription)
}
// Define where the video output should go
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "video data queue"))
//videoOutput.alwaysDiscardsLateVideoFrames = true
videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
// Add the video output to the capture session
session.addOutput(videoOutput)
let videoConnection = videoOutput.connection(with: .video)
videoConnection?.videoOrientation = .portrait
// Configure the preview layer
previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
previewView.layer.addSublayer(previewLayer)
}
}
// MARK: - UI Methods
extension CameraViewController {
@IBAction func recordTapped(_ sender: UIButton) {
recordButton.isEnabled = false
isRecording = true
saver = ImageSaver()
}
@IBAction func closeButtonTapped(_ sender: UIButton) {
containerView.isHidden = true
recordButton.isEnabled = true
session.startRunning()
}
func stopRecording() {
isRecording = false
recordButton.progress = 0.0
}
func displayCombinedImage(_ image: CIImage) {
session.stopRunning()
combinedImageView.image = UIImage(ciImage: image)
containerView.isHidden = false
}
}
// MARK: - Capture Video Data Delegate Methods
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
if !isRecording {
return
}
guard
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
let cgImage = CIImage(cvImageBuffer: imageBuffer).cgImage()
else {
return
}
let image = CIImage(cgImage: cgImage)
imageProcessor.add(image)
saver?.write(image)
let currentFrame = recordButton.progress * CGFloat(maxFrameCount)
recordButton.progress = (currentFrame + 1.0) / CGFloat(maxFrameCount)
if recordButton.progress >= 1.0 {
stopRecording()
imageProcessor.processFrames(completion: displayCombinedImage)
}
}
}
6. ImageProcessor.swift
import CoreImage
import Vision
class ImageProcessor {
var frameBuffer: [CIImage] = []
var alignedFrameBuffer: [CIImage] = []
var completion: ((CIImage) -> Void)?
var isProcessingFrames = false
var frameCount: Int {
return frameBuffer.count
}
func add(_ frame: CIImage) {
if isProcessingFrames {
return
}
frameBuffer.append(frame)
}
func processFrames(completion: ((CIImage) -> Void)?) {
isProcessingFrames = true
self.completion = completion
let firstFrame = frameBuffer.removeFirst()
alignedFrameBuffer.append(firstFrame)
for frame in frameBuffer {
let request = VNTranslationalImageRegistrationRequest(targetedCIImage: frame)
do {
let sequenceHandler = VNSequenceRequestHandler()
try sequenceHandler.perform([request], on: firstFrame)
} catch {
print(error.localizedDescription)
}
alignImages(request: request, frame: frame)
}
combineFrames()
}
func alignImages(request: VNRequest, frame: CIImage) {
guard
let results = request.results as? [VNImageTranslationAlignmentObservation],
let result = results.first
else {
return
}
let alignedFrame = frame.transformed(by: result.alignmentTransform)
alignedFrameBuffer.append(alignedFrame)
}
func combineFrames() {
var finalImage = alignedFrameBuffer.removeFirst()
let filter = AverageStackingFilter()
for (i, image) in alignedFrameBuffer.enumerated() {
filter.inputCurrentStack = finalImage
filter.inputNewImage = image
filter.inputStackCount = Double(i + 1)
finalImage = filter.outputImage()!
}
cleanup(image: finalImage)
}
func cleanup(image: CIImage) {
frameBuffer = []
alignedFrameBuffer = []
isProcessingFrames = false
if let completion = completion {
DispatchQueue.main.async {
completion(image)
}
}
completion = nil
}
}
7. ImageSaver.swift
import CoreImage
struct ImageSaver {
var count = 0
let url: URL
init() {
let uuid = UUID().uuidString
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
url = urls[0].appendingPathComponent(uuid)
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
}
mutating func write(_ image: CIImage, as name: String? = nil) {
guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else {
return
}
let context = CIContext()
let lossyOption = kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption
let imgURL: URL
if let name = name {
imgURL = url.appendingPathComponent("\(name).jpg")
} else {
imgURL = url.appendingPathComponent("\(count).jpg")
}
try? context.writeJPEGRepresentation(of: image,
to: imgURL,
colorSpace: colorSpace,
options: [lossyOption: 0.9])
count += 1
}
}
后记
本篇主要讲述了在iOS中使用Vision和Metal进行照片堆叠,感兴趣的给个赞或者关注~~~