Даунсэмплинга и аппроксимация для гауссовых пирамид изображения в Swift

Введение

Мне интересно написать функцию, которая выводит для меня следующий уровень в Gaussian Pyramid (в конечном итоге я хочу создать Лапласианскую пирамиду) для использования в обработке изображений. (Ссылка для ссылки https://en.wikipedia.org/wiki/Pyramid_(image_processing)#Gaussian_pyramid)

Проблема понижающего дискретизации

Теперь легкая часть этого заключается в том, что когда вы снижаете/повышаете выбор, фильтр 5-кратковременно сверяется с изображением перед изменением размера.

Однако интересная часть создания пирамид изображения заключается в том, что вам приходится уменьшать и увеличивать размер изображения в 0,5 или 2 раза в зависимости от того, в каком направлении вы собираетесь двигаться. У Swift есть несколько способов сделать это, например, используя CIAffineTransform и CILanczosTransform, однако мне интересно, есть ли способы сделать это немного наивно, потому что меня не интересует качество измененного изображения. Для этого сообщения я собираюсь использовать Lenna (512x512) в качестве примера, как показано ниже:

Знаменитая Lenna

Если мы хотим уменьшить размер изображения в два раза, мы возьмем все нечетные пиксельные данные для формирования нового изображения. В MATLAB это выполняется следующим образом (после гауссовского размытия):

Если I является вашим входным изображением и имеет размер NxM, с 3-мя сопоставлениями цветов, хранящимися для P (матрица 512x512x3), то уменьшенное изображение по шкале от .5 составляет

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

Все новое изображение является предыдущим с нечетными пронумерованными столбцами и строками изображения. Это дает следующее: 256x256 фото, которое является первым уровнем гауссовой пирамиды:

Downsampled Lenna

существует ли такая вещь в быстром? Является ли это выполнимым в Core Image или, возможно, в качестве настраиваемого фильтра OpenGL?

Проблема с повышающей дискретизацией:

Upsampling действительно используется только при создании лапласианской пирамиды. Однако наивная идея сделать это - сделать следующее:

Инициализировать R, контекст чистого изображения, размер которого вы хотите увеличить. В этом случае мы будем повышать дискретизацию сниженной фотографии Lenna, как показано выше, поэтому R должно быть пустым изображением 512x512.

Затем умножьте значения пикселей изображения с пониженной дискретизацией, I на 4. Это можно сделать быстро, свернув изображение с помощью матрицы 3x3 [0,0,0;0,4,0;0,0,0]. Затем можно равномерно распределить пиксели изображения в большее пустое изображение, R. Это выглядит так:

введите описание изображения здесь

Наконец, на этом изображении можно использовать одно и то же 5-кратное гауссовское размытие, чтобы восстановить изображение с улучшенной дискретизацией:

введите описание изображения здесь

Я хотел бы знать, можно ли использовать подобный метод upsampling в swift.

Еще одна вещь, о которой я не знаю, - это то, что на практике важно изменить размер изображения для гауссовой/лапласианской фильтрации. Если нет, то, конечно, я мог бы использовать самый быстрый встроенный метод, чем пытаться сделать свой собственный.

Ответ 1

Я сделал некоторый прогресс, и я в значительной степени считаю это ответом на мой вопрос, хотя некоторые вещи немного разные, и я не думаю, что этот метод очень быстрый. Я хотел бы услышать от кого-нибудь, как быстрее сделать этот код. В приведенном ниже примере кажется, что изменение размера изображения занимает больше всего времени, я получаю TON звонков в секцию вывода ovveride outputImage, и я понятия не имею, почему это так. К сожалению, когда я запускаю функцию Laplacian Pyramid ниже, она занимает около 5 секунд, чтобы закончить фотографию 275x300. Это просто нехорошо, и я немного теряю, как ускорить это. Мое подозрение в том, что фильтр повторной выборки является виновником. Однако я недостаточно разбираюсь, чтобы узнать, как сделать это быстрее.

Во-первых, пользовательские фильтры:

Это первое изменение размера изображения путем простого масштабирования. Я думаю, что это лучший способ масштабирования в этом случае, потому что все, что делается, это репликация пикселей при изменении размера. Например, если мы имеем следующий блок пикселей и выполняем масштаб 2.0, тогда отображение выглядит следующим образом:

[ ][ ][x][ ] ----->[ ][ ][ ][ ][x][x][ ][ ] (Спасибо Симону Гладману за идею об этом)

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

Это простая смесь различий.

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-кратным фильтром, как описано в бумаге Burt и Adelson. Не уверен, как избавиться от неудобных граничащих пикселей, которые кажутся лишними.

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)

}

Эта функция просто упрощает использование повторной выборки. Вы можете указать размер цели нового изображения. С этим легче справляться, чем устанавливать параметр масштаба 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
}

Ответ 2

библиотека обработки GPUImage может дать вам некоторую выборку и, возможно, привести к вашей пирамиде Лапласа.

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