1. 程式人生 > >【影象縮放】雙立方(三次)卷積插值(Android版改寫)

【影象縮放】雙立方(三次)卷積插值(Android版改寫)

最近在做圖片放大之後的畫面處理,嘗試了這種卷積插值法,原文如下:https://dailc.github.io/2017/11/01/imageprocess_bicubicinterpolation.html

然後我將其工程簡單地改寫成了Android版本的程式碼(只是個Demo,用來看看效果,可能含Bug):

package com.cjz.image;

/**
 * Created by cjz on 2018/12/5.
 */

import android.graphics.Bitmap;

/**
 * 取樣公式的常數A取值,調整銳化與模糊
 * -0.5 三次Hermite樣條
 * -0.75 常用值之一
 * -1 逼近y = sin(x*PI)/(x*PI)
 * -2 常用值之一
 *
 * todo bug 只能放大不能縮小
 */
public class ConvolutionImageProccess {
    private static final float A = -1;

    /**插值核心方程**/
    private static float interpolationCalculate(float x) {
        float absX = x >= 0 ? x : -x;
        float x2 = x * x;
        float x3 = absX * x2;

        if (absX <= 1) {
            return 1 - (A + 3) * x2 + (A + 2) * x3;
        } else if (absX <= 2) {
            return -4 * A + 8 * A * absX - 5 * A * x2 + A * x3;
        }

        return 0;
    }

    private static int getPixelValue(int pixelValue) {
        int newPixelValue = pixelValue;

        newPixelValue = Math.min(255, newPixelValue);
        newPixelValue = Math.max(0, newPixelValue);

        return newPixelValue;
    }

    /**
     * 獲取某行某列的畫素對於的rgba值
     * @param {Object} data 影象資料
     * @param {Number} srcWidth 寬度
     * @param {Number} srcHeight 高度
     * @param {Number} row 目標畫素的行
     * @param {Number} col 目標畫素的列
     */
    private static int[] getRGBAValue(int data[], int srcWidth, int srcHeight, int row, int col) {
        int newRow = row;
        int newCol = col;

        if (newRow >= srcHeight) {
            newRow = srcHeight - 1;
        } else if (newRow < 0) {
            newRow = 0;
        }

        if (newCol >= srcWidth) {
            newCol = srcWidth - 1;
        } else if (newCol < 0) {
            newCol = 0;
        }

        int newIndex = (newRow * srcWidth) + newCol;

        newIndex *= 4;

        return new int[] {
                data[newIndex + 0],
                data[newIndex + 1],
                data[newIndex + 2],
                data[newIndex + 3]
        };
    }




    private static  void  scale(int data[], int width, int height, int newData[], int newWidth, int newHeight) {
        int dstData[] = newData;

        // 計算壓縮後的縮放比
        float scaleW = newWidth / width;
        float scaleH = newHeight / height;

        // 區塊
        for (int col = 0; col < newWidth; col += 1) {
            for (int row = 0; row < newHeight; row += 1) {
                filter(data, dstData, col, row, width, height, scaleW, scaleH, newWidth);
            }
        }
    }


    private static  void filter(int data[], int dstData[], int dstCol, int dstRow, int width ,int height, float scaleW, float scaleH, int newWidth){
        // 源影象中的座標(可能是一個浮點)
        int srcCol = (int) Math.min(width - 1, dstCol / scaleW);
        int srcRow = (int) Math.min(height - 1, dstRow / scaleH);
        int intCol = (int) Math.floor(srcCol);
        int intRow = (int) Math.floor(srcRow);
        // 計算u和v
        int u = srcCol - intCol;
        int v = srcRow - intRow;
        // 真實的index,因為陣列是一維的
        int dstI = (dstRow * newWidth) + dstCol;
        dstI *= 4;
        // 儲存灰度值的權重卷積和
        int rgbaData[] = new int[]{0, 0, 0, 0};
        // 根據數學推導,16個點的f1*f2加起來是趨近於1的(可能會有浮點誤差)
        // 因此就不再單獨先加權值,再除了
        // 16個鄰近點
        for (int m = -1; m <= 2; m += 1) {
            for (int n = -1; n <= 2; n += 1) {
                int rgba[] = getRGBAValue(data, width, height, intRow + m, intCol + n);
                // 一定要正確區分 m,n和u,v對應的關係,否則會造成影象嚴重偏差(譬如出現噪點等)
                // F(row + m, col + n)S(m - v)S(n - u)
                float f1 = interpolationCalculate(m - v);
                float f2 = interpolationCalculate(n - u);
                float weight = f1 * f2;

                rgbaData[0] += rgba[0] * weight;
                rgbaData[1] += rgba[1] * weight;
                rgbaData[2] += rgba[2] * weight;
                rgbaData[3] += rgba[3] * weight;
            }
        }

        dstData[dstI + 0] = getPixelValue(rgbaData[0]);
        dstData[dstI + 1] = getPixelValue(rgbaData[1]);
        dstData[dstI + 2] = getPixelValue(rgbaData[2]);
        dstData[dstI + 3] = getPixelValue(rgbaData[3]);
    }

    public static Bitmap bicubicInterpolation(Bitmap src, int newWidth, int newHeight) {
        int pixels[] = new int[src.getWidth() * src.getHeight() * 4];
        int offset = 0;
        for(int y = 0; y < src.getHeight(); y++) {
            for (int x = 0; x < src.getWidth(); x++) {
                int pixel = src.getPixel(x, y);
                int alpha = pixel >> 24 & 0xFF;
                int red = pixel >> 16 & 0xFF;
                int green = pixel >> 8 & 0xFF;
                int blue = pixel & 0xFF;
                pixels[offset++] = alpha;
                pixels[offset++] = red;
                pixels[offset++] = green;
                pixels[offset++] = blue;
            }
        }
        int newPixels[] = new int[newWidth * newHeight * 4];
        scale(pixels,
                src.getWidth(),
                src.getHeight(),
                newPixels,
                newWidth,
                newHeight);

        int newImgData[] = new int[newWidth * newHeight];
        for(int i = 0, j = 0; i < newPixels.length; j++){
            int alpha = newPixels[i++];
            int red = newPixels[i++];
            int green = newPixels[i++];
            int blue = newPixels[i++];
            newImgData[j] |= (alpha << 24);
            newImgData[j] |= (red << 16);
            newImgData[j] |= (green << 8);
            newImgData[j] |= (blue);
        }
        Bitmap bitmap = Bitmap.createBitmap(newImgData, newWidth, newHeight, Bitmap.Config.ARGB_8888);
        return bitmap;
    }


    private static int getPixelInPixelsArray(int array[], int width, int height, int x, int y) {
        int offset = width * y + x;
        if (offset < array.length && x > 0 && y > 0) {
            return array[offset];
        } else {
            return 0;
        }
    }
}

文字和線條柵格化之後的圖片放大兩倍效果: