This practical work illustrate the thresholding and filtereing methods provided by OpenCV that are accessible using JavaCV. Thresholding images enables to separate object of interest from image background. On the other hand, filtering image enables to remove noise or to sharpen details.
Project skeleton can be downloaded at http://www.seinturier.fr/teaching/computer_vision/javacv/javacv-thresh-filter.zip. The project contains the sources skeleton and the needed data.
1. Thresholding images
Thresholding an image is a simple way to segmentation (separating various objects that are visible on an image). This separation is based on the variation of intensity between the object pixels and the background pixels. To differentiate the pixels of interest interested in from the rest, it is possible to perform a comparison of each pixel intensity value with respect to a threshold (determined according to the problem to solve).
Once the important pixels are separated properly, a determined value can be used to identify them, for example white pixels (255) mark interesting objects as black pixels (0) mark the objects that are not interesting.
Within OpenCV, all thresholding methods apply on gray scaled images.
1.1. Basic Thresholding
Basic (or global) thresholding methods consist in producing an image in which the intensity (i-e the value) of each pixel is set to a specific value according to a threshold value. Several basic thresholding methods are available in OpenCV.
As basic thresholding relies on pixels intensity, for the rest of this document, $p(x,\ y)$ denotes the intensity (i-e the value) of a pixel located at $(x,\ y)$ within an image.
1.1.1. Binary thresholding
Binary thresholding enables to produce an image that contains only the two differents values $\{0, maxval\}$ according to a threshold intensity, denoted $tresh$. If the intensity of a pixel is higher than $tresh$, the new pixel intensity is set to $maxval$, otherwise, its new intensity is set to $0$. Binary thresholding can be represented by the function $t_{b}$ defined such as:
$t_{b}(p(x,\ y)) = \begin{cases} maxval & \text{if } p(x,\ y) > tresh \\ 0 & \text{otherwise} \end{cases}$
OpenCV enables to apply binary tresholding on a image using the function threshold. This functions is available within JavaCV using:
threshold(Mat src, Mat dst, double tresh, double maxval, CV_THRESH_BINARY);
Exercise 1.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Threshold.java and append the method:
/**
* Compute the binary threshold for the <code>src</code> image.
* @param src the input image (grayscaledd)
* @param tresh the threshold value
* @param maxval the max value (for acceptec pixels)
* @return a Mat that represents the thresholded image.
*/
public static Mat threshBinary(Mat src, double tresh, double maxval) {
if (src != null) {
Mat dst = new Mat(src.rows(), src.cols(), CV_8UC1);
threshold(src, dst, tresh, maxval, CV_THRESH_BINARY);
return dst;
} else {
return new Mat();
}
}
The use of the type Mat, of the functions imread, imwrite, threshold and of the values IMREAD_GRAYSCALE, CV_8UC1 and CV_THRESH_BINARY need the following imports:
import static org.bytedeco.opencv.global.opencv_core.CV_8UC1;
import static org.bytedeco.opencv.global.opencv_imgcodecs.IMREAD_GRAYSCALE;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imread;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite;
import static org.bytedeco.opencv.global.opencv_imgproc.CV_THRESH_BINARY;
import static org.bytedeco.opencv.global.opencv_imgproc.threshold;
import org.bytedeco.opencv.opencv_core.Mat;
Within the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the method:
/**
* The exercise 1 code.
*/
public static void ex1() {
String file = "data/images/thresh_original.bmp";
// Load an image and converting it in grayscale
Mat image = imread(file, IMREAD_GRAYSCALE);
// Apply binary thresholding with tresh = 172 and maxval = 255
Mat m = threshBinary(image, 172, 255);
// Write the thresholded image as ex1_treshold_binary.png file
String output = "output/ex1_treshold_binary.png";
if (imwrite(output, m)) {
System.out.println("wrote tresholded image to "+output+".");
} else {
System.out.println("Cannot save thresholded image.");
}
}
Finally, still in the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the main method such as:
/**
* The main method.
* @param args the main method arguments
*/
public static void main(String[] args) {
System.out.println("Exercise 1: ");
ex1();
System.out.println("");
}
Run the program and check the resulting thresholded image (the file ex1_treshold_binary.png within the output directory). Try to makes the tresh and maxval parameter to vary and observe the modification of the output.
1.1.2. Invererted binary thresholding
Like for binary thresholding, inverted binary thresholding enables to produce an image that contains only the two differents values $\{0, maxval\}$ according to a threshold intensity, denoted $tresh$. If the intensity of a pixel is higher than $tresh$, then the new pixel intensity is set to $0$, otherwise, its new intensity is set to $maxval$. Inverted binary thresholding can be represented by the function $t_{ib}$ defined such as:
$t_{ib}(p(x,\ y)) = \begin{cases} 0 & \text{if } p(x,y) > tresh \\ maxval & \text{otherwise} \end{cases}$
OpenCV enables to apply inverted binary tresholding on a image using the function threshold. This functions is available within JavaCV using:
threshold(Mat src, Mat dst, double tresh, double maxval, CV_THRESH_BINARY_INV);
Exercise 2.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Threshold.java and append the method:
/**
* Compute the binary threshold for the <code>src</code> image.
* @param src the input image (grayscaledd)
* @param tresh the threshold value
* @param maxval the max value (for rejected pixels)
* @return a Mat that represents the thresholded image.
*/
public static Mat threshBinaryInverted(Mat src, double tresh, double maxval) {
if (src != null) {
Mat dst = new Mat(src.rows(), src.cols(), CV_8UC1);
threshold(src, dst, tresh, maxval, CV_THRESH_BINARY_INV);
return dst;
} else {
return new Mat();
}
}
The use of the value CV_THRESH_BINARY_INV need the following import:
import static org.bytedeco.opencv.global.opencv_imgproc.CV_THRESH_BINARY_INV;
Within the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the method:
/**
* The exercise 2 code.
*/
public static void ex2() {
String file = "data/images/thresh_original.bmp";
// Load an image and converting it in grayscale
Mat image = imread(file, IMREAD_GRAYSCALE);
Mat m = threshBinaryInverted(image, 172, 255);
String output = "output/ex2_treshold_binary_inv.png";
if (imwrite(output, m)) {
System.out.println("wrote tresholded image to "+output+".");
} else {
System.out.println("Cannot save thresholded image.");
}
}
Finally, still in the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the main method such as:
/**
* The main method.
* @param args the main method arguments
*/
public static void main(String[] args) {
System.out.println("Exercise 1: ");
ex1();
System.out.println("");
System.out.println("Exercise 2: ");
ex2();
System.out.println("");
}
Run the program and check the resulting thresholded image (the file ex2_treshold_binary_inv.png within the output directory). Try to makes the tresh and maxval parameter to vary and observe the modification of the output.
1.1.3. Truncated thresholding
Truncated thresholding enables to produce an image that contains values that relies within an interval $[0, tresh]$ where $tresh$ is a maximum threshold intensity. If the intensity of a pixel is higher than $tresh$, the new pixel intensity is set to $tresh$, otherwise, its original intensity is conserved. Truncated thresholding enables to limit maximum intensity value for an image and can be represented by a function $t_{t}$ defined such as:
$t_{t}(p(x,\ y)) = \begin{cases} tresh & \text{if } p(x,\ y) > tresh \\ p(x,\ y) & \text{otherwise} \end{cases}$
OpenCV enables to apply truncated tresholding on a image using the function threshold. This functions is available within JavaCV using:
threshold(Mat src, Mat dst, double tresh, double maxval, CV_THRESH_TRUNC);
Note: The maxval value is ignored using the CV_THRESH_TRUNC flag as truncated threshold only use tresh value.
Exercise 3.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Threshold.java and append the method:
/**
* Compute the truncated threshold for the <code>src</code> image.
* @param src the input image (grayscaledd)
* @param tresh the threshold value
* @return a Mat that represents the thresholded image.
*/
public static Mat threshTruncate(Mat src, double tresh) {
if (src != null) {
Mat dst = new Mat(src.rows(), src.cols(), CV_8UC1);
threshold(src, dst, tresh, tresh, CV_THRESH_TRUNC);
return dst;
} else {
return new Mat();
}
}
The use of the value CV_THRESH_TRUNC need the following import:
import static org.bytedeco.opencv.global.opencv_imgproc.CV_THRESH_TRUNC;
Within the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the method:
/**
* The exercise 3 code.
*/
public static void ex3() {
String file = "data/images/thresh_original.bmp";
// Load an image and converting it in grayscale
Mat image = imread(file, IMREAD_GRAYSCALE);
Mat m = threshTruncate(image, 120);
String output = "output/ex3_treshold_truncate.png";
if (imwrite(output, m)) {
System.out.println("wrote tresholded image to "+output+".");
} else {
System.out.println("Cannot save thresholded image.");
}
}
Finally, still in the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the following code to the main method:
System.out.println("Exercise 3: ");
ex3();
System.out.println("");
Run the program and check the resulting thresholded image (the file ex3_treshold_truncate.png within the output directory). Try to makes the tresh parameter to vary and observe the modification of the output.
1.1.4. Thresholding to zero
Thresholding to zero enables to produce an image where all pixel with an intensity inferior to a certain $tresh$ value is set to $0$. Thresholding to zero enables to only keep pixels with a minimal intensity and can be represented by a function $t_{z}$ defined such as:
$t_{z}(p(x,\ y)) = \begin{cases} p(x,\ y) & \text{if } p(x,\ y) > tresh \\ 0 & \text{otherwise} \end{cases}$
OpenCV enables to apply tresholding to zero on a image using the function threshold. This functions is available within JavaCV using:
threshold(Mat src, Mat dst, double tresh, double maxval, CV_THRESH_TOZERO);
Note: The maxval value is ignored using the CV_THRESH_TOZERO flag as thresholding to zero only use tresh value.
Exercise 4.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Threshold.java and append the method:
/**
* Compute the threshold to zero for the <code>src</code> image.
* @param src the input image (grayscaledd)
* @param tresh the threshold value
* @return a Mat that represents the thresholded image.
*/
public static Mat threshToZero(Mat src, double tresh) {
if (src != null) {
Mat dst = new Mat(src.rows(), src.cols(), CV_8UC1);
threshold(src, dst, tresh, tresh, CV_THRESH_TOZERO);
return dst;
} else {
return new Mat();
}
}
The use of the value CV_THRESH_TOZERO need the following import:
import static org.bytedeco.opencv.global.opencv_imgproc.CV_THRESH_TOZERO;
Within the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the method:
/**
* The exercise 4 code.
*/
public static void ex4() {
String file = "data/images/thresh_original.bmp";
// Load an image and converting it in grayscale
Mat image = imread(file, IMREAD_GRAYSCALE);
Mat m = threshToZero(image, 160);
String output = "output/ex4_treshold_to_zero.png";
if (imwrite(output, m)) {
System.out.println("wrote tresholded image to "+output+".");
} else {
System.out.println("Cannot save thresholded image.");
}
}
Finally, still in the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the following code to the main method:
System.out.println("Exercise 4: ");
ex4();
System.out.println("");
Run the program and check the resulting thresholded image (the file ex4_treshold_to_zero.png within the output directory). Try to makes the tresh parameter to vary and observe the modification of the output.
1.1.5. Inverted thresholding to zero
Inverted thresholding to zero enables to produce an image where all pixel with an intensity superior to a certain $tresh$ value is set to $0$. Inverted thresholding to zero enables to only keep pixels with a limited intensity and can be represented by a function $t_{zi}$ defined such as:
$t_{zi}(p(x,\ y)) = \begin{cases} 0 & \text{if } p(x,\ y) > tresh \\ p(x,\ y) & \text{otherwise} \end{cases}$
OpenCV enables to apply inverted tresholding to zero on a image using the function threshold. This functions is available within JavaCV using:
threshold(Mat src, Mat dst, double tresh, double maxval, CV_THRESH_TOZERO_INV);
Note: The maxval value is ignored using the CV_THRESH_TOZERO_INV flag as thresholding to zero only use tresh value.
Exercise 5.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Threshold.java and append the method:
/**
* Compute the threshold to zero for the <code>src</code> image.
* @param src the input image (grayscaledd)
* @param tresh the threshold value
* @return a Mat that represents the thresholded image.
*/
public static Mat threshToZeroInverted(Mat src, double tresh) {
if (src != null) {
Mat dst = new Mat(src.rows(), src.cols(), CV_8UC1);
threshold(src, dst, tresh, tresh, CV_THRESH_TOZERO_INV);
return dst;
} else {
return new Mat();
}
}
The use of the value CV_THRESH_TOZERO_INV need the following import:
import static org.bytedeco.opencv.global.opencv_imgproc.CV_THRESH_TOZERO_INV;
Within the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the method:
/**
* The exercise 5 code.
*/
public static void ex5() {
String file = "data/images/thresh_original.bmp";
// Load an image and converting it in grayscale
Mat image = imread(file, IMREAD_GRAYSCALE);
Mat m = threshToZeroInverted(image, 160);
String output = "output/ex5_treshold_to_zero_inv.png";
if (imwrite(output, m)) {
System.out.println("wrote tresholded image to "+output+".");
} else {
System.out.println("Cannot save thresholded image.");
}
}
Finally, still in the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the following code to the main method:
System.out.println("Exercise 5: ");
ex5();
System.out.println("");
Run the program and check the resulting thresholded image (the file ex5_treshold_to_zero_inv.png within the output directory). Try to makes the tresh parameter to vary and observe the modification of the output. Is there any unexpected behaviors ?
Inverted thresholding to zero artifacts: When the pixel values at the boundary transition from 0 to the value of the number, over very few pixels, some of the boundary pixels fall below the threshold.
1.2. Adaptive Threshold
Basic (global) thresolding methods use a single arbitrary threshold value in order to modify the image. This approach can be limited if the image is not uniform (like, for example, if the image has different lighting conditions in different areas). In that case, adaptive thresholding aims to determine the threshold for a pixel based on a small region around it. Adaptive thresholding behaves like basic thresholding with different thresholds for different regions of the same image.
The threshold value can be computed for each pixel $(x,\ y)$ using 2 differents methods (mean and Gaussian). Both methods relies on the neighborhood of size $n$ around the pixel and an arbitrary value $C$.
Mean: The mean method compute the threshold value as the mean of the neighbourhood area (all the pixels that are closer than n) minus the value $C$. More formally:
\[ tresh\ =\ \frac{1}{n^{2}} \sum_{i=-\frac{n}{2}}^{\frac{n}{2}}\sum_{j=-\frac{n}{2}}^{\frac{n}{2}}p(x+i,\ y+j)\ -\ C \]
Gaussian: The Gaussian method compute the gaussian-weighted sum of the pixel neighbourhood values minus the value $C$.
Within OpenCV, adaptive threshold is done using:
void adaptiveThreshold(Mat src, Mat dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C );
With the parameters:
- src is the input image (grayscaled)
- dst is the resulting image (the thresholded one) with the same size ant type as src
- maxValue is the parameter used by the underlying basic thresholding for determining the thresholded pixel intensity
- adaptiveMethod is the method to use for adapting the threshold parameter. Can be either:
- CV_ADAPTIVE_THRESH_MEAN_C for mean computation
- CV_ADAPTIVE_THRESH_GAUSSIAN_C for Gaussian computation
- thresholdType determine which underlying basic threshold has to be used. Can be either:
- CV_THRESH_BINARY for binary thresholding
- CV_THRESH_BINARY_INV for inverted binary thresholding
- blockSize the size (in pixels) of the neighborhood to take in accound when computing threshold value using mean or gaussian computation
- C is the arbitrary minoration parameter
Exercise 6.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Threshold.java and append the method:
/**
* The exercise 6 code.
*/
public static void ex6() {
String file = "data/images/thresh_original.bmp";
// Load an image and converting it in grayscale
Mat src = imread(file, IMREAD_GRAYSCALE);
}
Complete the ex6 method to perform all possible adaptive thresholding from the src image:
- Mean / binary
- Mean / binary inverted
- Gaussian / binary
- Gaussian / binary inverted
The results have to be saved within the output directory as images named ex6_<adaptive_method>_<threshold_method>.png. The parameters to be used are:
- maxValue = 255
- blockSize = 11
- C = 2.0
Still in the file src/main/java/fr/utln/javacv/intermediate/Threshold.java, append the following code to the main method:
System.out.println("Exercise 6: ");
ex6();
System.out.println("");
Run the program and check the resulting thresholded images.
2. Filtering
Images filtering enables to apply linear transformations to images. As images can be seen as 2-dimensional signals, they can be filtered with various low-pass filters (LPF) or high-pass filters (HPF). Low-pass filters can be used for removing noise, on the other hand, high-pass filters help in finding sharpen values. These two aprocach can be applied on images and enables respectively to smooth (blur) images or to sharpen images (and, for example, making edges more visibles). Applying a filter on an image is made by computing a convolution.
2.1. Convolution
A convolution is the application of a kernel, denoted $K$ for each pixel of an image. A convolution kernel is a $n$ sized square matrix with the general form:
\[
K\ =\ \begin{bmatrix}
k_{0, 0} & \cdots & k_{0,j} & \cdots & k_{0, n-1} \\
\vdots & \ddots & \vdots & \ddots & \vdots \\
k_{i,0} & \cdots & k_{i,j} & \cdots & k_{i,n-1} \\
\vdots & \ddots & \vdots & \ddots & \vdots \\
k_{n-1,0} & \cdots & k_{n-1,j} & \cdots & k_{n-1,n-1}
\end{bmatrix}, k_{i,j}\in\mathbb{R}
\]
For example, a convolution kernel of size $3$ can be:
\[
K\ =\ \begin{bmatrix}
-1 & 1 & 1 \\
1 & 2 & 1 \\
1 & 1 & -1
\end{bmatrix}
\]
The computation of a convolution is processed as follows:
- From a convolution kernel $K$ of size $n\times{}n$
- Centering the kernel over a pixel $(x,\ y)$ with an intensity $p(x,y)$
- Multiplying each value $K(i,j)$ in the kernel, with the corresponding pixel intensity $p(u,\ v)$ in the source image
- Summing the result of the previous multiplications
- Replacing the value of pixel $p\prime{}(x,\ y)$ within the destination image with the computed sum
More formally:
\[ p\prime{}(x,y) = \sum_{i=0}^{n-1} \sum_{j=0}^{n-1} k_{i,j}p(x+i-\frac{n}{2}, y+j-\frac{n}{2}) \]
Note: Previous equation illustrate that convolution only depends on the kernel size and values.
Convoluting an image can be done within OpenCV using the method:
void filter2D(Mat src, Mat dst, int ddepth, Mat kernel )
that is available through the import:
import static org.bytedeco.opencv.global.opencv_imgproc.filter2D;
The parameters of the filter2D method are:
- src is the source image
- dst is the destination image that has the same size and the same numbers of channels as src
- ddepth is the depth of dst. If this parameter is set to -1, the depth of dst will be the same as the depth of src
- kernel is the convolution kernel to use (a n-sized square Mat)
Exercise 7.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Filtering.java and append the method:
/**
* The exercise 7 implementation.
*/
public static void ex7() {
// Load a source image
Mat src = imread("data/images/wood.jpg", IMREAD_UNCHANGED);
// Create a destination image with same size and type
Mat dst = new Mat(src.rows(), src.cols(), src.type());
// Create the kernel
// [ 0 0 0 ]
// [ 0 1 0 ]
// [ 0 0 0 ]
int n = 3;
Mat kernel = Mat.zeros(n, n, CV_64FC1).asMat();
DoubleIndexer indexer = kernel.createIndexer();
indexer.put(1, 1, 1);
// Applying the convolution
filter2D(src, dst, -1, kernel);
// Saving the output
imwrite("output/filtering-ex1.png", dst);
}
Remind: The class DoubleIndexer and the value CV_64FC1 needs the imports:
import static org.bytedeco.opencv.global.opencv_core.CV_64FC1;
import org.bytedeco.javacpp.indexer.DoubleIndexer;
Still in the file src/main/java/fr/utln/javacv/intermediate/Filtering.java, append the following code to the main method:
/**
* The main method.
* @param args the main method arguments
*/
public static void main(String[] args) {
System.out.println("Exercise 7.");
ex7();
System.out.println("");
}
Run the program and check the resulting thresholded images. Is there any differences between the input image data/images/wood.jpg and the output image output/filtering-ex7.png ?
Convolution can be set to identity transformation using a kernel with all values set to $0$ except the central value that is set to $1$.
\[
K\ =\ \begin{bmatrix}
0 & \cdots & 0 & \cdots & 0 \\
\vdots & \ddots & \vdots & \ddots & \vdots \\
0 & \cdots & 1 & \cdots & 0 \\
\vdots & \ddots & \vdots & \ddots & \vdots \\
0 & \cdots & 0 & \cdots & 0
\end{bmatrix}, k_{i,j}\in\mathbb{R}
\]
Applying a convolution with such a kernel to an image will not modify it.
Within previous exercise, creating a kernel is made by the instructions:
int n = 3;
Mat kernel = Mat.zeros(n, n, CV_64FC1).asMat();
DoubleIndexer indexer = kernel.createIndexer();
indexer.put(1, 1, 1);
As JavaCV does not easily allow to create a Mat from arrays of numbers, creating kernels can be time consuming.
Exercise 8.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Filtering.java and append the method:
/**
* Create a convolution kernel of size <code>n</code> initialized with the given array.
* The array has to be n x n sized.
* @param n the size of the kernel
* @param matrix the initial values
* @return the created convolution kernel
*/
public static Mat createKernel(int n, double[][] matrix) {
Mat kernel = Mat.zeros(n, n, CV_64FC1).asMat();
// Complete the code
return kernel;
}
Complete the createKernel method in order to enables the creation of a kernel as a Mat from the given size n and the given matrix values.
Within the same file, add the following method for displaying a Mat:
/**
* Display the given {@link Mat}
* @param m the {@link Mat} to display
*/
public static void displayMatDouble(Mat m) {
// Create an indexer for accessing Mat data
DoubleIndexer indexer = m.createIndexer();
for(int row = 0; row < m.rows(); row++) {
System.out.print("[");
for(int col = 0; col < m.cols(); col++) {
// Display the value of the Mat cell at (row, col)
System.out.print(" "+indexer.get(row, col));
}
System.out.println(" ]");
}
}
Within the same file, add the method:
/**
* The exercise 8 implementation.
*/
public static void ex8() {
double[][] matrix = {{1, 0, 1},
{0, 1, 0},
{1, 1, 1}};
Mat kernel = createKernel(3, matrix);
displayMatDouble(kernel);
}
Still in the file src/main/java/fr/utln/javacv/intermediate/Filtering.java, append the following code to the main method:
System.out.println("Exercise 8.");
ex8();
System.out.println("");
Run the program and ensure that the creation of convolutional kernel from an array of numbers is functionnal.
The nature of the convolution kernel (size, values) influences the transformation applied. Depending on the kernel, it is possible to blur, sharpen or even detect edges in an image.
2.2. Image blurring
Blurring image consist in averaging all pixels values according to its neighborhood, in order to minimize the difference between close pixels intensities.
2.2.1. Convolutional blurring
Convolutional blurring is made by applying convolution with uniform values. For example, the convolution kernel:
\[
K\ =\ \begin{bmatrix}
1 & 1 & 1 & 1 & 1 \\
1 & 1 & 1 & 1 & 1 \\
1 & 1 & 1 & 1 & 1 \\
1 & 1 & 1 & 1 & 1 \\
1 & 1 & 1 & 1 & 1
\end{bmatrix}
\]
enables to blur images.
Exercise 9.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Filtering.java and append the method:
/**
* The exercise 9 implementation.
*/
public static void ex9() {
// Load a source image
Mat src = imread("data/images/wood.jpg", IMREAD_UNCHANGED);
// Create a destination image with same size and type
Mat dst = new Mat(src.rows(), src.cols(), src.type());
double[][] matrix = {{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1}};
Mat kernel = createKernel(5, matrix);
// Applying the convolution
filter2D(src, dst, -1, kernel);
// Saving the output
imwrite("output/filtering-ex9-blur.png", dst);
}
Still in the file Filtering.java, append the following code to the main method:
System.out.println("Exercise 9.");
ex9();
System.out.println("");
Run the program and check the output image . What happened ?
Applying a convolution to a pixel replace its intensity by the sum of the intensities of its neighborhood multiplicated by the values of the convolution kernel. If the sum of the kernel values is greater than $1$, the resulting intensity will be grater than the original intensity and so, the global brightness of the source image will be maximized. It is possible to avoid the maximization of the brightness by using normalized kernels. A normailzed kernel is obtained by dividing all its values by the norm of the matrix:
\[
K\ =\ \frac{1}{\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}k_{i,j}^2}\begin{bmatrix}
k_{0, 0} & \cdots & k_{0,j} & \cdots & k_{0, n-1} \\
\vdots & \ddots & \vdots & \ddots & \vdots \\
k_{i,0} & \cdots & k_{i,j} & \cdots & k_{i,n-1} \\
\vdots & \ddots & \vdots & \ddots & \vdots \\
k_{n-1,0} & \cdots & k_{n-1,j} & \cdots & k_{n-1,n-1}
\end{bmatrix}
\]
Multiplying all the values of a Mat by a scalar is possible within OpenCV using the function:
public static native MatExpr multiply(Mat a, double s)
This function is available using the import:
import static org.bytedeco.opencv.global.opencv_core.multiply;
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Filtering.java and append the method:
/**
* Create a normalized convolution kernel of size <code>n</code> initialized with the given array.
* The array has to be n x n sized.
* @param n the size of the kernel
* @param matrix the initial values
* @return the created convolution kernel
*/
public static Mat createKernelNormalized(int n, double[][] matrix) {
Mat kernel = Mat.zeros(n, n, CV_64FC1).asMat();
// Put code here
return kernel;
}
Complete the createKernelNormalized method in order to enables the creation of a normalized kernel as a Mat from the given size n and the given matrix values.
Remind: Multiplying a Mat by a scalar is possible within OpenCV using the function multiply.
Still in the file Filtering.java, add the following method:
/**
* The exercise 10 implementation.
*/
public static void ex10() {
// Load a source image
Mat src = imread("data/images/wood.jpg", IMREAD_UNCHANGED);
// Create a destination image with same size and type
Mat dst = new Mat(src.rows(), src.cols(), src.type());
double[][] matrix = {{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1},
{1, 1, 1, 1, 1}};
Mat kernel = createKernelNormalized(5, matrix);
displayMatDouble(kernel);
// Applying the convolution
filter2D(src, dst, -1, kernel);
// Saving the output
imwrite("output/filtering-ex10-blur-normalized.png", dst);
}
Finally, within the file Filtering.java, append the following code to the main method:
System.out.println("Exercise 10.");
ex10();
System.out.println("");
Run the program and check the output image filtering-ex10-blur-normalized.png. Is the result is correct ?
2.2.2. Gaussian blurring
Convolutional blurring enables to apply a kernel to an image. However, the kernel remains the same for the whole image. As it is the case for thresholding, the use of the same convolution kernel for the whole image may cause edges to be smoothed and can lead to problem when performing, for example, edges detection. Gaussian blurring address this problem by performing a weighted average. In this case, pixel values are weighted according to their distance from the center of the kernel. Pixels further from the center have less influence on the weighted average. This method of computation enable to preserve edges.
OpenCV enable to use Gaussian filtering using the method:
public static native void GaussianBlur(Mat src, Mat dst, Size ksize, double sigmaX, double sigmaY, int borderType )
that is available using the import:
import static org.bytedeco.opencv.global.opencv_imgproc.GaussianBlur;
The GaussianBlur use the following parameters:
- src is the source image
- dst is the destination image that has the same size and the same numbers of channels as src
- ksize is the size of the convolutional kernel to use
- sigmaX and sigmaY are the Gaussian kernel standard deviations, in the X (horizontal) and Y (vertical) direction. If the values are set to $0$ (default behavior), the standard deviations are computed from the kernel size (width and height respectively). If set, these two values has to be positive
- borderType is always set to BORDER_DEFAULT
Exercise 11.
Within the project javacv-thresh-filter, edit the file src/main/java/fr/utln/javacv/intermediate/Filtering.java and append the method:
/**
* The exercise 11 implementation.
*/
public static void ex11() {
// Load a source image
Mat src = imread("data/images/wood.jpg", IMREAD_UNCHANGED);
// Create a destination image with same size and type
Mat dst = new Mat(src.rows(), src.cols(), src.type());
// Applying the Gaussian blur
GaussianBlur(src, dst, new Size(5,5), 0, 0, BORDER_DEFAULT);
// Saving the output
imwrite("output/filtering-ex11-blur-gaussian.png", dst);
}
Still within the file Filtering.java, append the following code to the main method:
System.out.println("Exercise 11.");
ex11();
System.out.println("");
Run the program and check the gaussian blurred image filtering-ex11-blur-gaussian.png. Compare it with the result of exercise 10 (filtering-ex10-blur-normalized.png). What are the differences ?
2.3. Image sharpening