Spaces:
Runtime error
Runtime error
// 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 }) | |
} | |
} | |