Sunday, March 5, 2017

มาลอง Thresholding กัน

มาลองประมวลผลภาพอย่างง่ายต่อด้วยการทำ Thresholding ซึ่งก็คือการกำหนดค่า Threshold (มีทั้งแบบกำหนดเอง หรือได้จากการคำนวณโดยอัตโนมัติ) ที่มีค่าระหว่างช่วงค่าของพิกเซล 0-255 แล้วนำค่านี้ไปตัดสินใจสร้างรูปผลลัพธ์อีกที

ปกติแล้ว Thresholding มักจะใช้ในงาน segmentation คือแยกส่วนของภาพสีเทา ซึ่งมักจะแยกออกเป็นสองส่วนคือส่วนที่เราสนใจ (foreground) กับส่วนที่เราไม่สนใจ (background) ดังนั้นกระบวนการนี้จึงอาจถูกเรียกอีกชื่อว่า Binarization ก็ได้

ลองมาดูตัวอย่างผลลัพธ์ของการทำ Thresholding 5 แบบกันก่อนครับ
 


OpenCV มีคำสั่งในการทำ Thresholding คือ
public static double threshold(Mat src, Mat dst, double thresh, double maxval, int type)

-src และ dst คือ source และ destination matrix นั่นเอง
-thresh คือค่า threshold ที่เราเอาไว้แยกภาพ
-maxval คือค่าสูงสุดที่เราทำ thresholding แล้วอยากให้เป็น ปกติก็มักจะกำหนดให้เป็นค่าสูงสุดของภาพ เช่น 255
-type คือ วิธีการ thresholding มีด้วยกัน 7 แบบ ในที่นี้เราจะลอง 5 แบบ ได้แก่
(คัดลอกจาก http://docs.opencv.org/java/2.4.9/org/opencv/imgproc/Imgproc.html#threshold(org.opencv.core.Mat,%20org.opencv.core.Mat,%20double,%20double,%20int)

THRESH_BINARY
dst(x,y) = maxval if src(x,y) > thresh; 0 otherwise

THRESH_BINARY_INV
dst(x,y) = 0 if src(x,y) > thresh; maxval otherwise

THRESH_TRUNC
dst(x,y) = threshold if src(x,y) > thresh; src(x,y) otherwise

THRESH_TOZERO
dst(x,y) = src(x,y) if src(x,y) > thresh; 0 otherwise

THRESH_TOZERO_INV
dst(x,y) = 0 if src(x,y) > thresh; src(x,y) otherwise

ลองมาดูโค้ดกัน

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_alignParentBottom="true"
        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"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rBinary"
            android:layout_weight="1" />

        <RadioButton
            android:text="Binary, Inverted"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rBinaryInverted"
            android:layout_weight="1" />

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

        <RadioButton
            android:text="To zero"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rToZero"
            android:layout_weight="1" />

        <RadioButton
            android:text="To zero, Inverted"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rToZeroInverted"
            android:layout_weight="1" />
    </RadioGroup>

    <TextView
        android:text="Threshold"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tvThreshold"
        android:textSize="18sp"
        android:layout_below="@+id/ivImage"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="12dp" />

    <SeekBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/sbThreshold"
        android:layout_alignBottom="@+id/tvThreshold"
        android:layout_toRightOf="@+id/tvThreshold"
        android:layout_alignRight="@+id/ivImage"
        android:layout_alignEnd="@+id/ivImage"
        android:max="255"
        android:progress="127" />

</RelativeLayout>

java

package com.example.mobile.opencv102;

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 android.widget.SeekBar;

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, SeekBar.OnSeekBarChangeListener{
    private Bitmap bitmap, result;
    private ImageView ivImage;
    private RadioGroup rgroup;
    private SeekBar sbThreshold;
    private Mat mat1, mat2;
    private int threshold = 127;
    private int type = -1;

    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);
        sbThreshold = (SeekBar) findViewById(R.id.sbThreshold);
        sbThreshold.setOnSeekBarChangeListener(this);

        //decode resource file to bitmap with no scale
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lenna_gray);

        //source matrix, unsigned 8-bit 4 channels (RGBA)
        mat1 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC4);
        //convert bitmap to matrix
        Utils.bitmapToMat(bitmap, mat1);

        //output matrix, grayscale 8-bit 4 channels
        mat2 = new Mat(bitmap.getWidth(), bitmap.getHeight(), CvType.CV_8UC4);
        //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);
    }

    public void adjust(double thres, int type) {
        //thresholding
        //public static double threshold(Mat src, Mat dst, double thresh, double maxval, int type)
        Imgproc.threshold(mat1, mat2, thres, 255, type);

        //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.rOriginal) {
            ivImage.setImageResource(R.drawable.lenna_gray);
            type = -1;
        }
        else if(id==R.id.rBinary) {
            type = Imgproc.THRESH_BINARY;
            adjust(threshold, type);
        }
        else if(id==R.id.rBinaryInverted) {
            type = Imgproc.THRESH_BINARY_INV;
            adjust(threshold, type);
        }
        else if(id==R.id.rTruncate) {
            type = Imgproc.THRESH_TRUNC;
            adjust(threshold, type);
        }
        else if(id==R.id.rToZero) {
            type = Imgproc.THRESH_TOZERO;
            adjust(threshold, type);
        }
        else if(id==R.id.rToZeroInverted) {
            type = Imgproc.THRESH_TOZERO_INV;
            adjust(threshold, type);
        }
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        threshold = progress;
        if(type!=-1) {
            adjust(threshold, type);
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }
}

อ้างอิง
  • http://docs.opencv.org/3.2.0/db/d8e/tutorial_threshold.html
  • http://docs.opencv.org/java/2.4.9/org/opencv/imgproc/Imgproc.html#threshold(org.opencv.core.Mat,%20org.opencv.core.Mat,%20double,%20double,%20int)

No comments:

Post a Comment