Tuesday, March 7, 2017

Thresholding โดยการหาค่า Threshold แบบอัตโนมัติ

ปกติเราจะต้องกำหนดค่า Threshold เพื่อทำ Thresholding ซึ่งตามความเป็นจริงถือว่ายากพอสมควร เพราะไม่รู้ว่าควรจะกำหนดค่านี้เป็นเท่าไหร่ดี เลยมีนักวิจัยคิดวิธีการที่จะคำนวณหาค่านี้โดยอัตโนมัติ และ OpenCV ได้นำมาใช้ด้วยกัน 2 วิธี คือ วิธีของ Otsu และ วิธี Triangle ลองไปอ่านหลักการกันได้ตามนี้ครับ
  • https://en.wikipedia.org/wiki/Otsu's_method
  • https://www.ncbi.nlm.nih.gov/pubmed/70454
ลองดูผลลัพธ์กันก่อน


ถ้าจะใช้งานสองวิธีนี้ใน OpenCV ก็ต้องเตรียม matrix ของรูปต้นฉบับให้เป็นแบบ CV_8UC1 หรือแบบ 8-bit 1 channel เสียก่อน (ใน reference บอกไว้) ดังนั้น เมื่อแปลง bitmap ให้เป็น Mat ด้วยคำสั่ง
Utils.bitmapToMat(bitmap, mat);

เมตริกซ์ mat ที่ได้จะเป็นแบบ CV_8UC4 โดยอัตโนมัติ เราจึงจำเป็นต้องแปลงให้เป็นแบบที่เราต้องการผ่านคำสั่ง
Imgproc.cvtColor(mat, mat1, Imgproc.COLOR_RGB2GRAY, 1);
จากนั้นค่อยมากำหนดการทำ thresholding โดยทั้งสองวิธีข้างต้นจะใช้ควบคู่กับการทำ thresholding 5 รูปแบบก่อนหน้านี้ เช่น ถ้าเราต้องการทำ binary thresholding โดยใช้ Otsu's method เพื่อกำหนดค่า threshold โดยอัตโนมัติก็จะใช้คำสั่ง
Imgproc.threshold(mat1, mat2, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
ให้สังเกตการกำหนดค่าพารามิเตอร์จะพบว่า ค่า threshold เรากำหนดให้เป็น 0 เพราะว่าเดี๋ยววิธีของ Otsu จะคำนวณให้เอง ส่วนวิธีการทำ thresholding ก็จะเป็นสองแบบรวมกัน เลยใช้รูปแบบ Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU ครับ

โค้ดทั้งหมดก็จะประมาณนี้
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:id="@+id/ivImage"
        app:srcCompat="@drawable/lenna_gray"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_weight="1" />

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

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

        <RadioButton
            android:text="Binary Otsu"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rBinaryOtsu"
            android:layout_weight="1" />

        <RadioButton
            android:text="Binary Triangle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rBinaryTriangle"
            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;
import org.opencv.imgproc.Imgproc;

public class MainActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener{
    private Bitmap bitmap, result;
    private ImageView ivImage;
    private RadioGroup rgroup;
    private Mat mat1, mat2;

    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.lenna_gray);


        //temporary matrix to get pixel values from bitmap
        Mat temp = new Mat();
        //convert bitmap to matrix, this matrix will always be of type CV_8UC4
        Utils.bitmapToMat(bitmap, temp);

        //source matrix, for Otsu and Triangle thresholding must be unsigned 8-bit 1 channel (gray)
        mat1 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC1);
        //convert temp matrix to mat1 of type CV_8U1, 1 channel
        Imgproc.cvtColor(temp, mat1, Imgproc.COLOR_RGB2GRAY, 1);

        //output matrix, grayscale 8-bit 1 channel
        mat2 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC1);

        //create bitmap having width (columns) and height(rows) as matrix and in the format of 8 bit/pixel
        result = Bitmap.createBitmap(mat2.cols(), mat2.rows(), Bitmap.Config.RGB_565);
    }

    @Override
    public void onCheckedChanged(RadioGroup radioGroup, int id) {
        if(id==R.id.rOriginal) {
            ivImage.setImageResource(R.drawable.lenna_gray);
            return;
        }
        else if(id==R.id.rBinaryOtsu) {
            Imgproc.threshold(mat1, mat2, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
        }
        else if(id==R.id.rBinaryTriangle) {
            Imgproc.threshold(mat1, mat2, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_TRIANGLE);
        }
        //convert result matrix to bitmap
        Utils.matToBitmap(mat2, result);
        //show bitmap in ImageView
        ivImage.setImageBitmap(result);
    }
}

1 comment:

  1. Hi, I am very interested in your work. Are you available for freelance work? If so, please email me at austin.syn.kim@gmail.com to discuss things further. We can communicate in Thai, if needed.

    Thank you,
    Austin

    ReplyDelete