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;
}
}