Separate histogram equalization for left and right sides

In real-world conditions, it is common to have strong lighting on one half of the face and weak lighting on the other. This has an enormous effect on the face recognition algorithm, as the left- and right-hand sides of the same face will seem like very different people. So, we will perform histogram equalization separately on the left and right halves of the face, to have a standardized brightness and contrast on each side of the face.

If we simply applied histogram equalization on the left half and then again on the right half, we would see a very distinct edge in the middle because the average brightness is likely to be different on the left and the right side. So to remove this edge, we will apply the two histogram equalizations gradually from the left or right-hand side toward the center, and mix it with a whole face histogram equalization.

Then, the far left-hand side will use the left histogram equalization, the far right-hand side will use the right histogram equalization, and the center will use a smooth mix of the left and right values and the whole face equalized value.

The following screenshot shows how the left-equalized, whole-equalized, and right-equalized images are blended together:

To perform this, we need copies of the whole face equalized, as well as the left half equalized and the right half equalized, which is done as follows:

    int w = faceImg.cols; 
int h = faceImg.rows;
Mat wholeFace;
equalizeHist(faceImg, wholeFace);
int midX = w/2;
Mat leftSide = faceImg(Rect(0,0, midX,h));
Mat rightSide = faceImg(Rect(midX,0, w-midX,h));
equalizeHist(leftSide, leftSide);
equalizeHist(rightSide, rightSide);

Now, we combine the three images together. As the images are small, we can easily access the pixels directly using the image.at<uchar>(y,x) function, even if it is slow; so let's merge the three images by directly accessing pixels in the three input images and output images, as follows:

    for (int y=0; y<h; y++) { 
for (int x=0; x<w; x++) {
int v;
if (x < w/4) {
// Left 25%: just use the left face.
v = leftSide.at<uchar>(y,x);
}
else if (x < w*2/4) {
// Mid-left 25%: blend the left face & whole face.
int lv = leftSide.at<uchar>(y,x);
int wv = wholeFace.at<uchar>(y,x);
// Blend more of the whole face as it moves
// further right along the face.
float f = (x - w*1/4) / (float)(w/4);
v = cvRound((1.0f - f) * lv + (f) * wv);
}
else if (x < w*3/4) {
// Mid-right 25%: blend right face & whole face.
int rv = rightSide.at<uchar>(y,x-midX);
int wv = wholeFace.at<uchar>(y,x);
// Blend more of the right-side face as it moves
// further right along the face.
float f = (x - w*2/4) / (float)(w/4);
v = cvRound((1.0f - f) * wv + (f) * rv);
}
else {
// Right 25%: just use the right face.
v = rightSide.at<uchar>(y,x-midX);
}
faceImg.at<uchar>(y,x) = v;
} // end x loop
} //end y loop

This separated histogram equalization should significantly help reduce the effect of different lighting on the left- and right-hand sides of the face, but we must understand that it won't completely remove the effect of one-sided lighting, since the face is a complex 3D shape with many shadows.