2016-07-01 5 views
10
におけるガウス画像ピラミッドのためのダウンサンプリングとアップサンプリング

はじめスウィフト

私は私のためにガウシアンピラミッド(私は最終的にはラプラシアンピラミッドの作成に取得したい)の次のレベルを出力する関数を書くことに興味があります画像処理に使用される。 (参考リンクhttps://en.wikipedia.org/wiki/Pyramid_(image_processing)#Gaussian_pyramid

ダウンサンプリング問題

さて、これの簡単な部分であることをあなたダウン/アップサンプリング、5タップのフィルタは、リサイズ前の画像とconvoledされたとき。

しかし、画像ピラミッドを作成することについての興味深い点は、進行方向に応じて、画像をダウンサンプリングして0.5倍または2倍アップサンプリングする必要があることです。 Swiftには、CIAffineTransformやCILanczosTransformのようないくつかの方法がありますが、サイズ変更された画像の品質には気にしないので、ちょっと気をつけてください。我々は2倍に画像をダウンサンプリングしたい場合は、私たちは奇数のすべてを取るだろう

The famous Lenna

:この記事のために、私は以下の見て、一例として、レナ(512×512)を使用するつもりです新たな画像を形成する。

が入力画像であり、P(512x512x3行列)用に3つの色マッピングが格納されたNxMのサイズの場合、スケールで間引かれた画像は次のように実行されます(ガウスブラー後)。 0.5の

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

あるすべての新しいイメージは、奇数は、画像の番号が列と行の番号が付けで、前のです。これは、次のことを得、ガウシアンピラミッドの最初のレベルである256×256写真:

Downsampled Lenna

そのようなことは、迅速に存在していますか?それはCore Image、あるいはOpenGLカスタムフィルタで可能ですか?

アップサンプリングの問題:ラプラシアンピラミッドを作成するときに

アップサンプリングが実際にのみ使用されます。しかし、これを行うための素朴なアイデアは、以下を行うことです:

Rを初期化します。アップサンプルしたいサイズの空白のイメージコンテキスト。この場合、上記のようにダウンサンプリングしたLenna写真をアップサンプリングするので、Rは512x512の空白イメージでなければなりません。

次に、ダウンサンプリングされた画像のピクセル値であるIに4を掛けます。これは、画像を3x3の行列[0,0,0;0,4,0;0,0,0]で畳み込むことによって迅速に行うことができます。次に、画像のピクセルを大きな空白の画像に一様に分配することができます。Rこれは、次のようになります。

enter image description here

最後に、1はアップサンプリングされた画像回復するために、この画像に同じ5タップガウスぼかしを採用することができます:それはだ場合

enter image description here

を私は知りたいのですが迅速なアップサンプリングの同様の方法を用いることが可能である。

ガウス/ラプラシアンフィルタリングの画像のサイズを変更するテクニックで本当に問題があるかどうかは不明です。もしそうでなければ、確かに私は自分で作ろうとするよりも、最も速い方法で作ったものを使うことができます。

+0

あなたが見たことがあり、この:https://developer.apple.com/reference/metalperformanceshaders/mpsimagegaussianpyramid –

+0

私は持っているが、私は本当にカスタムフィルタを使用する前に、代替は私が欲しいものを得るためにあったかどうかを確認したかったです。これらのことはあまり画像処理ではあまり一般的ではありませんので、あらかじめリンゴを組み込んだ方法があると思いました。 –

+0

あなたが必要とする全ての機能を持っているかどうかは分かりませんが、 'Accelerate Framework'を試すことができますhttps://developer.apple.com/videos/play/wwdc2013/713/ – juanjo

答えて

0

私はいくつかの進歩を遂げている、と私はかなりこの私の質問への答えを検討、いくつかのものは少し異なるですが、私はしませんこの方法は非常に高速ですヒンク。誰もがこのコードをより速くする方法を知りたいと思っています。以下では、画像のサイズを変更するのが最も時間を要しているようですが、ovveride outputImageセクションへの呼び出しがたくさんあり、それがなぜなのかわかりません。残念ながら、以下のLaplacian Pyramid機能を実行すると、275x300の写真で約5秒かかります。これはちょうど良いことではありません、私はそれをスピードアップする方法について少し損失があります。私の疑問は、リサンプルフィルタが原因だということです。しかし、私はそれをより速くする方法を知るために十分な精通がありません。

まず、カスタムフィルタ:

この最初のものは、単純な再スケーリングすることにより、画像のサイズを変更します。私は、サイズ変更時にピクセルの複製が行われるため、このケースでは再スケーリングの最善の手法だと思います。私たちは、画素の次のブロックを持っており、2.0のスケールを行う場合たとえば、マッピングは次のようになります。

[ ][ ][x][ ] ----->[ ][ ][ ][ ][x][x][ ][ ](この1上のアイデアのためのサイモンGladmanのおかげで)

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

この1つは単純な違いのブレンドです。

いくつかの関数定義のすぐ
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) 

    } 

} 

この関数は単にバート&アデルソンの論文に記載されたのと同じ5タップのフィルタによれば、画像にガウスぼかしを行います。余分なように見えるぎこちない境界ピクセルを取り除く方法を確かめないでください。

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) 

} 

この関数は、resampleの使用を単純化します。新しいイメージのターゲットサイズを指定できます。これは、スケールパラメータ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 
} 

この機能は、差異フィルタの使用を単純化します。ちょうどそれが

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 

} 

この関数は、各ピラミッドのレベル次元を計算し、配列に格納します。後で有効です。

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 
} 

ここでは良いことに:これは与えられたレベル数のガウスピラミッドを作成します。

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 
} 

最後に、この関数は、指定されたレベル数のラプラシアンピラミッドを作成します。両方のPyramid関数では、各レベルが配列に格納されていることに注意してください。

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

私がまだやっていない唯一のことは、画像ピラミッドから新しい画像を取得する機能を作ることです。それを後で行い、それを編集してください。 –

+0

ラプラシアンレベル間で浮動するマイナス記号を保存する方法が必要です。ラプラシアンレベルでの計算が保存されなければならないためです。 –

2

GPUImage processing libraryは、いくつかのアップサンプリングを行い、ラプラシアンピラミッドにつながる可能性があります。

pod 'GPUImage'

シャープにアップサンプリング:

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アップサンプリング:

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

ここで注意すべきことは、イメージを再スケーリングするために、特定の種類の再サンプリング方法であるLanczos Transformを使用しています。この変換は、可能な限り詳細を保持したい場合に理想的ですが、これは私が求めているものではありません。実際、画像のピラミッドを作成するには、ほとんどの場合詳細を保存することは気にしません。 –