2016-07-01 5 views
10

परिचयdownsampling और स्विफ्ट में गाऊसी छवि से पिरामिडों के Upsampling

मैं एक समारोह है कि मेरे लिए आउटपुट एक गाऊसी पिरामिड में अगले स्तर लेखन में रुचि (मैं अंत में एक Laplacian पिरामिड बनाने के लिए प्राप्त करना चाहते हैं) कर रहा हूँ छवि प्रसंस्करण में उपयोग के लिए। (संदर्भ के लिए लिंक https://en.wikipedia.org/wiki/Pyramid_(image_processing)#Gaussian_pyramid)

downsampling समस्या

अब इस का आसान हिस्सा है कि जब आप नीचे/upsample, एक 5-नल फिल्टर का आकार बदलने से पहले छवि के साथ convoled है।

हालांकि, छवि पिरामिड बनाने के बारे में दिलचस्प हिस्सा यह है कि आप जिस दिशा में जा रहे हैं उसके आधार पर आपको 5 या 2 के कारक द्वारा एक छवि को डाउन नमूना और अपमान करना होगा। स्विफ्ट के पास ऐसा करने के कुछ तरीके हैं, जैसे कि सीआईएफफिन ट्रान्सफॉर्म और सिलैन्ज़ोस ट्रान्सफॉर्म का उपयोग करना, हालांकि मैं सोच रहा हूं कि इसे थोड़ा और अधिक नैतिक तरीके से करने के तरीके हैं क्योंकि मुझे आकार बदलने वाली छवि की गुणवत्ता की परवाह नहीं है। इस पोस्ट के लिए, मैं एक उदाहरण के रूप Lenna (512x512) का उपयोग करने के लिए जा रहा हूँ, नीचे देखी गई:

The famous Lenna

हम दो का एक पहलू से एक छवि downsample चाहते हैं, हम अजीब के सभी ले जाएगा एक नई छवि बनाने के लिए क्रमांकित पिक्सेल डेटा। MATLAB में इस (गाऊसी कलंक के बाद) के रूप में किया जाता है:

हैं I पैमाने के आधार पर आपके इनपुट छवि तो नाश छवि है और पी (एक 512x512x3 मैट्रिक्स) के लिए भंडारित 3 रंग मैपिंग के साथ, आकार में NxM है, .5 की

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

सभी नई छवि पिछले है के साथ अजीब गिने कॉलम और छवि की पंक्तियों गिने है।

Downsampled Lenna

ऐसी बात स्विफ्ट में मौजूद है: यह निम्नलिखित, गाऊसी पिरामिड के प्रथम स्तर है कि एक 256x256 तस्वीर पैदावार? क्या यह कोर छवि, या शायद एक ओपनजीएल कस्टम फ़िल्टर में करने योग्य है?

Upsampling समस्या:

Upsampling वास्तव में केवल एक Laplacian पिरामिड बनाते समय किया जाता है। हालांकि ऐसा करने का बेवकूफ विचार निम्नलिखित है:

R आरंभ करें, आकार के रिक्त छवि संदर्भ को अपमानित करना चाहते हैं। इस मामले में हम ऊपर दिखाए गए डाउनस्प्लेड लेनना फोटो को अपनाना चाहते हैं, इसलिए R 512x512 खाली छवि होना चाहिए।

इसके बाद, 3x3 मैट्रिक्स [0,0,0;0,4,0;0,0,0] के साथ छवि convolving द्वारा downsampled चित्र का पिक्सेल मूल्यों, I 4. यह द्वारा तेजी से किया जा सकता गुणा। फिर कोई छवि के पिक्सल को बड़ी रिक्त छवि, R में समान रूप से वितरित कर सकता है।

enter image description here

अंत में, एक ही 5 नल गाऊसी कलंक इस छवि पर upsampled छवि ठीक करने के लिए काम कर सकते हैं::

enter image description here

मैं अगर यह जानना चाहते हैं इस तरह दिखता है तेजी से upsampling की एक समान विधि को रोजगार के लिए संभव है।

एक और चीज जो मुझे अनिश्चित है, यह वास्तव में गाऊशियन/लैपलासीन फ़िल्टरिंग के लिए छवि का आकार बदलने के लिए तकनीक पर महत्वपूर्ण है। यदि नहीं, तो निश्चित रूप से मैं अपना खुद का बनाने की कोशिश करने से बस सबसे तेज़ तरीके से निर्मित विधि का उपयोग कर सकता हूं।

+0

आप को देखा है यह: https://developer.apple.com/reference/metalperformanceshaders/mpsimagegaussianpyramid –

+0

मेरे पास है, लेकिन मैं वास्तव में अगर वहाँ था कस्टम फ़िल्टर उपयोग करने से पहले एक विकल्प है कि मैं क्या चाहते हैं देखना चाहता था । ये चीजें छवि प्रसंस्करण में इतनी असामान्य नहीं हैं, इसलिए मुझे लगा कि सेब के पहले से निर्मित एक तरीका होगा। –

+0

मुझे यकीन नहीं है कि इसमें आपके पास आवश्यक सभी कार्य हैं, लेकिन आप 'फ्रेमवर्क को तेज करें' https://developer.apple.com/videos/play/wwdc2013/713/ – juanjo

उत्तर

0

मैं कुछ प्रगति की है, और मैं काफी यह मेरा प्रश्न का उत्तर पर विचार , हालांकि कुछ चीजें एक अलग हैं और मैं टी नहीं करता हूं इस विधि को हिंक करें बहुत तेज है। मुझे यह कोड देखने के लिए किसी से भी सुनना अच्छा लगेगा कि इस कोड को तेज़ी से कैसे बनाया जाए। नीचे दिए गए, ऐसा लगता है कि छवि का आकार बदलने में सबसे अधिक समय लग रहा है, मुझे ओवेराइड आउटपुट इमेज सेक्शन में कॉल का एक टन मिलता है और मुझे नहीं पता कि यह क्यों है। दुर्भाग्य से जब मैं नीचे लाप्लाशियन पिरामिड फ़ंक्शन चलाता हूं, तो 275x300 फ़ोटो पर पूरा होने में लगभग 5 सेकंड लगते हैं। यह सिर्फ अच्छा नहीं है, और मैं इसे कम करने के तरीके के रूप में कुछ नुकसान में हूं। मेरा संदेह यह है कि पुनर्विक्रय फ़िल्टर अपराधी है। हालांकि मुझे यह जानने के लिए पर्याप्त जानकारी नहीं है कि इसे कैसे तेजी से बनाया जाए।

पहले, कस्टम फिल्टर:

यह पहले एक एक सरल rescaling करके एक छवि आकार बदलता है। मुझे लगता है कि यह इस मामले में पुनर्विक्रय की सबसे अच्छी तकनीक है क्योंकि जो कुछ भी किया जाता है वह आकार बदलते समय पिक्सेल की प्रतिकृति है।

[ ][ ][x][ ] ----->[ ][ ][ ][ ][x][x][ ][ ] (साइमन 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 
    } 
} 

यह: उदाहरण के लिए, अगर हम पिक्सल के निम्नलिखित ब्लॉक है और एक 2.0 पैमाने करते हैं, तो मानचित्रण की तरह लग रहा है निम्नलिखित एक साधारण अंतर मिश्रण है।

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) 

    } 

} 
अब कुछ समारोह परिभाषा के लिए

:

इस समारोह बस छवि पर एक गाऊसी कलंक करता है, बर्ट & Adelson के पत्र में वर्णित के रूप में ही 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) 

} 

यह फ़ंक्शन बस अनुकरण के उपयोग को सरल बनाता है। आप नई छवि का लक्ष्य आकार निर्दिष्ट कर सकते हैं।स्केल पैरामीटर आईएमओ सेट करने के बजाय यह सौदा करना आसान हो जाता है।

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 
} 

अंत में, इस समारोह का स्तर की दी गई संख्या के साथ Laplacian पिरामिड बनाता है। ध्यान दें कि पिरामिड कार्यों दोनों में, प्रत्येक स्तर को ऐरे में संग्रहीत किया जाता है।

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'

पैनापन 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

यहां नोट करने की बात यह है कि आप एक लांज़ोस ट्रांसफॉर्म का उपयोग कर रहे हैं - एक विशिष्ट प्रकार की पुनरावृत्ति विधि - छवि को पुन: सहेजने के लिए। यह परिवर्तन आदर्श है यदि आप जितना संभव हो उतना विस्तार संरक्षित करना चाहते हैं, लेकिन यह काफी नहीं है जो मैं पूछ रहा हूं - वास्तव में, एक छवि पिरामिड बनाने के लिए, आपको लगभग विस्तार को संरक्षित करने की परवाह नहीं है। –

संबंधित मुद्दे