How to remove shadow from scanned images using OpenCV?

Since you didn’t specify any language, I’ll assume Python to illustrate.

A decent starting point might be taking the approach I show in this answer and expand it to work with multiple channels.

Something along the lines of

import cv2
import numpy as np

img = cv2.imread('shadows.png', -1)

rgb_planes = cv2.split(img)

result_planes = []
result_norm_planes = []
for plane in rgb_planes:
    dilated_img = cv2.dilate(plane, np.ones((7,7), np.uint8))
    bg_img = cv2.medianBlur(dilated_img, 21)
    diff_img = 255 - cv2.absdiff(plane, bg_img)
    norm_img = cv2.normalize(diff_img,None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)
    result_planes.append(diff_img)
    result_norm_planes.append(norm_img)
    
result = cv2.merge(result_planes)
result_norm = cv2.merge(result_norm_planes)

cv2.imwrite('shadows_out.png', result)
cv2.imwrite('shadows_out_norm.png', result_norm)

The non-normalized result looks as follows:

enter image description here

And the normalized result:

enter image description here


Example C++ implementation provided by @ruben-estrada-marmolejo

Added as requested, c/c++ code, withouth relaying on using namespace

//Compile with:
            //g++ example.cpp -o salida `pkg-config --cflags --libs opencv4`
            //Ruben Estrada Marmolejo
            //[email protected]
            //Original idea: https://stackoverflow.com/questions/44752240/how-to-remove-shadow-from-scanned-images-using-opencv/44752405#44752405 
            #include<opencv4/opencv2/cvconfig.h>
            #include<opencv2/core/core.hpp>
            #include<opencv2/ml/ml.hpp>
            //#include<opencv/cv.h>
            #include<opencv2/imgproc/imgproc.hpp>
            #include<opencv2/highgui/highgui.hpp>
            #include<opencv2/video/background_segm.hpp>
            #include<opencv2/videoio.hpp>
            #include<opencv2/imgcodecs.hpp>
            #include <iostream>

            void removeShadow(cv::Mat const& src, cv::Mat &result1_diff_img, cv::Mat &result2_norm_img){
                std::vector<cv::Mat> channels;
                cv::split(src, channels);

                cv::Mat zero = cv::Mat::zeros(src.size(), CV_8UC1);
                
                cv::Mat kernel;
                kernel = getStructuringElement(cv::MORPH_OPEN,cv::Size(1,1));
                cv::Mat diff_img[3];
                cv::Mat norm_img[3];
                for (int i =0; i<3;i++){
                cv::Mat dilated_img;
                dilate(channels[i],dilated_img,kernel,cv::Point(-1,-1),1,cv::BORDER_CONSTANT,cv::morphologyDefaultBorderValue());
                cv::Mat bg_img;
                cv::medianBlur(channels[i], bg_img, 21);
                cv::absdiff(channels[i], bg_img, diff_img[i]);
                cv::bitwise_not(diff_img[i],diff_img[i]);
                cv::normalize(diff_img[i], norm_img[i], 0, 255, cv::NORM_MINMAX, CV_8UC1, cv::noArray());
                }
                std::vector<cv::Mat> R1B1 = { diff_img[0], zero, zero };
                std::vector<cv::Mat> R1G1 = { zero, diff_img[1], zero };
                std::vector<cv::Mat> R1R1 = { zero, zero, diff_img[2] };

                cv::Mat result1_B;
                cv::Mat result1_G;
                cv::Mat result1_R;

                cv::merge(R1B1, result1_B);
                cv::merge(R1G1, result1_G);
                cv::merge(R1R1, result1_R);

                cv::bitwise_or(result1_B, result1_G, result1_G);
                cv::bitwise_or(result1_G, result1_R, result1_diff_img);

                std::vector<cv::Mat> R2B1 = { norm_img[0], zero, zero };
                std::vector<cv::Mat> R2G1 = { zero, norm_img[1], zero };
                std::vector<cv::Mat> R2R1 = { zero, zero, norm_img[2] };

                cv::Mat result2_B;
                cv::Mat result2_G;
                cv::Mat result2_R;

                cv::merge(R2B1, result2_B);
                cv::merge(R2G1, result2_G);
                cv::merge(R2R1, result2_R);

                cv::bitwise_or(result2_B, result2_G, result2_G);
                cv::bitwise_or(result2_G, result2_R, result2_norm_img);

            }

            int main(){

                cv::Mat img = cv::imread("test.jpg", cv::IMREAD_COLOR);
                if(img.empty())
                {
                std::cout << "Could not read the image: " << std::endl;
                return 1;
                }
                cv::Mat result1;
                cv::Mat result2;
                removeShadow(img, result1, result2);
                
                imshow("Display window", result1);
                int k = cv::waitKey(0); // Wait for a keystroke in the window
                if(k == 's')
                {
                cv::imwrite("result1.png", result1);
                }
                return 0;

                


            }

Leave a Comment