MiDaS / mobile /ios /Midas /Extensions /CVPixelBufferExtension.swift
qninhdt's picture
Upload 191 files
ef877a2 verified
// Copyright 2019 The TensorFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
import Accelerate
import Foundation
extension CVPixelBuffer {
var size: CGSize {
return CGSize(width: CVPixelBufferGetWidth(self), height: CVPixelBufferGetHeight(self))
}
/// Returns a new `CVPixelBuffer` created by taking the self area and resizing it to the
/// specified target size. Aspect ratios of source image and destination image are expected to be
/// same.
///
/// - Parameters:
/// - from: Source area of image to be cropped and resized.
/// - to: Size to scale the image to(i.e. image size used while training the model).
/// - Returns: The cropped and resized image of itself.
func resize(from source: CGRect, to size: CGSize) -> CVPixelBuffer? {
let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: self.size)
guard rect.contains(source) else {
os_log("Resizing Error: source area is out of index", type: .error)
return nil
}
guard rect.size.width / rect.size.height - source.size.width / source.size.height < 1e-5
else {
os_log(
"Resizing Error: source image ratio and destination image ratio is different",
type: .error)
return nil
}
let inputImageRowBytes = CVPixelBufferGetBytesPerRow(self)
let imageChannels = 4
CVPixelBufferLockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0))
defer { CVPixelBufferUnlockBaseAddress(self, CVPixelBufferLockFlags(rawValue: 0)) }
// Finds the address of the upper leftmost pixel of the source area.
guard
let inputBaseAddress = CVPixelBufferGetBaseAddress(self)?.advanced(
by: Int(source.minY) * inputImageRowBytes + Int(source.minX) * imageChannels)
else {
return nil
}
// Crops given area as vImage Buffer.
var croppedImage = vImage_Buffer(
data: inputBaseAddress, height: UInt(source.height), width: UInt(source.width),
rowBytes: inputImageRowBytes)
let resultRowBytes = Int(size.width) * imageChannels
guard let resultAddress = malloc(Int(size.height) * resultRowBytes) else {
return nil
}
// Allocates a vacant vImage buffer for resized image.
var resizedImage = vImage_Buffer(
data: resultAddress,
height: UInt(size.height), width: UInt(size.width),
rowBytes: resultRowBytes
)
// Performs the scale operation on cropped image and stores it in result image buffer.
guard vImageScale_ARGB8888(&croppedImage, &resizedImage, nil, vImage_Flags(0)) == kvImageNoError
else {
return nil
}
let releaseCallBack: CVPixelBufferReleaseBytesCallback = { mutablePointer, pointer in
if let pointer = pointer {
free(UnsafeMutableRawPointer(mutating: pointer))
}
}
var result: CVPixelBuffer?
// Converts the thumbnail vImage buffer to CVPixelBuffer
let conversionStatus = CVPixelBufferCreateWithBytes(
nil,
Int(size.width), Int(size.height),
CVPixelBufferGetPixelFormatType(self),
resultAddress,
resultRowBytes,
releaseCallBack,
nil,
nil,
&result
)
guard conversionStatus == kCVReturnSuccess else {
free(resultAddress)
return nil
}
return result
}
/// Returns the RGB `Data` representation of the given image buffer.
///
/// - Parameters:
/// - isModelQuantized: Whether the model is quantized (i.e. fixed point values rather than
/// floating point values).
/// - Returns: The RGB data representation of the image buffer or `nil` if the buffer could not be
/// converted.
func rgbData(
isModelQuantized: Bool
) -> Data? {
CVPixelBufferLockBaseAddress(self, .readOnly)
defer { CVPixelBufferUnlockBaseAddress(self, .readOnly) }
guard let sourceData = CVPixelBufferGetBaseAddress(self) else {
return nil
}
let width = CVPixelBufferGetWidth(self)
let height = CVPixelBufferGetHeight(self)
let sourceBytesPerRow = CVPixelBufferGetBytesPerRow(self)
let destinationBytesPerRow = Constants.rgbPixelChannels * width
// Assign input image to `sourceBuffer` to convert it.
var sourceBuffer = vImage_Buffer(
data: sourceData,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: sourceBytesPerRow)
// Make `destinationBuffer` and `destinationData` for its data to be assigned.
guard let destinationData = malloc(height * destinationBytesPerRow) else {
os_log("Error: out of memory", type: .error)
return nil
}
defer { free(destinationData) }
var destinationBuffer = vImage_Buffer(
data: destinationData,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: destinationBytesPerRow)
// Convert image type.
switch CVPixelBufferGetPixelFormatType(self) {
case kCVPixelFormatType_32BGRA:
vImageConvert_BGRA8888toRGB888(&sourceBuffer, &destinationBuffer, UInt32(kvImageNoFlags))
case kCVPixelFormatType_32ARGB:
vImageConvert_BGRA8888toRGB888(&sourceBuffer, &destinationBuffer, UInt32(kvImageNoFlags))
default:
os_log("The type of this image is not supported.", type: .error)
return nil
}
// Make `Data` with converted image.
let imageByteData = Data(
bytes: destinationBuffer.data, count: destinationBuffer.rowBytes * height)
if isModelQuantized { return imageByteData }
let imageBytes = [UInt8](imageByteData)
return Data(copyingBufferOf: imageBytes.map { Float($0) / Constants.maxRGBValue })
}
}