Thursday, March 2, 2017

เล่นกับ Pixel ต่อ การกลับสีรูป (Image negative / inverse)

ลองมาเล่นกับ Pixel อีก โดยการกลับสีรูป ซึ่งหลักการก็ค่อนข้างง่าย คือ พิกเซลมีค่าเป็นเท่าไหร่ ก็เปลี่ยนเป็นค่าตรงกันข้าม เช่น จาก 0 ก็เปลี่ยนเป็น 255 หรือจาก 255 ก็เป็น 0

พูดง่ายๆก็คือ การกลับสีรูปคือการคำนวณ 255-pixel นั่นเอง



ลองดูขั้นตอนกันครับ

สมมติว่าเราจะใช้ interface ง่ายๆเหมือนตัวอย่างที่แล้ว

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.mobile.opencv102.MainActivity">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/ivImage"
        app:srcCompat="@drawable/lenna256" />

    <ToggleButton
        android:text="ToggleButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:id="@+id/tbProcess"
        android:layout_below="@+id/ivImage"
        android:layout_centerHorizontal="true"
        android:onClick="process" />

</RelativeLayout>

ในส่วนของโค้ด ก็จะมีขั้นตอนหลักๆคล้ายตัวอย่างที่แล้ว ผมจะขอสรุปย่อๆดังนี้
1. สร้าง matrix สำหรับรูปต้นฉบับและรูปผลลัพธ์ mat1 และ mat2
Mat mat1 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC4);
Mat mat2 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC3);

สังเกตว่าในที่นี้ matrix ผลลัพธ์เป็นชนิด CV_8UC3 เพราะเราจะไม่ใช้ alpha channel

2. กลับสีรูป ทำได้อย่างน้อยสามวิธีเช่น
2.1 ใช้คำสั่งสำเร็จรูป
จะใช้
Core.bitwise_not(mat1, mat2);
หรือ
mat1.convertTo(mat2, CvType.CV_8UC3, -1, 255);
ก็ได้ มีความหมายเหมือนเอา 255 ไปลบทุก pixel

2.2 วนลูปแต่และแถวแต่ละคอลัมน์

int row = mat2.rows();
int col = mat2.cols();
for(int r=0;r<row;r++) {
 for(int c=0;c<col;c++) {
  //read pixel
  double[] pixelSet = mat1.get(r, c);
  //find average of RGB
  double[] pixel = new double[3];
  pixel[0] = 255-pixelSet[0];
  pixel[1] = 255-pixelSet[1];
  pixel[2] = 255-pixelSet[2];
  //put average value of pixel to matrix
  mat2.put(r, c, pixel);
 }
}

2.3 dump matrix มาใส่ array

mat1.convertTo(mat1, CvType.CV_64FC4);
//create temp arrays to keep pixels for both original and output
double[] source = new double[(int)(mat1.total()*mat1.channels())];
double[] dest = new double[(int)(mat2.total()*mat2.channels())];

//dump matrix to array
mat1.get(0, 0, source);
int j=0;
//source RGBA, move every four bytes
for(int i=0;i<source.length;i+=4) {
 //negative
 dest[j] = 255-source[i];
 dest[j+1] = 255-source[i+1];
 dest[j+2] = 255-source[i+2];
 //dest RGB, mover every three bytes
 j+=3;
}
//put the modified temp array to matrix
mat2.put(0, 0, dest);

โค้ดรวมๆก็เป็นแบบนี้ครับ

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ToggleButton;

import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;

public class MainActivity extends AppCompatActivity {
    private Bitmap bitmap;
    private ToggleButton tbProcess;
    private ImageView ivImage;

    static {
        if(OpenCVLoader.initDebug()) {
            Log.i("OpenCV", "Success");
        }
        else {
            Log.i("OpenCV", "Fail");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ivImage = (ImageView) findViewById(R.id.ivImage);
        tbProcess = (ToggleButton) findViewById(R.id.tbProcess);

        //decode resource file to bitmap with no scale
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lenna256);
        //if you don't want Android to auto resize the bitmap according to device resolution
//        BitmapFactory.Options option = new BitmapFactory.Options();
//        option.inScaled = false;
//        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lenna256, option);
    }

    public void process(View view) {
        if(!tbProcess.isChecked()) {
            ivImage.setImageResource(R.drawable.lenna256);
            return;
        }
        //source matrix, unsigned 8-bit 4 channels (RGBA)
        Mat mat1 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC4);
        //convert bitmap to matrix
        //Be careful that the output Mat have the same size as the input Bitmap
        //and of the 'CV_8UC4' type, RGBA format.
        Utils.bitmapToMat(bitmap, mat1);

        //output matrix, grayscale 8-bit 3 channels
        Mat mat2 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC3);
        //negative or inverse (255-pixel)
        //Method 1: use built-in class, fastest
//        Core.bitwise_not(mat1, mat2);
        //out = a(in) + b = -(in) + 255 = 255 - in
//        mat1.convertTo(mat2, CvType.CV_8UC3, -1, 255);

        //Method 2: 2D array, simple but slow
//        int row = mat2.rows();
//        int col = mat2.cols();
//        for(int r=0;r<row;r++) {
//            for(int c=0;c<col;c++) {
//                //read pixel
//                double[] pixelSet = mat1.get(r, c);
//                //find average of RGB
//                double[] pixel = new double[3];
//                pixel[0] = 255-pixelSet[0];
//                pixel[1] = 255-pixelSet[1];
//                pixel[2] = 255-pixelSet[2];
//                //put average value of pixel to matrix
//                mat2.put(r, c, pixel);
//            }
//        }

        //Method 3: single loop, more complex but faster
        //it requires converting mat data type from byte to double
        //otherwise the mat.get() gives the wrong pixel value
        mat1.convertTo(mat1, CvType.CV_64FC4);
        //create temp arrays to keep pixels for both original and output
        double[] source = new double[(int)(mat1.total()*mat1.channels())];
        double[] dest = new double[(int)(mat2.total()*mat2.channels())];

        //dump matrix to array
        mat1.get(0, 0, source);
        int j=0;
        //source RGBA, move every four bytes
        for(int i=0;i<source.length;i+=4) {
            //negative
            dest[j] = 255-source[i];
            dest[j+1] = 255-source[i+1];
            dest[j+2] = 255-source[i+2];
            //dest RGB, mover every three bytes
            j+=3;
        }
        //put the modified temp array to matrix
        mat2.put(0, 0, dest);

        //create bitmap having width (columns) and height(rows) as matrix and in the format of 8 bit/pixel
        //ARGB_8888 and RGB565 are normal bitmap formats
        Bitmap result = Bitmap.createBitmap(mat2.cols(), mat2.rows(), Bitmap.Config.RGB_565);
        //convert result matrix to bitmap
        Utils.matToBitmap(mat2, result);
        //show bitmap in ImageView
        ivImage.setImageBitmap(result);
    }
}

No comments:

Post a Comment