Detecting if an image has a border using PIL
I am using this code to remove an image border using PIL:
def RemoveBlackBorders(img):
bg = Image.new(img.mode, img.size, img.getpixel((0,0)))
diff = ImageChops.difference(img, bg)
diff = ImageChops.add(diff, diff, 2.0, -100)
bbox = diff.getbbox()
if bbox:
return img.crop(bbox)
What I found here: Trim Whitespace Using PIL
And I use this to process all the images contained in the folder:
def CropImages():
global FOLDER
for i in range(1, len(os.listdir(FOLDER))+1):
image = FOLDER + "\\" + str(i) + ".jpg"
img = Image.open(image)
img = RemoveBlackBorders(img)
img.save(image, "JPEG")
Now the problem is that it takes a long ~ 1000 images to complete this operation, so I want to check, BEFORE starting the process, if one of the images in the folder has a border to be deleted, because if the 1.jpg image has a border , the image [n] .jpg will have it for sure too.
source to share
I haven't worked on PIL very much, so I'll try to implement a solution using OPenCV, and if you're satisfied you can put some effort into rewriting your code using PIL.
Assumptions:
- Borders are present only at the top and bottom of this image Frame.
- The borders are dark black.
So let's take a sample image:
First of all, we load the given image to find the length and width of the given image.
import cv2
img = cv2.imread("sample_frame.jpg") #Loading an image in RGB mode.
height, width, channels = img.shape
Now we iterate over pixels parallel to the height and distance (width * 0.5) from both sides, or you can tell the center of the image.
As we know, the border of dark black matches our assumption, so for black (R, G, B) = (0, 0, 0). Or we can say that all values are strictly less than 4 (including some noise in the image.).
border_threshold_R = 4
border_threshold_G = 4
border_threshold_B = 4
mid_pixels = []
top_border_height = 0
bottom_border_height = 0
Iterating in the top half:
for i in xrange(height/2):
mid_pixel_top_half = img[i][width/2]
R, G, B = mid_pixel_top_half[2], mid_pixel_top_half[1], mid_pixel_top_half[0]
if (R<border_threshold_R) and (G<border_threshold_G) and (B<border_threshold_B):
top_border_height+=1
else:
break
Iterating the bottom half:
for i in xrange(height-1, (height/2)-1, -1):
mid_pixel_bottom_half = img[i][width/2]
R, G, B = mid_pixel_bottom_half[2], mid_pixel_bottom_half[1], mid_pixel_bottom_half[0]
if (R<border_threshold_R) and (G<border_threshold_G) and (B<border_threshold_B):
bottom_border_height+=1
else:
break
We now have a range in which a given image is dark black, but we still cannot tell if it is in a border or not. To solve this problem, random iteration is performed in a direction parallel to the image width, but at a distance less than top_border_height
and bottom_border_height
, and check if we can successfully iterate over a row with (R, G, B) pixel values less than the threshold (<4) ... For each successful iteration of the line, we increment the variable that shows the corrected border width.
Let's define a function that returns true only when the full string has RGB values less than the threshold.
def iterate_line(img, r_thresh, g_thresh, b_thresh, y):
"""
This function returns true only when a given row at a height "y"
from the origin(top - left) if fully black and false othrwise
"""
for i in img[y]:
if not((i[0]<b_thresh) and (i[1]<g_thresh) and i[2]<b_thresh):
return False
return True
And now iterate over the estimated border sizes to find the exact border sizes.
corrected_top_border_height = 0
corrected_bottom_border_height =0
for i in xrange(top_border_height):
if iterate_line(img, border_threshold_R, border_threshold_G, border_threshold_B, i):
corrected_top_border_height+=1
else:
break
for i in xrange(height-1, height-1-bottom_border_height, -1):
if iterate_line(img, border_threshold_R, border_threshold_G, border_threshold_B, i):
corrected_bottom_border_height+=1
else:
break
For this image, the corresponding values are:
top_border_height : 15
bottom_border_height : 15
corrected_top_border_height : 8
corrected_bottom_border_height : 8
The complete code might look like this:
import cv2
img = cv2.imread("sample_frame.jpg") #Loading an image in RGB mode.
def iterate_line(img, r_thresh, g_thresh, b_thresh, y):
"""
This function returns true only when a given row at a height "y"
from the origin(top - left) if fully black and false othrwise
"""
for i in img[y]:
if not((i[0]<b_thresh) and (i[1]<g_thresh) and i[2]<b_thresh):
return False
return True
height, width, channels = img.shape
print width, height
border_threshold_R = 4
border_threshold_G = 4
border_threshold_B = 4
top_border_height = 0
bottom_border_height = 0
for i in xrange(height/2):
mid_pixel_top_half = img[i][width/2]
R, G, B = mid_pixel_top_half[2], mid_pixel_top_half[1], mid_pixel_top_half[0]
if (R<border_threshold_R) and (G<border_threshold_G) and (B<border_threshold_B):
top_border_height+=1
else:
break
for i in xrange(height-1, (height/2)-1, -1):
mid_pixel_bottom_half = img[i][width/2]
R, G, B = mid_pixel_bottom_half[2], mid_pixel_bottom_half[1], mid_pixel_bottom_half[0]
if (R<border_threshold_R) and (G<border_threshold_G) and (B<border_threshold_B):
bottom_border_height+=1
else:
break
if (top_border_height>1) and (bottom_border_height>1):
corrected_top_border_height = 0
corrected_bottom_border_height =0
for i in xrange(top_border_height):
if iterate_line(img, border_threshold_R, border_threshold_G, border_threshold_B, i):
corrected_top_border_height+=1
else:
break
for i in xrange(height-1, height-1-bottom_border_height, -1):
if iterate_line(img, border_threshold_R, border_threshold_G, border_threshold_B, i):
corrected_bottom_border_height+=1
else:
break
if corrected_bottom_border_height>1 and corrected_top_border_height>1:
print "The frame has borders."
else:
print "The frame has no borders."
else:
print "The frame has no borders."
print top_border_height, bottom_border_height
print corrected_top_border_height, corrected_bottom_border_height
source to share