35

I'd like to remove shadow before image binarization using OpenCV. I've tried Otsu Method and adaptive thresholding, however for images where there are large regions of shadow, these two methods will not give good results.

Any better solutions? Thanks in advance.

[Sample image]1

[Sample image]2

Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
Liu Zhiyuan
  • 519
  • 1
  • 6
  • 5

1 Answers1

81

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
            //ruben.estrada@hetpro.com.mx
            //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;

                


            }
Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
  • 2
    I have write exact same code in ObjectiveC but this code doesn't work for Color. It transforms to Black & White. Even Logo is transforming to Black & White. But my actual need to show Colored logo should be Colored. Just need to remove the Shadow Only. If you help that would be much appreciated. Thanks. – krunal Oct 25 '18 at 11:39
  • @krunal You should post a new question then, and include some example input that causes issues as well as a MCVE in ObjectiveC. – Dan Mašek Oct 25 '18 at 18:19
  • @DanMašek Any luck? :\ – jtlz2 Feb 05 '19 at 08:52
  • 1
    @jtlz2 Uh, any luck with what exactly? – Dan Mašek Feb 05 '19 at 17:00
  • Apologies - was meant for @krunal... :/ I wanted to know whether they had been able to get it working for colour – jtlz2 Feb 05 '19 at 18:06
  • However, I didn't get proper results with colored Text but it properly removes shadow and whiten the Bg, Text color Gray or Black. – krunal Feb 07 '19 at 04:18
  • I can't offer a full solution at the moment... but I wonder if you convert the channels to a HSV model and just remove the saturation from the image if it would just take care of itself (I may be mixing saturation and value - surely maxing one or the other would help solve the problem, right?). I am not well versed enough in opencv to do that quickly, and nor do I have the time. The solution I am looking for happens to be the one posted, but since it isn't what is intended for the user asking the question, I won't upvote it. Valiant attempt at the problem tho. – Shmack Jun 22 '20 at 19:36
  • @ruben-estrada-marmolejo Thank you for the addition. It would be nice if you could make it into a self-contained example (with all necessary includes and I/O so that it behaves just like the Python code), fix the indentation and make sure it doesn't rely on any `using namespace` directives (i.e. add `cv::` where it's missing, it's kind of half-half right now). – Dan Mašek Dec 02 '22 at 00:24