チーム15C7

課題名

モザイク処理を行うAndroidアプリの実装

研究者名

3年14組67番 Shota Shimanaka
3年14組68番 Shun Hosaka

使用言語

Java

概要

カメラから取り込んだ写真にモザイク処理をかける。
スライダーにより値を変化させることでモザイクを行うビット数を調節可能である。

今回は、適当な実画像を読み込みたかったためAndroidアプリの形で、カメラを使用して画像の読み込みを行うようにした。

以下の画像は、カメラから画像を読み込んだ後のスクリーンショットである。
device-2015-07-13-153540.png

次に、こちらがスライダーの値を43にした際のモザイク処理の結果である。
device-2015-07-13-153511.png


ソースコード

Andoridの余分な処理は省くが、以下が処理を行うように作成したUtilクラスである。
Mosaicにはアベレージフィルターを使用し、モザイク処理を行うようにした。

public class BitmapProcessingUtil {
  public static final String TAG = BitmapProcessingUtil.class.getSimpleName();

  public static void processingMosaic(final Bitmap bitmap, final int dot, final OnProcessingBitmapListener listener) {
       new AsyncTask<Bitmap, Integer, Bitmap>() {
           @Override
           protected Bitmap doInBackground(Bitmap... params) {
               Bitmap mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
               // ピクセルデータ分ループ
               final int width = bitmap.getWidth();
               final int height = bitmap.getHeight();
               int[] pixels = new int[width * height];
               mutableBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
               pixels = mozaicProcessing(width, height, dot, pixels);
               mutableBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
               return mutableBitmap;
           }

          @Override
           protected void onPostExecute(Bitmap bitmap) {
               super.onPostExecute(bitmap);
               if (listener != null) {
                   if (bitmap != null) {
                       listener.onSuccessProcessing(bitmap);
                   } else {
                       listener.onFailedProcessing();
                   }
               }
           }
       }.execute(bitmap);
   }

  private static int[] mozaicProcessing(final int width, final int height, final int dot, int[] pixels) {
       final int square = dot * dot;
       // ピクセルデータ分ループ
       for (int w = 0, widthEnd = width / dot; w < widthEnd; w++) {
           for (int h = 0, heightEnd = height / dot; h < heightEnd; h++) {
               // ドットの中の平均値を使う
               int sumR = 0;
               int sumG = 0;
               int sumB = 0;
               int moveX = w * dot;
               int moveY = h * dot;
               for (int dw = 0; dw < dot; dw++) {
                   for (int dh = 0; dh < dot; dh++) {
                       int dotColor = pixels[moveX + dw + (moveY + dh) * width];
                       sumR += Color.red(dotColor);
                       sumG += Color.green(dotColor);
                       sumB += Color.blue(dotColor);
                   }
               }
               sumR = sumR / square;
               sumG = sumG / square;
               sumB = sumB / square;
               for (int dw = 0; dw < dot; dw++) {
                   for (int dh = 0; dh < dot; dh++) {
                       pixels[moveX + dw + (moveY + dh) * width] = Color.rgb(sumR, sumG, sumB);
                   }
               }
           }
       }
       return pixels;
   }

  public interface OnProcessingBitmapListener {
       public void onSuccessProcessing(Bitmap bitmap);

      public void onFailedProcessing();
   }

}

また、今回は出力が不十分であったがFFTも以下の通り作成した。Utilクラスとして作成した。

public class FFTUtil {
  public static void fft(
           double[] inputRe,
           double[] inputIm,
           double[] outputRe,
           double[] outputIm,
           int bitSize) {
       int dataSize = 1 << bitSize;
       int[] reverseBitArray = bitScrollArray(dataSize);

      // バタフライ演算のための置き換え
       for (int i = 0; i < dataSize; i++) {
           outputRe[i] = inputRe[reverseBitArray[i]];
           outputIm[i] = inputIm[reverseBitArray[i]];
       }

      // バタフライ演算
       for (int stage = 1; stage <= bitSize; stage++) {
           int butterflyDistance = 1 << stage;
           int numType = butterflyDistance >> 1;
           int butterflySize = butterflyDistance >> 1;

          double wRe = 1.0;
           double wIm = 0.0;
           double uRe = Math.cos(Math.PI / butterflySize);
           double uIm = -Math.sin(Math.PI / butterflySize);

          for (int type = 0; type < numType; type++) {
               for (int j = type; j < dataSize; j += butterflyDistance) {
                   int jp = j + butterflySize;
                   double tempRe = outputRe[jp] * wRe - outputIm[jp] * wIm;
                   double tempIm = outputRe[jp] * wIm + outputIm[jp] * wRe;
                   outputRe[jp] = outputRe[j] - tempRe;
                   outputIm[jp] = outputIm[j] - tempIm;
                   outputRe[j] += tempRe;
                   outputIm[j] += tempIm;
               }
               double tempWRe = wRe * uRe - wIm * uIm;
               double tempWIm = wRe * uIm + wIm * uRe;
               wRe = tempWRe;
               wIm = tempWIm;
           }
       }
   }

  /// <summary>
   /// 1次元IFFT
   /// </summary>
   public static void ifft(
           double[] inputRe,
           double[] inputIm,
           double[] outputRe,
           double[] outputIm,
           int bitSize) {

      int dataSize = 1 << bitSize;

      for (int i = 0; i < dataSize; i++) {
           inputIm[i] = -inputIm[i];
       }
       fft(inputRe, inputIm, outputRe, outputIm, bitSize);
       for (int i = 0; i < dataSize; i++) {
           outputRe[i] /= (double) dataSize;
           outputIm[i] /= (double) (-dataSize);
       }
   }

  // ビットを左右反転した配列を返す
   private static int[] bitScrollArray(int arraySize) {
       int[] reBitArray = new int[arraySize];
       int arraySizeHarf = arraySize >> 1;

      reBitArray[0] = 0;
       for (int i = 1; i < arraySize; i <<= 1) {
           for (int j = 0; j < i; j++) {
               reBitArray[j + i] = reBitArray[j] + arraySizeHarf;
           }
           arraySizeHarf >>= 1;
       }
       return reBitArray;
   }

  /// <summary>
   /// 2次元FFT
   /// </summary>
   /// <param name="inDataR">実数入力部</param>
   /// <param name="inDataI">虚数入力部</param>
   /// <param name="outDataR">実数出力部</param>
   /// <param name="outDataI">虚数出力部</param>
   /// <param name="xSize">x方向サイズ</param>
   /// <param name="ySize">y方向サイズ</param>
   public static void fft2D(
           double[][] inDataRe,
           double[][] inDataIm,
           double[][] outDataRe,
           double[][] outDataIm,
           int xSize,
           int ySize) {
       double[][] tempRe = new double[xSize][ySize];
       double[][] tempIm = new double[xSize][ySize];

      int xbit = getBitNum(xSize);
       int ybit = getBitNum(ySize);

      for (int j = 0; j < ySize; j++) {
           double[] re = new double[xSize];
           double[] im = new double[xSize];
           fft(
                   getArray(inDataRe, xSize, j),
                   getArray(inDataIm, xSize, j),
                   re, im,
                   xbit);
           for (int i = 0; i < xSize; i++) {
               tempRe[i][j] = re[i];
               tempIm[i][j] = im[i];
           }
       }
       for (int i = 0; i < xSize; i++) {
           double[] re = new double[ySize];
           double[] im = new double[ySize];
           fft(
                   tempRe[i],
                   tempIm[i],
                   re, im,
                   ybit);
           for (int j = 0; j < ySize; j++) {
               outDataRe[i][j] = re[j];
               outDataIm[i][j] = im[j];
           }
       }
   }

  // ビット数取得
   private static int getBitNum(int num) {
       int bit = -1;
       while (num > 0) {
           num >>= 1;
           bit++;
       }
       return bit;
   }

  // 1次元配列取り出し
   private static double[] getArray(double[][] data2D, int xSize, int seg) {
       double[] reData = new double[xSize];
       for (int i = 0; i < xSize; i++) {
           reData[i] = data2D[i][seg];
       }
       return reData;
   }

  /// <summary>
   /// 2次元IFFT
   /// </summary>
   /// <param name="inDataR">実数入力部</param>
   /// <param name="inDataI">虚数入力部</param>
   /// <param name="outDataR">実数出力部</param>
   /// <param name="outDataI">虚数出力部</param>
   /// <param name="xSize">x方向サイズ</param>
   /// <param name="ySize">y方向サイズ</param>
   public static void ifft2D(double[][] inDataRe,
                             double[][] inDataIm,
                             double[][] outDataRe,
                             double[][] outDataIm,
                             int xSize,
                             int ySize) {
       double[][] tempRe = new double[ySize][xSize];
       double[][] tempIm = new double[ySize][xSize];

      int xbit = getBitNum(xSize);
       int ybit = getBitNum(ySize);

      for (int j = 0; j < ySize; j++) {
           double[] re = new double[xSize];
           double[] im = new double[xSize];
           ifft(
                   getArray(inDataRe, xSize, j),
                   getArray(inDataIm, xSize, j),
                   re, im, xbit);

          for (int i = 0; i < xSize; i++) {
               tempRe[j][i] = re[i];
               tempIm[j][i] = im[i];
           }
       }

      for (int i = 0; i < xSize; i++) {
           double[] re = new double[ySize];
           double[] im = new double[ySize];
           ifft(
                   getArray(tempRe, xSize, i),
                   getArray(tempIm, xSize, i),
                   re, im, ybit);

          for (int j = 0; j < ySize; j++) {
               outDataRe[i][j] = re[j];
               outDataIm[i][j] = im[j];
           }
       }
   }

  /// <summary>
   /// 2次元配列で周波数帯のパワースペクトルを求める
   /// </summary>
   public static double[][] powerSpectorl2D(
           double[][] inDataRe,
           double[][] inDataIm,
           int xSize,
           int ySize) {

      double[][] data = new double[xSize][ySize];
       for (int i = 0; i < xSize; i++) {
           for (int j = 0; j < ySize; j++) {
               data[i][j] = Math.log10(
                       Math.pow(inDataRe[i][j], 2) +
                               Math.pow(inDataIm[i][j], 2));
           }
       }
       return data;
   }
}


呼び出し用にBitmapUtilに以下のメソッドを追加してある。
public static void processingFFT(final Bitmap bitmap, final OnProcessingBitmapListener listener) {
      new AsyncTask<Bitmap, Integer, Bitmap>() {
           @Override
           protected Bitmap doInBackground(Bitmap... params) {
               Bitmap mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
               // ImageUtil.resize(mutableBitmap, 0.5f);
               // ピクセルデータ分ループ
               final int width = mutableBitmap.getWidth();
               final int height = mutableBitmap.getHeight();
               int[] pixels = new int[width * height];
               mutableBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
               double[][] inRe = convertDoubleMatrix(pixels, width, height);
               double[][] inIm = new double[width][height];
               double[][] re = new double[width][height]; // 実数
               double[][] im = new double[width][height]; // 虚数
               // 2次元フーリエ変換
               FFTUtil.fft2D(inRe, inIm, re, im, width, height);
               // 実数と虚数のパワースペクトルを計算
               double[][] power = FFTUtil.powerSpectorl2D(re, im, width, height);
               pixels = convertIntArray(power, width, height);
               mutableBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
               return mutableBitmap;
           }

          @Override
           protected void onPostExecute(Bitmap bitmap) {
               super.onPostExecute(bitmap);
               if (listener != null) {
                   if (bitmap != null) {
                       listener.onSuccessProcessing(bitmap);
                   } else {
                       listener.onFailedProcessing();
                   }
               }
           }
       }.execute(bitmap);
   }

  private static double[][] convertDoubleMatrix(int[] pixel, int width, int height) {
       double[][] convertedImage = new double[width][height];
       for (int y = 0; y < height; y++) {
           for (int x = 0; x < width; x++) {
               convertedImage[x][y] = (double) pixel[x + width * y];
           }
       }
       return convertedImage;
   }

  private static int[] convertIntArray(double[][] data, int width, int height) {
       int[] pixels = new int[width * height];
       for (int y = 0; y < height; y++) {
           for (int x = 0; x < width; x++) {
               // Log.d("ConverInt", "data[x][y]:" + data[x][y]);
               pixels[x + width * y] = (int) data[x][y];
           }
       }
       return pixels;
   }

考察

今回は画像の端の部分をモザイク処理しきれなかった。これはモザイク処理をするドットをドット数分、右側と下側に対して取得しているため、画像の右と下に、画像の幅と高さで割ったあまり分のドットが処理をできなかったからだ。
そのため、解決策としては現在計算に用いている値を複製することで同じ様な結果が得られると考えられる。

また、FFTに関しては一度グレースケールに変換しなかったため、ノイズが多くうまく処理できなかったと考えられる。
それとは別に、スマートフォンのプロセッサでのFFTは非常に処理が重く、画像を送信するときなどにFFTをする場合でも、通信速度と処理速度を考えると今回の方法ではFFTの効果は薄いと感じた。

  • 最終更新:2015-07-13 16:48:45

このWIKIを編集するにはパスワード入力が必要です

認証パスワード