2016-07-01 47 views
10

IntroduzioneDownsampling e upsampling per gaussiana immagine Piramidi a Swift

Sono interessato a scrivere una funzione che genera per me il livello successivo in una piramide gaussiana (alla fine ho voglio arrivare alla creazione di una piramide Laplaciano) per l'uso nell'elaborazione delle immagini. (Link per riferimento https://en.wikipedia.org/wiki/Pyramid_(image_processing)#Gaussian_pyramid)

Il Downsampling problema

Ora la parte più semplice di questo è che quando si giù/upsample, un filtro 5-tap è convoled con l'immagine prima del ridimensionamento.

Tuttavia, la parte interessante relativa alla creazione di piramidi di immagini è che è necessario eseguire il downsampling e il campionamento di un'immagine di un fattore di 0,5 o 2, a seconda della direzione in cui si sta andando. Swift ha alcuni modi per farlo, come usare CIAffineTransform e CILanczosTransform, tuttavia mi chiedo se ci sono modi per farlo in modo un po 'più ingenuo perché non mi interessa la qualità dell'immagine ridimensionata. Per questo post, ho intenzione di utilizzare Lenna (512x512) a titolo di esempio, si vede qui sotto:

The famous Lenna

Se vogliamo downsample un'immagine di un fattore due, avremmo preso tutte le dispari dati pixel numerati per formare una nuova immagine. In MATLAB questo viene eseguito come segue (dopo la sfocatura gaussiana):

Se I è l'immagine di input ed è di dimensione NxM, con 3 mappature di colori memorizzate per P (una matrice 512x512x3), quindi l'immagine decimata di una scala .5 è

R = I(1:2:end, 1:2:end,:)

Tutta la nuova immagine è il precedente con il numero dispari colonne e righe dell'immagine numerati. Questo produce il seguente, una foto 256x256 che è il primo livello della piramide gaussiana:

Downsampled Lenna

fa una cosa del genere esistono in rapida? È fattibile in Core Image, o forse in un filtro personalizzato OpenGL?

Il problema Upsampling:

Upsampling è davvero utilizzato solo quando si crea una piramide laplaciano. Tuttavia, l'idea ingenua di eseguire questa operazione è:

Inizializzare R, un contesto di immagine vuota della dimensione a cui si desidera eseguire l'upsample. In questo caso eseguiremo il campionamento della foto di Lenna sottocampionata come visto sopra, quindi R deve essere un'immagine vuota 512x512.

Successivamente, moltiplica i valori dei pixel dell'immagine sottosampata, I di 4. Ciò può essere eseguito in modo rapido convolvendo l'immagine con la matrice 3x3 [0,0,0;0,4,0;0,0,0]. Quindi è possibile distribuire uniformemente i pixel dell'immagine nell'immagine vuota più grande, R.Questo appare come:

enter image description here

Infine, si può impiegare la stessa 5 rubinetto sfocatura gaussiana su questa immagine per recuperare l'immagine upsampling:

enter image description here

Mi piacerebbe sapere se si tratta di possibile impiegare un metodo simile di sovracampionamento rapido.

Un'altra cosa di cui non sono sicuro è se è davvero importante sulla tecnica ridimensionare un'immagine per il filtraggio gaussiano/laplaciano. In caso contrario, potrei usare il metodo integrato più veloce che provare a crearne uno mio.

+0

Hai visto questo: https://developer.apple.com/reference/metalperformanceshaders/mpsimagegaussianpyramid –

+0

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. –

+0

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

risposta

0

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 
} 
+0

L'unica cosa che non ho ancora fatto è creare una funzione che ottenga la nuova immagine da una piramide di immagini. Lo faremo più avanti e modificarlo. –

+0

Volevo solo lanciarlo, che avrà bisogno di un modo per preservare il segno negativo che fluttua tra il livello di Laplacian, dal momento che qualsiasi calcolo che fai con essi DEVE essere preservato. –

2

Il GPUImage processing library può darti un po 'di campionamento e può portare alla tua piramide di Laplacian .

pod 'GPUImage'

NITIDEZZA UPSAMPLING:

UIImage *inputImage = [UIImage imageNamed:@"cutelady"]; 
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc]initWithImage:inputImage]; 
GPUImageSharpenFilter *stillImageFilter = [[GPUImageSharpenFilter alloc] init]; 
[stillImageSource addTarget:stillImageFilter]; 
[stillImageFilter useNextFrameForImageCapture]; 
[stillImageSource processImage]; 
UIImage *currentFilteredVideoFrame = [stillImageFilter imageFromCurrentFramebuffer]; 

Lanczos UPSAMPLING:

UIImage *inputImage = [UIImage imageNamed:@"cutelady"]; 
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:inputImage]; 
GPUImageLanczosResamplingFilter *stillImageFilter = [[GPUImageLanczosResamplingFilter alloc] init]; 
[stillImageSource addTarget:stillImageFilter]; 
[stillImageFilter useNextFrameForImageCapture]; 
[stillImageSource processImage]; 
[stillImageSource forceProcessingAtSizeRespectingAspectRatio:CGSizeMake(200, 200)]; 
UIImage *currentFilteredVideoFrame = [stillImageFilter imageFromCurrentFramebuffer]; 
cell.imageView.image = currentFilteredVideoFrame; 
+0

La cosa da notare qui è che stai usando una Trasformazione di Lanczos - un tipo specifico di metodo di ricampionamento - per ridimensionare l'immagine. Questa trasformazione è ideale se si desidera preservare il maggior numero di dettagli possibile, ma non è proprio quello che sto chiedendo: in effetti, per creare una piramide di immagini, non ci si preoccupa quasi di preservare i dettagli. –