Crop black edges with OpenCV

后端 未结 7 1463
遥遥无期
遥遥无期 2020-12-01 01:08

I think it should be a very simple problem, but I cannot find a solution or an effective keyword for search.

I just have this image.

相关标签:
7条回答
  • 2020-12-01 01:36

    OK, so for completeness, I implemented each of the recommendations above, added an iterative version of the recursive algorithm (once corrected) and did a set of performance tests.

    TLDR: Recursive is probably the best for the average case (but use the one below--the OP has a couple bugs), and the autocrop is the best for images you expect to be almost empty.

    General findings: 1. The recursive algorithm above has a couple of off-by-1 bugs in it. Corrected version is below. 2. The cv2.findContours function has problems with non-rectangular images, and actually even trims some of the image off in various scenarios. I added a version which uses cv2.CHAIN_APPROX_NONE to see if it helps (it doesn't help). 3. The autocrop implementation is great for sparse images, but poor for dense ones, the inverse of the recursive/iterative algorithm.

    import numpy as np
    import cv2
    
    def trim_recursive(frame):
      if frame.shape[0] == 0:
        return np.zeros((0,0,3))
    
      # crop top
      if not np.sum(frame[0]):
        return trim_recursive(frame[1:])
      # crop bottom
      elif not np.sum(frame[-1]):
        return trim_recursive(frame[:-1])
      # crop left
      elif not np.sum(frame[:, 0]):
        return trim_recursive(frame[:, 1:])
        # crop right
      elif not np.sum(frame[:, -1]):
        return trim_recursive(frame[:, :-1])
      return frame
    
    def trim_contours(frame):
      gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
      _,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
      _, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
      if len(contours) == 0:
        return np.zeros((0,0,3))
      cnt = contours[0]
      x, y, w, h = cv2.boundingRect(cnt)
      crop = frame[y:y + h, x:x + w]
      return crop
    
    def trim_contours_exact(frame):
      gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
      _,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
      _, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
      if len(contours) == 0:
        return np.zeros((0,0,3))
      cnt = contours[0]
      x, y, w, h = cv2.boundingRect(cnt)
      crop = frame[y:y + h, x:x + w]
      return crop
    
    def trim_iterative(frame):
      for start_y in range(1, frame.shape[0]):
        if np.sum(frame[:start_y]) > 0:
          start_y -= 1
          break
      if start_y == frame.shape[0]:
        if len(frame.shape) == 2:
          return np.zeros((0,0))
        else:
          return np.zeros((0,0,0))
      for trim_bottom in range(1, frame.shape[0]):
        if np.sum(frame[-trim_bottom:]) > 0:
          break
    
      for start_x in range(1, frame.shape[1]):
        if np.sum(frame[:, :start_x]) > 0:
          start_x -= 1
          break
      for trim_right in range(1, frame.shape[1]):
        if np.sum(frame[:, -trim_right:]) > 0:
          break
    
      end_y = frame.shape[0] - trim_bottom + 1
      end_x = frame.shape[1] - trim_right + 1
    
      # print('iterative cropping x:{}, w:{}, y:{}, h:{}'.format(start_x, end_x - start_x, start_y, end_y - start_y))
      return frame[start_y:end_y, start_x:end_x]
    
    def autocrop(image, threshold=0):
      """Crops any edges below or equal to threshold
    
      Crops blank image to 1x1.
    
      Returns cropped image.
    
      """
      if len(image.shape) == 3:
        flatImage = np.max(image, 2)
      else:
        flatImage = image
      assert len(flatImage.shape) == 2
    
      rows = np.where(np.max(flatImage, 0) > threshold)[0]
      if rows.size:
        cols = np.where(np.max(flatImage, 1) > threshold)[0]
        image = image[cols[0]: cols[-1] + 1, rows[0]: rows[-1] + 1]
      else:
        image = image[:1, :1]
    
      return image
    

    Then to test it, I made this simple function:

    import datetime
    import numpy as np
    import random
    
    ITERATIONS = 10000
    
    def test_image(img):
      orig_shape = img.shape
      print ('original shape: {}'.format(orig_shape))
      start_time = datetime.datetime.now()
      for i in range(ITERATIONS):
        recursive_img = trim_recursive(img)
      print ('recursive shape: {}, took {} seconds'.format(recursive_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
      start_time = datetime.datetime.now()
      for i in range(ITERATIONS):
        contour_img = trim_contours(img)
      print ('contour shape: {}, took {} seconds'.format(contour_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
      start_time = datetime.datetime.now()
      for i in range(ITERATIONS):
        exact_contour_img = trim_contours(img)
      print ('exact contour shape: {}, took {} seconds'.format(exact_contour_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
      start_time = datetime.datetime.now()
      for i in range(ITERATIONS):
        iterative_img = trim_iterative(img)
      print ('iterative shape: {}, took {} seconds'.format(iterative_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
      start_time = datetime.datetime.now()
      for i in range(ITERATIONS):
        auto_img = autocrop(img)
      print ('autocrop shape: {}, took {} seconds'.format(auto_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
    
    
    def main():
      orig_shape = (10,10,3)
    
      print('Empty image--should be 0x0x3')
      zero_img = np.zeros(orig_shape, dtype='uint8')
      test_image(zero_img)
    
      print('Small image--should be 1x1x3')
      small_img = np.zeros(orig_shape, dtype='uint8')
      small_img[3,3] = 1
      test_image(small_img)
    
      print('Medium image--should be 3x7x3')
      med_img = np.zeros(orig_shape, dtype='uint8')
      med_img[5:8, 2:9] = 1
      test_image(med_img)
    
      print('Random image--should be full image: 100x100')
      lg_img = np.zeros((100,100,3), dtype='uint8')
      for y in range (100):
        for x in range(100):
          lg_img[y,x, 0] = random.randint(0,255)
          lg_img[y, x, 1] = random.randint(0, 255)
          lg_img[y, x, 2] = random.randint(0, 255)
      test_image(lg_img)
    
    main()
    

    ...AND THE RESULTS...

    Empty image--should be 0x0x3
    original shape: (10, 10, 3)
    recursive shape: (0, 0, 3), took 0.295851 seconds
    contour shape: (0, 0, 3), took 0.048656 seconds
    exact contour shape: (0, 0, 3), took 0.046273 seconds
    iterative shape: (0, 0, 3), took 1.742498 seconds
    autocrop shape: (1, 1, 3), took 0.093347 seconds
    Small image--should be 1x1x3
    original shape: (10, 10, 3)
    recursive shape: (1, 1, 3), took 1.342977 seconds
    contour shape: (0, 0, 3), took 0.048919 seconds
    exact contour shape: (0, 0, 3), took 0.04683 seconds
    iterative shape: (1, 1, 3), took 1.084258 seconds
    autocrop shape: (1, 1, 3), took 0.140886 seconds
    Medium image--should be 3x7x3
    original shape: (10, 10, 3)
    recursive shape: (3, 7, 3), took 0.610821 seconds
    contour shape: (0, 0, 3), took 0.047263 seconds
    exact contour shape: (0, 0, 3), took 0.046342 seconds
    iterative shape: (3, 7, 3), took 0.696778 seconds
    autocrop shape: (3, 7, 3), took 0.14493 seconds
    Random image--should be full image: 100x100
    original shape: (100, 100, 3)
    recursive shape: (100, 100, 3), took 0.131619 seconds
    contour shape: (98, 98, 3), took 0.285515 seconds
    exact contour shape: (98, 98, 3), took 0.288365 seconds
    iterative shape: (100, 100, 3), took 0.251708 seconds
    autocrop shape: (100, 100, 3), took 1.280476 seconds
    
    0 讨论(0)
提交回复
热议问题