As mentioned by @RickM there are various color spaces available to analyze your image. Since you are focused on removing the shade/shadow you need to focus on channels that contain brightness information and keep the color channels aside.
In this case the LAB color space turned out to be helpful. The luminance channel expressed a lot of info on the amount of brightness in the image
img = cv2.imread('4.png')
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
cv2.imshow('Luminance', l)

Then I obtained a threshold and masked the result with the original image to get mask1
:
ret2, th = cv2.threshold(l, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
mask1 = cv2.bitwise_and(img, img, mask = th)
cv2.imshow('mask1', mask1)

But now the background is not what you intended it to be.
I created an image with white pixels of the same image dimension (white
) and masked the inverted threshold image with it to get mask2
:
white = np.zeros_like(img)
white = cv2.bitwise_not(white)
mask2 = cv2.bitwise_and(white, white, mask = cv2.bitwise_not(th))
cv2.imshow('mask2', mask2)

Upon adding both these images you get he intended image:
cv2.imshow('fin_img', mask2 + mask1)

Keep in mind that this would work only for similar images provided in the question.