Scanned Painting Tile Scanned Painting Tile enter image description here

The images above are tiles taken from a scanned painting. It's easy to see where there are tiny reflections scattered throughout. I wish to remove (or diminish) the tiny reflections somehow, across a few thousand (50k) such images. I want to preserve as much of the remaining detail as possible.

I am more concerned with the point-like specks than I am the cracks.

I thought that de-speckling may work, but that seems to do something different.

  • $\begingroup$ Will a MATLAB code be helpful? $\endgroup$
    – Royi
    Commented Sep 29, 2022 at 16:48
  • $\begingroup$ @Royi, it might be.. I don't speak MATLAB - but it cannot be that much harder than the 30 other languages I know. $\endgroup$
    – Konchog
    Commented Sep 29, 2022 at 17:27
  • $\begingroup$ A median filter of suitable size perhaps? And masking it to only affect really bright pixels? $\endgroup$
    – Knut Inge
    Commented Sep 29, 2022 at 21:32

1 Answer 1


This is the best I could come to. Improvements are extremely welcome.

fn = 'tiles/xxx.jpg'  # set tile filename
img = cv2.imread(fn)  # read tile into img.
# median blur. This seems to be better than gaussian for bright dots.
blr = cv2.medianBlur(img, 15)
# now grab brightness V of HSV here - but Gray is possibly as good
hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
val = hsv[:, :, 2]
# use ADAPTIVE_THRESH_GAUSSIAN to find spots. 
# I manually tweaked the values- these seem to work well with what I have.
at = cv2.adaptiveThreshold(np.array(255 - val), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 17)
# Now invert the threshold, and run another for edges.
ia = np.array(255 - at)  # inversion of adaptiveThreshold of the value.
iv = cv2.adaptiveThreshold(ia, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 9)
# ib = merged edges with the dots (as an invert mask).
ib = cv2.subtract(iv, ia)
# Turn this to a 3 channel mask.
bz = cv2.merge([ib, ib, ib])
# Use the blur where the mask is, otherwise use the image.
dsy = np.where(bz == (0, 0, 0), blr, img)
result = dsy

Tile 1 Tile 2 Tile 3

  • 1
    $\begingroup$ Very good! Do you improved this code?? $\endgroup$
    – Curious G.
    Commented Jun 13, 2023 at 11:37
  • 1
    $\begingroup$ I used the code written here. The results were very satisfactory. $\endgroup$
    – Konchog
    Commented Jun 13, 2023 at 13:05
  • 1
    $\begingroup$ Amazing, actually you saved my life... =) I just asked... $\endgroup$
    – Curious G.
    Commented Jun 13, 2023 at 13:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.