Here's an approach using morphological operations + contour filtering
- Convert image to grayscale
- Otsu's threshold to obtain a binary image
- Perform morph close to connect words into a single contour
- Dilate to ensure that all bits of text are contained in the contour
- Find contours and filter using contour area
- Remove text by "filling" in the contour rectangle with the background color
I used chrome developer tools to determine the background color of the image which was (222,228,251). If you want to dynamically determine the background color, you could try finding the dominant color using k-means. Here's the result

import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, close_kernel, iterations=1)
dilate_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,3))
dilate = cv2.dilate(close, dilate_kernel, iterations=1)
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area > 800 and area < 15000:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (222,228,251), -1)
cv2.imshow('image', image)
cv2.waitKey()
Answer from nathancy on Stack Overflowpython - OpenCV - Remove text from image - Stack Overflow
Can someone please help me remove text from image? Python, OpenSource
image processing - Remove text from jpeg - Stack Overflow
How to remove text from MRI image using Python.?
Can someone please help me remove text from image? Python, OpenSource
I've tried many methods and models, but the results are not good.
The region where text is present is not perfectly blended into the original image background.
Obviosly, the simple method is cv2 inpaint and other are the SOTA inpainting models like stable diffusion inpainting, etc.
Please Help...
One way to do this is with a technique called inpainting. You can find that in (Python) Skimage at
http://scikit-image.org/docs/dev/api/skimage.restoration.html#inpaint-biharmonic
or in OpenCV at
https://docs.opencv.org/3.0-beta/modules/photo/doc/inpainting.html https://docs.opencv.org/3.4.0/df/d3d/tutorial_py_inpainting.html
Here is the Python Skimage inpainting processing:
kitty image:

watermark image:

The Skimage inpainting requires a binary mask image. So I can convert your watermark to such a mask by:
convert watermark.png -alpha extract -threshold 0 mask.png
mask image:

Here is the Python code:
#!/opt/local/bin/python3.6
import numpy as np
import skimage.io
import skimage.restoration
import skimage.exposure
img = skimage.io.imread('/Users/fred/desktop/kitty.png')
msk = skimage.io.imread('/Users/fred/desktop/mask.png')
msk = skimage.exposure.rescale_intensity(msk, in_range='image', out_range=(0,1))
newimg = skimage.restoration.inpaint_biharmonic(img, msk, multichannel=True)
skimage.io.imsave('/Users/fred/desktop/kitty_inpaint_biharmonic.png', newimg)

Imagemagick does not have an official version of that. But user snibgo on the Imagemagick forum has implemented a custom version he calls 'hole filling' at http://im.snibgo.com/fillholespri.htm. He shows an example at https://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=28640#p127233.
Furthermore, on the same page, he shows some clever Imagemagick code that does repeated small amounts of resizing. This achieves a somewhat similar result to inpainting. But in general, it will not be quite as good as inpainting. Nevertheless, it does work moderately well for your image.
kitty image:

watermark image:

First, I have to take your watermark image and extract a binary image from it where the text is white and the background black. Then I use it to make the kitty image transparent where the text resides. Then I crop out the area of the text just to make the subsequent processing faster.
convert kitty.png \
\( watermark.png -alpha extract -threshold 0 -negate \) \
-alpha off -compose copy_opacity -composite \
-crop 490x102+235+150 +repage tmp1.png

Then I run his rather long sequence of successive resizing of the image followed by merging all the layers and resizing back to the original size.
convert tmp1.png \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
\( +clone -resize 90.9091% \) \
-layers RemoveDups \
-filter Gaussian -resize 490x102! \
-background None \
-compose DstOver -layers merge \
-alpha opaque \
tmp2.png

Then finally, I composite this result back into the place on the kitty image from which I had cropped it.
convert kitty.png tmp2.png -geometry +235+150 -compose over -composite kitty2.png

At full resolution, you can still make out the very faint text residual in this image. The Skimage result is better as can be seen by rapidly alternating the two images.
Albert Myšák has the proper technique here, since one knows the exact alpha channel values for the text and the equation that describes how the alpha channel is blended with the image. Kudos!
My earlier methods are better suited to when one only knows the positions of the text pixels in the image, so that one can make a binary mask or convert the text to transparency or some other color needed by whatever inpainting software is used.
Here is the equivalent one line Imagemagick command broken into several continuation lines for easier reading and explaining.
Line1 - read the kitty image
Line2 - copy it and make it all white rgb(255,255,255), save it into memory and delete the copy image from the image sequence
Line3 - read the watermark image and extract the alpha channel. Then subtract it from white
Line4 - divide the result of line 3 by the white image
Line5 - divide the kitty image by the result of line 4
Line6 - save the result to disk
convert kitty.png \
\( -clone 0 -fill white -colorize 100 -write mpr:white +delete \) \
\( watermark.png -alpha extract mpr:white -compose minus -composite \
mpr:white +swap -compose divide -composite \) \
+swap -compose divide -composite \
kitty_restored.png

The cv2 pre-processing is unecessary here, tesseract is able to find the text on its own. See the example below, commented inline:
results = pytesseract.image_to_data('1.png', config='--psm 11', output_type='dict')
for i in range(len(results["text"])):
# extract the bounding box coordinates of the text region from
# the current result
x = results["left"][i]
y = results["top"][i]
w = results["width"][i]
h = results["height"][i]
# Extract the confidence of the text
conf = int(results["conf"][i])
if conf > 60: # adjust to your liking
# Cover the text with a white rectangle
cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), -1)
Detected text on the left, cleaned image on the right:

Another option, without using Tesseract. Just use the area of the contours to filter the smaller ones by covering them with white-filled rectangles:
# Imports
import cv2
import numpy as np
# Read image
imagePath = "C://opencvImages//"
inputImage = cv2.imread(imagePath+"0enxN.png")
# Convert BGR to grayscale:
binaryImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
# Invert image:
binaryImage = 255 - binaryImage
# Find the external contours on the binary image:
contours, hierarchy = cv2.findContours(binaryImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Invert image:
binaryImage = 255 - binaryImage
# Look for the bounding boxes:
for _, c in enumerate(contours):
# Get the contour's bounding rectangle:
boundRect = cv2.boundingRect(c)
# Get the dimensions of the bounding rect:
rectX = boundRect[0]
rectY = boundRect[1]
rectWidth = boundRect[2]
rectHeight = boundRect[3]
# Get Bounding Rectangle Area:
rectArea = rectWidth * rectHeight
# Set minimum area threshold:
minArea = 1000
# Check for minimum area:
if rectArea < minArea:
# Draw white rectangle to cover small contour:
cv2.rectangle(binaryImage, (rectX, rectY), (rectX + rectWidth, rectY + rectHeight),
(255, 255, 255), -1)
cv2.imshow("Binary Mask", binaryImage)
cv2.waitKey(0)
This produces:










