Friday, March 3, 2017

Matrix Scaling กับ Point operation

Pixel processing อาจแบ่งได้ออกเป็น 2 แบบหลักๆ คือ ประมวลผลแต่ละพิกเซลของใครของมัน (ขอใช้ภาษาที่เข้าใจง่ายๆ :p ) หรือ point operation กับ การประมวลผลที่ต้องใช้พิกเซลข้างๆด้วย (Neighborhood operation)

ในที่นี้จะพูดถึง point operation ก่อน ซึ่งเราได้เจอมาแล้วในหัวข้อก่อนๆ เช่น การเปลี่ยนรูปสีให้เป็นรูปสีเทา หรือ การทำ image negative

หากเราต้องการประมวลผลแต่ละพิกเซล ในแต่ละ channel RGBA ส่วนใหญ่การประมวลผลก็จะอยู่ในรูปแบบ
output = alpha*input + beta

เมื่อ output คือเมตริกซ์ผลลัพธ์ และ input คือ เมตริกซ์ตั้งต้น ส่วน alpha และ beta ก็จะเป็นตัวคูณและตัวบวกเพิ่ม เราอาจเรียกการคำนวณนี้ว่า Matrix Scaling

เมื่อค่า alpha และ beta มีค่าต่างๆกัน จะให้ผลลัพธ์ดังตัวอย่างต่อไปนี้
  • alpha = 1 และ beta != 0 จะเป็นการเพิ่มหรือลดความสว่าง (brightness) ของรูป
  • alpha >0 และ beta = 0 จะเป็นการเพิ่มหรือลดค่า contrast ของรูป 
  • alpha = -1 และ beta = 255 เป็นการทำ image negative
ใน OpenCV เราสามารถใช้คำสั่ง
input.converTo(Mat output, int type, double alpha, double beta);

โดยที่ type ก็จะเป็นชนิดของเมตริกซ์ผลลัพธ์ที่ต้องการ เช่น CvType.CV_8UC4 เป็นต้น หรือกำหนดให้เป็นค่าติดลบเช่น -1 ก็ได้ ถ้าอยากให้เมตริกซ์ผลลัพธ์เป็นชนิดเดียวกับเมตริกซ์ตั้งต้น

ข้อดีของคำสั่งนี้ใน OpenCV คือมันจะปรับค่าของพิกเซลให้อยู่ในช่วง 0-255 โดยอัตโนมัติ

เราจะมาลองใช้คำสั่งนี้กันอีกครั้ง เพื่อปรับค่า brightness และ contrast ของรูป ดังนี้



โค้ดก็ประมาณนี้ครับ
xml

<?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" />

    <RadioGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/ivImage"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="25dp"
        android:orientation="horizontal"
        android:id="@+id/rgroup">

        <RadioButton
            android:text="Origin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rbtOrigin"
            android:layout_weight="1"
            android:checked="true" />

        <RadioButton
            android:text="Brightness"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rbtBright"
            android:layout_weight="1" />

        <RadioButton
            android:text="Contrast"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rbtContrast"
            android:layout_weight="1" />

    </RadioGroup>

</RelativeLayout>

java

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

import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.CvType;
import org.opencv.core.Mat;

public class MainActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener{
    private Bitmap bitmap;
    private ImageView ivImage;
    private RadioGroup rgroup;

    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);
        rgroup = (RadioGroup) findViewById(R.id.rgroup);
        rgroup.setOnCheckedChangeListener(this);
        //decode resource file to bitmap with no scale
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lenna256);
    }

    public void adjust(double alpha, double beta) {
        //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 1 channel
        Mat mat2 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC3);
        //Method 1: use built-in class, fastest
        //increase brightness or contrast or both
        //out = alpha*(in) + beta
        mat1.convertTo(mat2, CvType.CV_8UC3, alpha, beta);

        //Method 2: single loop
        //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;
//        double alpha=1, beta=100;
//        //source RGBA, move every four bytes
//        for(int i=0;i<source.length;i+=4) {
//            //change brightness and contrast
//            dest[j] = alpha*source[i] + beta;
//            dest[j+1] = alpha*source[i+1] + beta;
//            dest[j+2] = alpha*source[i+2] + beta;
//            //dest RGB, mover every three bytes
//            j+=3;
//        }
//        //put the modified temp array to matrix, this step will clamp the pixel to <= 255
//        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);
    }

    @Override
    public void onCheckedChanged(RadioGroup radioGroup, int id) {
        if(id==R.id.rbtOrigin) {
            ivImage.setImageResource(R.drawable.lenna256);
        }
        else if(id==R.id.rbtBright) {
            adjust(1,100);
        }
        else if(id==R.id.rbtContrast) {
            adjust(2,0);
        }
    }
}

No comments:

Post a Comment