ho fatto qualche progresso, e ho praticamente considerare questo una risposta alla mia domanda , anche se alcune cose sono un po 'diverse e io no hink questo metodo è molto veloce. Mi piacerebbe sapere da chiunque di vedere come rendere questo codice più veloce. Nel seguito, sembra che il ridimensionamento dell'immagine richieda più tempo, ottengo un numero di chiamate alla sezione output della sezione ovveride e non ho idea del perché. Sfortunatamente quando eseguo la funzione Laplacian Pyramid qui sotto, ci vogliono circa 5 secondi per completare una foto 275x300. Questo non va bene, e sono un po 'in perdita su come accelerarlo. Il mio sospetto è che il filtro resample sia il colpevole. Tuttavia non sono abbastanza esperto per sapere come renderlo più veloce.
In primo luogo, i filtri personalizzati:
Questo primo ridimensiona un'immagine con un semplice ridimensionamento. Penso che sia la migliore tecnica di riscalamento in questo caso perché tutto ciò che viene fatto è una replica dei pixel quando ridimensionati. Per esempio, se abbiamo il seguente blocco di pixel ed eseguire una scala 2.0, quindi la mappatura è simile al seguente:
[ ][ ][x][ ] ----->[ ][ ][ ][ ][x][x][ ][ ]
(Grazie a Simon Gladman per l'idea su questo)
public class ResampleFilter: CIFilter
{
var inputImage : CIImage?
var inputScaleX: CGFloat = 1
var inputScaleY: CGFloat = 1
let warpKernel = CIWarpKernel(string:
"kernel vec2 resample(float inputScaleX, float inputScaleY)" +
" { " +
" float y = (destCoord().y/inputScaleY); " +
" float x = (destCoord().x/inputScaleX); " +
" return vec2(x,y); " +
" } "
)
override public var outputImage: CIImage!
{
if let inputImage = inputImage,
kernel = warpKernel
{
let arguments = [inputScaleX, inputScaleY]
let extent = CGRect(origin: inputImage.extent.origin,
size: CGSize(width: inputImage.extent.width*inputScaleX,
height: inputImage.extent.height*inputScaleY))
return kernel.applyWithExtent(extent,
roiCallback:
{
(index,rect) in
let sampleX = rect.origin.x/self.inputScaleX
let sampleY = rect.origin.y/self.inputScaleY
let sampleWidth = rect.width/self.inputScaleX
let sampleHeight = rect.height/self.inputScaleY
let sampleRect = CGRect(x: sampleX, y: sampleY, width: sampleWidth, height: sampleHeight)
return sampleRect
},
inputImage : inputImage,
arguments : arguments)
}
return nil
}
}
Questo uno è una miscela semplice differenza.
public class DifferenceOfImages: CIFilter
{
var inputImage1 : CIImage? //Initializes input
var inputImage2 : CIImage?
var kernel = CIKernel(string: //The actual custom kernel code
"kernel vec4 Difference(__sample image1,__sample image2)" +
" { " +
" float colorR = image1.r - image2.r; " +
" float colorG = image1.g - image2.g; " +
" float colorB = image1.b - image2.b; " +
" return vec4(colorR,colorG,colorB,1); " +
" } "
)
var extentFunction: (CGRect, CGRect) -> CGRect =
{ (a: CGRect, b: CGRect) in return CGRectZero }
override public var outputImage: CIImage!
{
guard let inputImage1 = inputImage1,
inputImage2 = inputImage2,
kernel = kernel
else
{
return nil
}
//apply to whole image
let extent = extentFunction(inputImage1.extent,inputImage2.extent)
//arguments of the kernel
let arguments = [inputImage1,inputImage2]
//return the rectangle that defines the part of the image that CI needs to render rect in the output
return kernel.applyWithExtent(extent,
roiCallback:
{ (index, rect) in
return rect
},
arguments: arguments)
}
}
Subito per alcune definizioni di funzioni:
Questa funzione solo esegue una sfocatura gaussiana sull'immagine, secondo lo stesso filtro 5 rubinetto come descritto in carta Burt & Adelson. Non sei sicuro di come sbarazzarti dei pixel scomodi confinanti che sembrano essere extra.
public func GaussianFilter(ciImage: CIImage) -> CIImage
{
//5x5 convolution to image
let kernelValues: [CGFloat] = [
0.0025, 0.0125, 0.0200, 0.0125, 0.0025,
0.0125, 0.0625, 0.1000, 0.0625, 0.0125,
0.0200, 0.1000, 0.1600, 0.1000, 0.0200,
0.0125, 0.0625, 0.1000, 0.0625, 0.0125,
0.0025, 0.0125, 0.0200, 0.0125, 0.0025 ]
let weightMatrix = CIVector(values: kernelValues,
count: kernelValues.count)
let filter = CIFilter(name: "CIConvolution5X5",
withInputParameters: [
kCIInputImageKey: ciImage,
kCIInputWeightsKey: weightMatrix])!
let final = filter.outputImage!
let rect = CGRect(x: 0, y: 0, width: ciImage.extent.size.width, height: ciImage.extent.size.height)
return final.imageByCroppingToRect(rect)
}
Questa funzione semplifica l'utilizzo del ricampionamento. È possibile specificare una dimensione di destinazione della nuova immagine.Questo risulta essere più facile da gestire piuttosto che impostare un parametro di scala IMO.
public func resampleImage(inputImage: CIImage, sizeX: CGFloat, sizeY: CGFloat) -> CIImage
{
let inputWidth : CGFloat = inputImage.extent.size.width
let inputHeight : CGFloat = inputImage.extent.size.height
let scaleX = sizeX/inputWidth
let scaleY = sizeY/inputHeight
let resamplefilter = ResampleFilter()
resamplefilter.inputImage = inputImage
resamplefilter.inputScaleX = scaleX
resamplefilter.inputScaleY = scaleY
return resamplefilter.outputImage
}
Questa funzione semplifica solo l'uso del filtro differenza. Basta notare che è
imageOne - ImageTwo
.
public func Difference(imageOne:CIImage,imageTwo:CIImage) -> CIImage
{
let generalFilter = DifferenceOfImages()
generalFilter.inputImage1 = imageOne
generalFilter.inputImage2 = imageTwo
generalFilter.extentFunction = { (fore, back) in return back.union(fore)}
return generalFilter.outputImage
}
Questa funzione calcola le dimensioni di livello di ogni piramide e le memorizza in un array. Utile per dopo.
public func LevelDimensions(image: CIImage,levels:Int) -> [[CGFloat]]
{
let inputWidth : CGFloat = image.extent.width
let inputHeight : CGFloat = image.extent.height
var levelSizes : [[CGFloat]] = [[inputWidth,inputHeight]]
for j in 1...(levels-1)
{
let temp = [floor(inputWidth/pow(2.0,CGFloat(j))),floor(inputHeight/pow(2,CGFloat(j)))]
levelSizes.append(temp)
}
return levelSizes
}
Ora per il bene: questo crea una Gaussian Pyramid un determinato numero di livelli.
public func GaussianPyramid(image: CIImage,levels:Int) -> [CIImage]
{
let PyrLevel = LevelDimensions(image, levels: levels)
var GauPyr : [CIImage] = [image]
var I : CIImage
var J : CIImage
for j in 1 ... levels-1
{
J = GaussianFilter(GauPyr[j-1])
I = resampleImage(J, sizeX: PyrLevel[j][0], sizeY: PyrLevel[j][1])
GauPyr.append(I)
}
return GauPyr
}
Infine, questa funzione crea la piramide di Laplacian con un determinato numero di livelli. Nota che in entrambe le funzioni Pyramid, ogni livello è memorizzato in una matrice.
public func LaplacianPyramid(image:CIImage,levels:Int) -> [CIImage]
{
let PyrLevel = LevelDimensions(image, levels:levels)
var LapPyr : [CIImage] = []
var I : CIImage
var J : CIImage
J = image
for j in 0 ... levels-2
{
let blur = GaussianFilter(J)
I = resampleImage(blur, sizeX: PyrLevel[j+1][0], sizeY: PyrLevel[j+1][1])
let diff = Difference(J,imageTwo: resampleImage(I, sizeX: PyrLevel[j][0], sizeY: PyrLevel[j][1]))
LapPyr.append(diff)
J = I
}
LapPyr.append(J)
return LapPyr
}
Hai visto questo: https://developer.apple.com/reference/metalperformanceshaders/mpsimagegaussianpyramid –
ho, ma ho davvero voluto vedere se ci fosse un'alternativa prima di utilizzare filtri personalizzati per ottenere quello che voglio . Queste cose non sono così rari nell'elaborazione delle immagini, quindi ho pensato che ci sarebbe stato un modo per farlo prima di Apple. –
Non sono sicuro che abbia tutte le funzioni di cui hai bisogno, ma puoi provare il 'Accelerate Framework' https://developer.apple.com/videos/play/wwdc2013/713/ – juanjo