如何在 Metal 中 crop/resize 纹理数组

How to crop/resize texture array in Metal

假设我有一个 N 通道 MPSImage 或基于 MTLTexture 的纹理数组。

如何从中裁剪一个区域,复制所有 N 个通道,但更改 "pixel size"?

我将只讨论裁剪案例,因为调整大小案例涉及重新采样并且稍微复杂一些。如果您真的需要它,请告诉我。

假设您的源 MPSImage 是 128x128 像素的 12 特征通道(3 切片)图像,您的目标图像是 64x64 像素的 8 特征通道图像(2 切片),并且您想要将源的最后两个切片的右下角 64x64 区域复制到目标中。

据我所知,没有 API 允许您一次复制 from/to 数组纹理的多个切片,因此您需要发出多个 blit 命令来覆盖所有切片:

let sourceRegion = MTLRegionMake3D(64, 64, 0, 64, 64, 1)
let destOrigin = MTLOrigin(x: 0, y: 0, z: 0)
let firstSlice = 1
let lastSlice = 2 // inclusive

let commandBuffer = commandQueue.makeCommandBuffer()
let blitEncoder = commandBuffer.makeBlitCommandEncoder()

for slice in firstSlice...lastSlice {
    blitEncoder.copy(from: sourceImage.texture,
                     sourceSlice: slice,
                     sourceLevel: 0,
                     sourceOrigin: sourceRegion.origin,
                     sourceSize: sourceRegion.size,
                     to: destImage.texture,
                     destinationSlice: slice - firstSlice,
                     destinationLevel: 0,
                     destinationOrigin: destOrigin)
}
    
blitEncoder.endEncoding()
commandBuffer.commit()

我不确定您为什么要裁剪,但请记住,MPSCNN 层可以处理 MPSImage 的较小部分。只需设置 offsetclipRect 属性,图层将仅在源图像的该区域起作用。

事实上,您可以使用 MPSCNNNeuronLinear 以这种方式进行裁剪。不确定这是否比使用 blit 编码器更快或更慢,但它肯定更简单。

编辑: 添加了代码示例。这是凭记忆输入的,所以可能会有小错误,但这是总体思路:

// Declare this somewhere:
let linearNeuron = MPSCNNNeuronLinear(a: 1, b: 0)

然后当你运行你的神经网络时,添加以下内容:

let yourImage: MPSImage = ...
let commandBuffer = ...

// This describes the size of the cropped image.
let imgDesc = MPSImageDescriptor(...)

// If you're going to use the cropped image in other layers 
// then it's a good idea to make it a temporary image.
let tempImg = MPSTemporaryImage(commandBuffer: commandBuffer, imageDescriptor: imgDesc)

// Set the cropping offset:
linearNeuron.offset = MPSOffset(x: ..., y: ..., z: 0)

// The clip rect is the size of the output image.
linearNeuron.clipRect = MTLRegionMake(0, 0, imgDesc.width, imgDesc.height)

linearNeuron.encode(commandBuffer: commandBuffer, sourceImage: yourImage, destinationImage: tempImg)

// Here do your other layers, taking tempImg as input.
. . .

commandBuffer.commit()