Грубые края с повторной выборкой Ланцоша на Голанге

Я писал некоторые основные методы изменения размера изображений в Голанге. Я видел несколько сообщений об изменении размера изображений, но я не могу понять, что мне не хватает...

По сути, моя проблема заключается в том, что при изменении размера изображения в Голанге мои результаты, как представляется, имеют много наложения.

Я пытался итеративно уменьшить частоту дискретизации изображения, но это не дало большого улучшения.

Вот мой код:

func resize(original image.Image,
    edgeSize int, filterSize int) image.Image {

    oldBounds := original.Bounds()
    if oldBounds.Dx() < edgeSize && oldBounds.Dy() < edgeSize {
        // No resize necessary
        return original
    }

    threshold := edgeSize * 4 / 3
    if oldBounds.Dx() > threshold || oldBounds.Dy() > threshold {
        fmt.Println("Upstream")
        original = customResizeImageToFitBounds(original, threshold, filterSize)
        oldBounds = original.Bounds()
    }

    newBounds := getNewBounds(oldBounds, edgeSize)

    resized := image.NewRGBA(newBounds)

    var ratioX = float64(oldBounds.Dx()) / float64(newBounds.Dx())
    var ratioY = float64(oldBounds.Dy()) / float64(newBounds.Dy())

    for x := 0; x < newBounds.Dx(); x++ {
        for y := 0; y < newBounds.Dy(); y++ {
            sourceX := ratioX * float64(x)
            minX := int(math.Floor(sourceX))

            sourceY := ratioY * float64(y)
            minY := int(math.Floor(sourceY))

            sampleSize := filterSize<<1 + 1
            var xCoeffs = make([]float64, sampleSize)
            var yCoeffs = make([]float64, sampleSize)

            var sumX = 0.0
            var sumY = 0.0
            for i := 0; i < sampleSize; i++ {
                xCoeffs[i] = lanczos(filterSize, sourceX-float64(minX+i-filterSize))
                yCoeffs[i] = lanczos(filterSize, sourceY-float64(minY+i-filterSize))

                sumX += xCoeffs[i]
                sumY += yCoeffs[i]
            }

            for i := 0; i < sampleSize; i++ {
                xCoeffs[i] /= sumX
                yCoeffs[i] /= sumY
            }

            rgba := make([]float64, 4)

            for i := 0; i < sampleSize; i++ {
                if yCoeffs[i] == 0.0 {
                    continue
                }
                currY := minY + i - filterSize

                rgbaRow := make([]float64, 4)
                for j := 0; j < sampleSize; j++ {
                    if xCoeffs[j] == 0.0 {
                        continue
                    }
                    currX := minX + i - filterSize

                    rij, gij, bij, aij := original.At(
                        clamp(currX, currY, oldBounds)).RGBA()

                    rgbaRow[0] += float64(rij) * xCoeffs[j]
                    rgbaRow[1] += float64(gij) * xCoeffs[j]
                    rgbaRow[2] += float64(bij) * xCoeffs[j]
                    rgbaRow[3] += float64(aij) * xCoeffs[j]
                }
                rgba[0] += float64(rgbaRow[0]) * yCoeffs[i]
                rgba[1] += float64(rgbaRow[1]) * yCoeffs[i]
                rgba[2] += float64(rgbaRow[2]) * yCoeffs[i]
                rgba[3] += float64(rgbaRow[3]) * yCoeffs[i]
            }

            rgba[0] = clampRangeFloat(0, rgba[0], 0xFFFF)
            rgba[1] = clampRangeFloat(0, rgba[1], 0xFFFF)
            rgba[2] = clampRangeFloat(0, rgba[2], 0xFFFF)
            rgba[3] = clampRangeFloat(0, rgba[3], 0xFFFF)

            var rgbaF [4]uint64
            rgbaF[0] = (uint64(math.Floor(rgba[0]+0.5)) * 0xFF) / 0xFFFF
            rgbaF[1] = (uint64(math.Floor(rgba[1]+0.5)) * 0xFF) / 0xFFFF
            rgbaF[2] = (uint64(math.Floor(rgba[2]+0.5)) * 0xFF) / 0xFFFF
            rgbaF[3] = (uint64(math.Floor(rgba[3]+0.5)) * 0xFF) / 0xFFFF

            rf := uint8(clampRangeUint(0, uint32(rgbaF[0]), 255))
            gf := uint8(clampRangeUint(0, uint32(rgbaF[1]), 255))
            bf := uint8(clampRangeUint(0, uint32(rgbaF[2]), 255))
            af := uint8(clampRangeUint(0, uint32(rgbaF[3]), 255))

            resized.Set(x, y, color.RGBA{R: rf, G: gf, B: bf, A: af})
        }
    }
    return resized
}


// Machine epsilon
var epsilon = math.Nextafter(1.0, 2.0) - 1

func lanczos(filterSize int, x float64) float64 {
    x = math.Abs(x)
    fs := float64(filterSize)
    if x < epsilon {
        return 1.0
    }

    if x > fs {
        return 0
    }

    piX := math.Pi * x
    piXOverFS := piX / fs
    return (math.Sin(piX) / piX) * (math.Sin(piXOverFS) / (piXOverFS))
}

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

Кто-нибудь, кто имеет опыт с передискретизацией изображений, видит что-то потенциально проблемное?

Для справки, вот мое исходное изображение: Исходное изображение

Вот мой результат:

Вот мой результат, если я уберу рекурсивный вызов:

Вот результат использования RMagick/ImageMagick через Ruby (для чего я снимаю):

У кого-нибудь есть совет, как мне получить более плавный результат? Этот конкретный пример является довольно резким уменьшением масштаба, но Rmagick смог очень быстро уменьшить его с отличным качеством, поэтому это должно быть возможно.

Мне сказали, что Lanczos3 Resampling дает хорошие результаты, и это то, что я пытаюсь использовать здесь - я не уверен, что моя реализация верна.

Также, как примечание: преобразование 0xFF / 0xFFFF происходит потому, что функция "At" golang возвращает значения rgba в диапазоне [0, 0xFFFF] ([0, 65535]), но "Set" принимает цвет, который инициализируется с диапазоном [0, 0xFF] ([0, 255])

На данный момент меня больше заботит качество, чем производительность.

1 ответ

Решение

Хорошо, я думаю, что нашел один способ решить проблему с алиасами. Вместо того чтобы использовать lanczos3, я использовал билинейную интерполяцию для повторной выборки исходного изображения с размером, немного превышающим размер, который я собирался (edgeSize = 1080), гауссово размытие изображения, затем масштабирование изображения до целевого размера (edgeSize = 600) на этот раз с бикубической интерполяцией. Это дало мне результаты, примерно такие же, как те, что мне давал RMagick.

Другие вопросы по тегам