0

I have a number of small images that are screen captures from a video. A couple of example images follow (with various typesof edges present):

first video cap second video cap

In short, I'm trying to crop the image to the closest part of the 'main' image, which is inside an (almost) uniformly 'black' border... or sometimes, there's a bit of a 'jittery' edge. You could think of it as going to the centre of the image and then radiate out until you hit a 'rectangular ('black' or 'nearly black') border'.

The biggest issue as near as I can see is to determine the location and dimensions of the 'cropping rectangle' around the image.. but so far, I haven't been able to get anywhere with doing that.

I've tried using 'cropdetect' filters in ffmpeg; there's not anything really helpful with Perl; ...and as I'm new to Python, I still haven't worked-out if there's any simple module that can do what I need. I have looked at 'scikit-image'... but was totally bamboozled by it, as I don't have a good enough knowledge of Python, let alone a sufficientlly 'technical' knowledge of image formats, colour depth, manipulation techniques, etc that would let me use 'scikit-image'.

I'd appreciate any suggestions on how to tackle this problem, even better if there was a simple way to do it. From my little bit of understanding of 'scikit-image', it seems like the 'Canny edge detect' or the 'prewitt_v'/'prewitt_h' filters might be relevant...?

I'm using Python 3.7.0 (and Active State Perl v5.20.2, if there's any way to use that), both running under Windows 8.1.

Thanks a lot for any forthcoming suggestions.

Skeeve
  • 159
  • 1
  • 2
  • 11
  • Are the borders always those exact same colours? Are the files all JPEG or are some PNG? Is the height of the very dark window title-bar and grey menu bar (with **File**, **Edit**, **View**) always the same? – Mark Setchell Mar 13 '20 at 14:38
  • The colours of the immediate 'border' area are not always 'black' but are always significantly darker than the 'body' of the image... and are of varying thickness when comparing captures. The images are always JPEG files... but it's no big deal to convert them to a PNG format, if that means they can be processed easier. The dimensions of the window 'frame' and pull-down menu are always the same... Thanks. – Skeeve Mar 13 '20 at 21:20

1 Answers1

0

I've made an attempt at solving this... but it's not very 'robust'. It uses the luminance values of the pixels when the image has been greyscaled:-

# ----
#  this will find the 'black'-ish portions and crop the image to these points
#  ozboomer, 25-Apr-2020 3-May-2020
#
# ---------

import pprint
import colorsys

from PIL import Image, ImageFilter

# ---- Define local functions (I *still* don't understand why I have to put these here)

def calculate_luminances(r, g, b):
   """Return luminance values of supplied RGB and greyscale of RGB"""

   lum = (0.2126 * r) + (0.7152 * g) + (0.0722 * b)    # luminance

   H, S, V = colorsys.rgb_to_hsv(r, g, b)              # HSV for the pixel RGB
   R, G, B = colorsys.hsv_to_rgb(H, 0, V)              # ...and greyscale RGB

   glum = (0.2126 * R) + (0.7152 * G) + (0.0722 * B)   # greyscale luminance

   return(lum, glum)

# end calculate_luminances

def radial_edge(radial_vector, ok_range):
   """Return the point in the radial where the luminance marks an 'edge'  """

   print("radial_edge: test range=", ok_range)

   edge_idx = -1
   i = 0

   for glum_value in radial_vector:
      print("  radial_vector: i=", i, "glum_value=", "%.2f" % round(glum_value, 2))

      if int(glum_value) in ok_range:
         print("  IN RANGE!  Return i=", i)
         edge_idx = i
         break

      i = i + 1      
   # endfor         

   return(edge_idx)

# ---- End local function definitions

# ---- Define some constants, variables, etc

#image_file = "cap.bmp"
#image_file = "cap2.png"
#image_file = "cap3.png"
image_file = "Sample.jpg"
#image_file = "cap4.jpg"
output_file = "Cropped.png";

edge_threshold = range(0, 70)  # luminance in this range = 'an edge'

#
# The image layout:-
#
# [0,0]----------+----------[W,0]
#   |            ^            |
#   |            |            |
#   |            R3           |
#   |            |            |
#   +<--- R1 ---[C]--- R2 --->+
#   |            |            |
#   |            R4           |
#   |            |            |
#   |            v            |
# [0,H]----------+----------[W,H]
#

# -------------------------------------
#  Main Routine
#

# ---- Get the image file ready for processing

try:
   im = Image.open(image_file)  # RGB.. mode

except:
   print("Unable to load image,", image_file)
   exit(1)

# Dammit, Perl, etc code is SO much less verbose:-
# open($fh, "<", $filename) || die("\nERROR: Can't open file, '$filename'\n$!\n");

print("Image - Format, size, mode: ", im.format, im.size, im.mode)

W, H = im.size         # The (width x height) of the image
XC = int(W / 2.0)      # Approx. centre of image
YC = int(H / 2.0)

print("Image Centre: (XC,YC)=", XC, ",", YC)

# --- Define the ordinate ranges for each radial

R1_range = range(XC, -1, -1)  # Actual range: XC->0 by -1 ... along YC ordinate
R2_range = range(XC,  W,  1)  #             : XC->W by +1 ... along YC ordinate
R3_range = range(YC, -1, -1)  #             : YC->0 by -1 ... along XC ordinate
R4_range = range(YC,  H,  1)  #             : YC->H by +1 ... along XC ordinate 

# ---- Check each radial for its 'edge' point

radial_luminance = []
for radial_num in range (1,5):  # We'll do the 4 midlines
   radial_luminance.clear()

   if radial_num == 1:
      print("Radial: R1")
      for x in R1_range:
         R, G, B = im.getpixel((x, YC))
         [lum, glum] = calculate_luminances(R, G, B)
         print("  CoOrd=(", x, ",", YC, ") RGB=", 
               (R, G, B), "lum=", "%.2f" % round(lum, 2), 
               "glum=", "%.2f" % round(glum, 2))         
         radial_luminance.append(glum)
      # end: get another radial pixel         

      left_margin = XC - radial_edge(radial_luminance, edge_threshold)

   elif radial_num == 2: 
      print("Radial: R2")
      for x in R2_range:
         R, G, B = im.getpixel((x, YC))
         [lum, glum] = calculate_luminances(R, G, B)
         print("  CoOrd=(", x, ",", YC, ") RGB=", 
               (R, G, B), "lum=", "%.2f" % round(lum, 2), 
               "glum=", "%.2f" % round(glum, 2))         
         radial_luminance.append(glum)  
      # end: get another radial pixel         

      right_margin = XC + radial_edge(radial_luminance, edge_threshold)

   elif radial_num == 3: 
      print("Radial: R3")
      for y in R3_range:
         R, G, B = im.getpixel((XC, y))
         [lum, glum] = calculate_luminances(R, G, B)
         print("  CoOrd=(", XC, ",", y, ") RGB=", 
               (R, G, B), "lum=", "%.2f" % round(lum, 2), 
               "glum=", "%.2f" % round(glum, 2))         
         radial_luminance.append(glum)  
      # end: get another radial pixel         

      top_margin = YC - radial_edge(radial_luminance, edge_threshold)

   elif radial_num == 4: 
      print("Radial: R4")
      for y in R4_range:
         R, G, B = im.getpixel((XC, y))
         [lum, glum] = calculate_luminances(R, G, B)
         print("  CoOrd=(", XC, ",", y, ") RGB=", 
               (R, G, B), "lum=", "%.2f" % round(lum, 2), 
               "glum=", "%.2f" % round(glum, 2))         
         radial_luminance.append(glum)  
      # end: get another radial pixel         

      bottom_margin = YC + radial_edge(radial_luminance, edge_threshold)

   # end: which radial we're processing      

im.close()
crop_items = (left_margin, top_margin, right_margin, bottom_margin)
print("crop_items:", crop_items)

# ---- Crop the original image and save it

im = Image.open(image_file)
im2 = im.crop(crop_items)      
im2.save(output_file, 'png')

exit(0)

# [eof]

I would expect the radial_edge() function would need to be modified to do something about checking the surrounding pixels to determine if we have a real edge... 'coz the current ok_range probably needs to be determined for each image, so there's no point to trying to automate the cropping using a script such as this.

Still looking for a robust and reliable way to attack this problem...

Skeeve
  • 159
  • 1
  • 2
  • 11