Downsampling и Upsampling для гауссовских пирамид изображений в Swift
введение
мне интересно написать функцию, которая выводит для меня следующий уровень в Гауссовой пирамиде(я в конечном итоге хочу добраться до создания пирамиды Лапласа) для использования в обработке изображений. (Ссылка для справки https://en.wikipedia.org/wiki/Pyramid_(image_processing)#Gaussian_pyramid)
Проблема Downsampling
теперь легкая часть этого заключается в том, что когда вы вниз / вверх, 5-нажмите фильтр convoled с изображения перед изменением размера.
тем не менее, интересная часть о создании пирамид изображений заключается в том, что вам нужно уменьшить и увеличить изображение в разы .5 или 2, в зависимости от того, в каком направлении вы идете. Swift имеет несколько способов сделать это, например, с помощью CIAffineTransform и CILanczosTransform, однако мне интересно, есть ли способы сделать это немного наивнее, потому что меня не волнует качество измененного изображения. Для этого поста я собираюсь используйте Lenna (512x512) в качестве примера, как показано ниже:
если мы хотим уменьшить изображение в два раза, мы возьмем все нечетные пиксельные данные для формирования нового изображения. В MATLAB это выполняется следующим образом (после гауссова размытия):
если I
- Это ваше входное изображение и размер NxM, с отображениями цветов 3, сохраненными для P (матрица 512x512x3), а затем уничтоженное изображение по шкале .5 is
R = I(1:2:end, 1:2:end,:)
все новое изображение является предыдущим с нечетными нумерованными столбцами и строками изображения. Это дает следующее, фотография 256x256, которая является первым уровнем гауссовой пирамиды:
существует ли такая вещь в swift? Это выполнимо в образе ядра или, может быть, пользовательский фильтр OpenGL?
Проблема Upsampling:
Upsampling действительно только используется при создании пирамиды Лапласа. Однако наивная идея сделать это-сделать следующее:
инициализации R
, пустой контекст изображения размера, который вы хотите upsample. В этом случае мы будем upsampling downsampled фотографии Ленна, как показано выше, так R
должно быть 512x512 пустое изображение.
затем умножьте значения пикселей изображения с пониженной суммой,I
на 4. Это можно сделать в swift, свернув изображение с матрицей 3x3 [0,0,0;0,4,0;0,0,0]
. Затем один может равномерно распределять пиксели изображения в большее пустое изображение,R
. Это выглядит так:
наконец, можно использовать те же 5 нажмите гауссова размытия на этом изображении, чтобы восстановить увеличенного изображения:
Я хотел бы знать, можно ли использовать аналогичный метод upsampling в swift.
еще одна вещь, в которой я не уверен, если это действительно имеет значение на метод изменения размера изображения для фильтрации Гаусса / Лапласа. Если нет, то, конечно, я мог бы просто использовать самый быстрый встроенный метод, чем пытаться сделать свой собственный.
2 ответов
на библиотека обработки GPUImage может дать вам некоторую выборку и, возможно, привести к вашему Пирамиды Лапласа.
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];
ЛАНЦОША АПСЕМПЛИНГ:
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;
Я добились определенного прогресса, и я считаю это ответом на мой вопрос, хотя некоторые вещи немного разные, и я не думаю, что этот метод очень быстро. Я хотел бы услышать от кого-нибудь, чтобы увидеть, как сделать этот код быстрее. Ниже кажется, что изменение размера изображения занимает больше всего времени, я получаю тонну звонков в раздел ovveride outputImage, и я понятия не имею, почему это так. К сожалению, когда я запускаю функцию пирамиды Лапласа ниже, она занимает около 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-крановым фильтром, как описано в статье Берта и Адельсона. Не уверен, как избавиться от неловкое граничащих пикселей, что, кажется, лишние.
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
}
наконец, эта функция создает пирамиду Лапласа с заданным количеством уровней. Обратите внимание, что в обеих функциях пирамиды каждый уровень хранится в массиве.
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
}