Digital Image Processing Class: First Unit Exercises

This post is dedicated to the Digital Image Processing exercises with professor Agostinho, which is happening during the second semester of this year. Here, the exercises related to the first unit of the class are going to be listed, to be a part of the class final grade. The OpenCV is going to be the main library used in this course, with the C++ programming language. A simple tutorial can be found on my friend’s website.

All the codes can be accessed by clicking the titles of each exercise.
The root directory can be accessed here.

2.2.1: Negative of a region

The first exercise is to create the negative of an image inside a given rectangle. The following image illustrates the concept:

negative_img

For implementation simplicity, the images were converted to gray-scale. The trick is inverting the values of the pixels inside the selected region. If we consider the range of the gray-scale to be from 0 to 255, as the following code does:

for(int i=x0;i<y0;i++){
  for(int j=x1;j<y1;j++){
    image.at(i,j)= 255 - image.at(i,j);
  }
}

The user is asked to give the rectangle points at the beginning of execution.

2.2.2: Regions swapping

The next exercise asks to split the image in 4 equal pieces, and change their positions diagonally:

switched

The simplicity is similar with the previous exercise. In order to switch the four regions, I developed a series of “hardcoded” for loops to do this job:

copy = image.clone();
for(int i=0;i<rows/2;i++){
  for(int j=0;j<cols/2;j++){
    image.at(i,j)= copy.at(i+(rows/2),j+(cols/2));
  }
}

for(int i=rows/2;i<rows;i++){
  for(int j=cols/2;j<cols;j++){
    image.at(i,j)= copy.at(i-(rows/2),j-(cols/2));
  }
}

for(int i=rows/2;i<rows;i++){
  for(int j=0;j<cols/2;j++){
    image.at(i,j)= copy.at(i-(rows/2),j+(cols/2));
  }
}

for(int i=0;i<rows/2;i++){
  for(int j=cols/2;j<cols;j++){
    image.at(i,j)= copy.at(i+(rows/2),j-(cols/2));
  }
}

I also had to create a copy of the image, to preserve the original pixels information.

3.2.1: Efficiently counting bubbles


bolhas

The professor gave a sample code to count the number of “bubbles” inside the image above. However, the program can count only up to 255 bubbles, because each time it encounters an “uncounted” bubble, it gives a different color for it. The problem is that there are only 256 different intensities of gray in the standard 8-bit scale. The next picture illustrates the bubbles after the labeling.

lab_old

The solution is simple. Just choose an intensity to differ the counted bubbles from the uncounted ones. The 0 value is used for background, 255 for uncounted bubbles and 140 for counted bubbles. The bubbles are painted with the OpenCV function “floodFill”:

for(int i=0; i<height; i++){
  for(int j=0; j<width; j++){
    if(image.at(i,j) == 255){
      nobjects++;
      p.x=j;
      p.y=i;
      floodFill(image,p,140);
    }
  }
}

3.2.2: Counting bubbles with holes

The task here is to count bubbles with holes. It is asked to remove the bubbles from the corners, since it is not certain if they have holes or not. To do so, the code searches for white pixels at the borders of the image, and if they are found, paint those bubbles black:

for(int i=0; i<height; i++){
  for(int j=0; j<width; j++){
    if(i==height-1 || i==0 || j==0 || j==width-1){
      if(image.at(i,j) == 255){
        p.x=j;
        p.y=i;
        floodFill(image,p,0);
      }
    }
  }
}

no_border

Later, the background is painted with a fixed color, which will make the holes color different. A given intensity is chosen to differ the classes of instances in the image. The intensity 0 is used for holes, 70 for background, 140 for bubbles without holes, 210 for bubbles with holes and 255 for unidentified bubbles.

After, the code counts the number of bubbles by searching for white pixels, and painting then with 140. Then, it searches for black pixels which corresponds to holes. If a black pixel is found, it searches in the same line for the other margin of the bubble and paint it with 210, which will help the program to not count more than one hole per bubble. A Boolean variable is used to tell if the black pixel found was already counted as a bubble with hole or not.

int nholes=0;
bool hole_computed=false;
for(int i=0; i<height; i++){
  for(int j=0; j<width; j++){
    if(image.at(i,j) == 210){
      hole_computed=true;
    }
    else if(image.at(i,j) == 70){
      hole_computed=false;
    }
    if(image.at(i,j) == 0 && !hole_computed){
      nholes++;
      while(image.at(i,j) != 140) j++;
      p.x=j;
      p.y=i;
      floodFill(image,p,210);
    }
  }
}

bubble_holes

bubble_count

4.2.1: Equalization of images

This problem consists of equalizing a given image in order to utilize the full range of the pixels gray-scale. The concept is based on the histogram of the image, which is a discrete graph describing the number of pixels for each gray intensity. From this histogram, one can observe the minimum and maximum color intensity an image has, and from there, expand this range to the minimum of the gray scale (0) and maximum (255). In other words, the contrast of the image is improved with this approach.

Equalized image at a very dark environment, with histogram at upper-left corner:

bef_eq

Having a cellphone flashlight on my face:

shot

It is possible to observe the effect of equalization. On a very dark place, the image seems very noisy because of the maximum contrast given.

The program captures frames from the computer camera, converts it to gray-scale, calculates its histogram with OpenCV functions and shows both image and histogram at the screen. The gray-scale conversion and equalization are done as:

cvtColor(image, gray, CV_BGR2GRAY);
equalizeHist( gray, gray );

4.2.2: Motion detection based on histograms comparison

This exercise features the implementation of a motion detection. The concept is to constantly compare the actual image histogram with another image histogram got some time ago. If there is a movement of an object, the pixels intensity will vary, and the software tells that there is movement!

To implement this functionality, my program compares two consecutive frames by summing up the difference of each histogram values. When the sum passes through a predefined threshold, it prints that there is a movement.

for(int i=0; histR.rows > i;i++){
  dif += (int)abs(histR.at(i)-detector.at(i));
}
if(dif > 20) std::cout << "Movement!!\n";

In the video above, the histograms difference is constantly printed at the terminal.

5.2: Laplacian of Gaussian filter

The Laplacian of the Gaussian filter is asked to be implemented and analysed in this practice. The Laplacian filter is used to take an omnidirectional borders detection of the image, while the Gaussian makes the image blurred which attenuates the borders. Depending on the type of noise the image contains, the Gaussian filter can make the contours got from the Laplacian filter more solid.

The base code given contains some implementations of masks that are used to filter the image. The following image illustrates the Laplacian filter only:

laplacian

Then, the Gaussian filter is applied with the Laplacian filter:

filters

The borders are better seen because the Gaussian filter acts as a low pass filter, which attenuates the higher frequencies related to the noise of my computer camera.

The code below shows the masks used for both pictures. The OpenCV function “filter2D” implements the spacial convolution of the mask with the image. Both Laplacian and Gaussian filters can be applied at the same time since they are linear and associative. This article (in portuguese) explains better.

float laplacian[]={0,-1,0,
           -1,4,-1,
           0,-1,0};
float LoG[]={0,0,-1,0,0,
               0,-1,-2,-1,0,
              -1,-2,16,-2,-1,
               0,-1,-2,-1,0,
               0,0,-1,0,0 };
filter2D(frame32f, frameFiltered, frame32f.depth(), mask, Point(1,1), 0);

6.2.1: Tilt-shift effect

Here, it is needed to implement the camera effect of focusing a certain spot of the image, while blurring the rest of the image. Three controls are asked, the first controls the level of the region to be focused, the second controls the transition of the blurred part to the focused one, and the last controls the height of the focused region.

The next image shows controls for the focus region. All controls are initially zero, which results in a totally blurred image.

controls_tilt

Then, the region is selected with a certain width, but no transition to the blurred place.

tilt1

Now, with a transition coefficient.

tiltdecay

To make this effect, the original image is processed by an average filter, which makes it blurred. The trick is, summing the blurred image with the original image using different percentages of color intensity of the vertical direction. The subtraction of two tanh functions can make the desired effect. In the following image, the number that sums with x (20 and -30) controls the width of the region and the number that divides (6) controls the transition attenuation. For the original image, I used this function directly, while using the inverse (1-f(x)) for the blurred one. The function “addWeighted” is used to add both vectors by a constant factor.

tanh

for(int i=0; i<h; i++){
  double d = (double) heigh_slider+1;
  double l1 = (double) top_slider;
  double c = (double) alfa_slider*255.0/100.0;

  double res = ((tanh((i-c+l1)/d)-tanh((i-c-l1)/d))/2.0);
  for(int j=0; j<w; j++){
    temp1.at(i,j) = (unsigned char)((res)*image1.at(i,j));
    temp2.at(i,j) = (unsigned char)((1.0-res)*image2.at(i,j));
  }
}
addWeighted( temp1, 1.0, temp2, 1.0, 0.0, blended);

At the end, the image is saved with:

imwrite("tiltshot.png", blended);

6.2.2: Stop motion Tilt-shift effect

The goal of this exercise is to use the same resources of the previous exercise to create the effect of a stop-motion miniature video from a real video. The differences from the previous exercise is the read of a video file instead of computer camera, and the generation of a video output.

The next snippet of code illustrates the reading from a video file and the creation of the output object.

string filename = "test.webm";
VideoCapture capture(filename);
int frame_width=   capture.get(CV_CAP_PROP_FRAME_WIDTH);
int frame_height=   capture.get(CV_CAP_PROP_FRAME_HEIGHT);
VideoWriter outputVideo("out.avi",CV_FOURCC('M','J','P','G'),10, 
                        Size(frame_width,frame_height),true);

Then, the frames can be written to the output as:

addWeighted( temp1, 1.0, temp2, 1.0, 0.0, blended);
outputVideo << blended;

The tilt-shift region was implemented the same way as the previous exercise, with the following parameters:


for(int i=0; i<h; i++){
  double d = 10.0;
  double l1 = 50.0;
  double c = 300.0;
  double res = ((tanh((i-c+l1)/d)-tanh((i-c-l1)/d))/2.0);
  for(int j=0; j<w; j++){
    temp1.at(i,j) = (unsigned char)((res)*image1.at(i,j));
    temp2.at(i,j) = (unsigned char)((1.0-res)*image2.at(i,j));
  }
}

The result:

Finally, to create the stop-motion effect, four frames were discarded every loop iteration.

1 thought on “Digital Image Processing Class: First Unit Exercises

  1. Pingback: Digital Image Processing Class: Second Unit Exercises | Yang Tavares

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s