Draw Image Histogram in Android With OpenCV

In this tutorial, we are going to learn how to draw a histogram of an image using android OpenCV library.

I will not mention how to install and setup OpenCV in android. If you don’t know how to add OpenCV in your android project I will suggest you read my previous tutorial on How to install and use OpenCV in Android before you continue.

I started recently to explore OpenCV and what it can be used for in android application development.

As I am learning I have decided to share my knowledge with our blog reader. I am not an expert on this topic so if you see something that needs improving on I will be glad to read your feedback and suggestion.

What is an Image Histogram

Welcome to the world of digital image processing, In digital image processing, image is analyzed in such a way that its individual pixel can be manipulated to enhance the output image or to extract useful data from the image. It is a branch of digital signal processing.

So when we talk about image histogram we refer to the graphical representation of the pixel intensity in a digital image.

The graph is usually represented on the x-axis by a bin width which is a range of pixel intensity values while the y-axis is represented by frequency of occurrence in each bin range.

Image histogram is use for image editing and histogram equalization.

OpenCV CalcHist Method

OpenCV is one of the best library in the area of Computer Vision. OpenCV is rich with different algorithms use for image processing.

For calculating an image histogram, OpenCV has an in-built method called calcHist()which we will use to calculate histogram.

OpenCV Tutorial Context

In the tutorial we are going to get an image from our device gallery and click on a button to extract the image histogram.

The device gallery is open with an Intent and the image path is received in onActivityResult()callback method.

APP SCREENSHOTS

1. CREATE A NEW ANDROID PROJECT

  • Open Android Studio
  • Go to file menu
  • Select  new
  • Enter project name
  • Enter activity name
  • Keep other default settings
  • Click on finish button to create a new android project

2. Update colors.xml

Open the res folder > colors.xml file and add the color code below.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
    <color name="colorWhite">#ffffff</color>
    <color name="colorBlack">#000000</color>
</resources>

3. Open activity_histogram.xml

We are going to add some View widgets in our activity layout file. Open the layout file and paste the code below.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".histogram.HistogramActivity">

    <Button
        android:id="@+id/open_gallery"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/colorWhite"
        android:text="@string/select_gallery"
        android:background="@color/colorAccent"
        android:contentDescription="@string/app_name"/>
    
    <ImageView
        android:id="@+id/source_image"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_marginTop="20dp"
        android:layout_gravity="center"
        android:contentDescription="@string/app_name"/>
    
    <TextView
        android:id="@+id/source_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/source_image"
        android:textSize="12sp"
        android:visibility="gone"
        android:layout_marginTop="8dp"
        android:layout_gravity="center"/>
    
    <Button
        android:id="@+id/show_histogram"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="12dp"
        android:background="@color/colorAccent"
        android:text="@string/show_histogram"
        android:padding="12dp"
        android:textColor="@color/colorWhite"
        android:contentDescription="@string/app_name"/>
    
    <ImageView
        android:id="@+id/show_graph"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_marginTop="20dp"
        android:layout_gravity="center"
        android:contentDescription="@string/app_name"/>
    
</LinearLayout>

The above layout is simple and easy to understand so I will not explain it. Lets open the HistogramActivityclass and add some code snippet.

Open HistogramActivity class

In our HistogramActivity class, we will get the instances of our layout views using activity class findViewById()method.

When a user click on the open device gallery button, the user will need to select an image from the device.

The selected image is displayed on the top ImageView widget.

To calculate and display the selected image histogram, click on the show histogram button and the histogram will be displayed on the bottom ImageView widget.

Copy and paste the code below in the HistogramActivity class.

public class HistogramActivity extends AppCompatActivity {

    private static final String TAG = HistogramActivity.class.getSimpleName();

    private ImageView srcImage, showGraph;

    private TextView srcText;

    private static final int REQUEST_GET_SINGLE_FILE = 202;

    private Bitmap bitmap;

    private boolean isImageHistogram = false;

    private BaseLoaderCallback baseLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                    break;
                default:
                    super.onManagerConnected(status);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_histogram);

        ActionBar actionBar = getSupportActionBar();
        if(actionBar != null){
            setTitle("Image Histogram");
        }

        srcImage = (ImageView)findViewById(R.id.source_image);
        showGraph = (ImageView)findViewById(R.id.show_graph);

        srcText = (TextView)findViewById(R.id.source_text);

        Button openGalleryBtn = (Button)findViewById(R.id.open_gallery);
        openGalleryBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
                intent.addCategory(Intent.CATEGORY_OPENABLE);
                intent.setType("image/*");
                startActivityForResult(Intent.createChooser(intent, "Select Picture"),REQUEST_GET_SINGLE_FILE);

            }
        });

        Button showHistogramButton = (Button)findViewById(R.id.show_histogram);
        showHistogramButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(bitmap == null){
                    Toast.makeText(HistogramActivity.this, "You must upload image from gallery to show its histogram", Toast.LENGTH_SHORT).show();
                }else{
                    if(!isImageHistogram){
                        //add histogram code
                        Mat sourceMat = new Mat();
                        Utils.bitmapToMat(bitmap, sourceMat);

                        Size sourceSize = sourceMat.size();

                        int histogramSize = 256;
                        MatOfInt hisSize = new MatOfInt(histogramSize);

                        Mat destinationMat = new Mat();
                        List<Mat> channels = new ArrayList<>();

                        MatOfFloat range = new MatOfFloat(0f, 255f);
                        MatOfFloat histRange = new MatOfFloat(range);

                        Core.split(sourceMat, channels);

                        MatOfInt[] allChannel = new MatOfInt[]{new MatOfInt(0), new MatOfInt(1), new MatOfInt(2)};
                        Scalar[] colorScalar = new Scalar[]{new Scalar(220, 0, 0, 255), new Scalar(0, 220, 0, 255), new Scalar(0, 0, 220, 255)};

                        Mat matB = new Mat(sourceSize, sourceMat.type());
                        Mat matG = new Mat(sourceSize, sourceMat.type());
                        Mat matR = new Mat(sourceSize, sourceMat.type());

                        Imgproc.calcHist(channels, allChannel[0], new Mat(), matB, hisSize, histRange);
                        Imgproc.calcHist(channels, allChannel[1], new Mat(), matG, hisSize, histRange);
                        Imgproc.calcHist(channels, allChannel[2], new Mat(), matR, hisSize, histRange);


                        int graphHeight = 300;
                        int graphWidth = 200;
                        int binWidth = 3;

                        Mat graphMat = new Mat(graphHeight, graphWidth, CvType.CV_8UC3, new Scalar(0, 0, 0));

                        //Normalize channel
                        Core.normalize(matB, matB, graphMat.height(), 0, Core.NORM_INF);
                        Core.normalize(matG, matG, graphMat.height(), 0, Core.NORM_INF);
                        Core.normalize(matR, matR, graphMat.height(), 0, Core.NORM_INF);

                        //convert pixel value to point and draw line with points
                        for(int i = 0; i < histogramSize; i++){
                            Point bPoint1 = new Point(binWidth * (i - 1), graphHeight - Math.round(matB.get(i - 1, 0)[0]));
                            Point bPoint2 = new Point(binWidth * i, graphHeight - Math.round(matB.get(i, 0)[0]));
                            Core.line(graphMat, bPoint1, bPoint2, new Scalar(220, 0, 0, 255), 3, 8, 0);

                            Point gPoint1 = new Point(binWidth * (i - 1), graphHeight - Math.round(matG.get(i - 1, 0)[0]));
                            Point gPoint2 = new Point(binWidth * i, graphHeight - Math.round(matG.get(i, 0)[0]));
                            Core.line(graphMat, gPoint1, gPoint2, new Scalar(0, 220, 0, 255), 3, 8, 0);

                            Point rPoint1 = new Point(binWidth * (i - 1), graphHeight - Math.round(matR.get(i - 1, 0)[0]));
                            Point rPoint2 = new Point(binWidth * i, graphHeight - Math.round(matR.get(i, 0)[0]));
                            Core.line(graphMat, rPoint1, rPoint2, new Scalar(0, 0, 220, 255), 3, 8, 0);
                        }

                        //convert Mat to bitmap
                        Bitmap graphBitmap = Bitmap.createBitmap(graphMat.cols(), graphMat.rows(), Bitmap.Config.ARGB_8888);
                        Utils.matToBitmap(graphMat, graphBitmap);

                        // show histogram 
                        showGraph.setImageBitmap(graphBitmap);
                        //set the isImageHistogram
                        isImageHistogram = true;
                    }
                }
            }
        });
    }

    @Override
    public void onResume(){
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_11, this, baseLoaderCallback);
        } else {
            Log.d(TAG, "OpenCV library found inside package. Using it!");
            baseLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        try {
            if (resultCode == RESULT_OK) {
                if (requestCode == REQUEST_GET_SINGLE_FILE) {
                    Uri selectedImageUri = Objects.requireNonNull(data).getData();
                    // Get the path from the Uri
                    final String path = getPathFromURI(selectedImageUri);
                    if (path != null) {
                        File f = new File(path);
                        selectedImageUri = Uri.fromFile(f);
                    }
                    // Set the image in ImageView
                    bitmap = ImageUtils.resizeBitmap(Objects.requireNonNull(selectedImageUri).getEncodedPath(), 300, 200);
                    if(bitmap != null){
                        srcImage.setImageBitmap(bitmap);
                        srcText.setVisibility(View.VISIBLE);
                    }
                    Log.d(TAG, "Lod image path " + selectedImageUri + " - " + selectedImageUri.getEncodedPath());
                }
            }
        } catch (Exception e) {
            Log.e("FileSelectorActivity", "File select error", e);
        }
    }

    public String getPathFromURI(Uri contentUri) {
        String res = null;
        String[] proj = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
        if (cursor.moveToFirst()) {
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            res = cursor.getString(column_index);
        }
        cursor.close();
        return res;
    }
}

The code for drawing histogram is well commented but if you have any question kindly use the comment box below to ask your questions.

You can download the source code on our Github page.

This bring us to the end of this tutorial on drawing image histogram in android with OpenCV. We will continue to explore other OpenCV applications in android.

If you have any particular topic you will like us to cover kindly use the comment box and notify us.

 

 

6 Comments

  1. Avatar
    • Inducesmile
      • Avatar
  2. Avatar
    • Inducesmile

Add a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.