【问题标题】:Crop black edges with OpenCV使用 OpenCV 裁剪黑色边缘
【发布时间】:2012-11-12 09:20:23
【问题描述】:

我认为这应该是一个很简单的问题,但是我找不到解决方案或搜索的有效关键字。

我只有这张图片。

黑色边缘没用,所以我想剪掉它们,只留下 Windows 图标(和蓝色背景)。

我不想计算 Windows 图标的坐标和大小。 GIMP 和 Photoshop 具有某种自动裁剪功能。 OpenCV 没有?

【问题讨论】:

    标签: image-processing opencv


    【解决方案1】:

    我不确定你的所有图片是否都是这样的。但是对于这张图片,下面是一个简单的python-opencv代码来裁剪它。

    首先导入库:

    import cv2
    import numpy as np
    

    读取图像,将其转换为灰度,并以阈值为1的方式制作二值图像。

    img = cv2.imread('sofwin.png')
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    _,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
    

    现在在其中找到轮廓。将只有一个对象,因此请为其找到边界矩形。

    contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    cnt = contours[0]
    x,y,w,h = cv2.boundingRect(cnt)
    

    现在裁剪图像,并将其保存到另一个文件中。

    crop = img[y:y+h,x:x+w]
    cv2.imwrite('sofwinres.png',crop)
    

    结果如下:

    【讨论】:

    • 谢谢。你的意思是OpenCV没有提供一个既定的功能来切割边缘。
    • +1 不错的答案。是的,@LoveRight,这正是他的意思。解决此问题的另一种方法是discussed here
    • 只是想指出,如果它不能完全满足您的要求,您可以稍微调整一下阈值,我不得不将 1 提高到大约 10。_,thresh = cv2.threshold(gray,10,255,cv2.THRESH_BINARY)
    • @Abid,非常感谢,先生。它对我有用,底部也只有一个黑色边缘。对于OpenCV 3,代码会有细微的变化:contours,hierarchy,_ = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE):)
    • 说实话 numpy 作为图像库比 opencv 好得多,但是有点难以理解如何才能达到预期的效果
    【解决方案2】:
    import numpy as np
    
    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
    

    【讨论】:

    • 为什么要去掉颜色通道? flatImage = np.max(image, 2)
    • 由于使用了灰度阈值。像往常一样有多种合适的实现,这只是其中之一。
    【解决方案3】:

    一个精巧的小递归函数怎么样?

    import cv2
    import numpy as np
    def trim(frame):
        #crop top
        if not np.sum(frame[0]):
            return trim(frame[1:])
        #crop bottom
        elif not np.sum(frame[-1]):
            return trim(frame[:-2])
        #crop left
        elif not np.sum(frame[:,0]):
            return trim(frame[:,1:]) 
        #crop right
        elif not np.sum(frame[:,-1]):
            return trim(frame[:,:-2])    
        return frame
    

    加载图像并设置阈值以确保暗区为黑色:

    img = cv2.imread("path_to_image.png")   
    thold = (img>120)*img
    

    然后调用递归函数

    trimmedImage = trim(thold)
    

    【讨论】:

      【解决方案4】:

      好的,为了完整起见,我实现了上面的每个建议,添加了递归算法的迭代版本(一旦更正)并进行了一组性能测试。

      TLDR:对于一般情况,递归可能是最好的(但请使用下面的那个——OP 有几个错误),而自动裁剪最适合您希望几乎为空的图像。

      一般发现: 1. 上面的递归算法有几个 off-by-1 错误。修正版如下。 2. cv2.findContours 函数对非矩形图像有问题,实际上在各种场景中甚至会修剪掉一些图像。我添加了一个使用 cv2.CHAIN_APPROX_NONE 的版本来查看它是否有帮助(它没有帮助)。 3. autocrop 实现非常适合稀疏图像,但不适用于密集图像,这是递归/迭代算法的逆算法。

      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
      

      然后为了测试,我做了这个简单的函数:

      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()
      

      ...以及结果...

      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
      

      【讨论】:

        【解决方案5】:

        如果它对任何人有帮助,我对 @wordsforthewise 的 replacement 进行了调整,以获得基于 PIL 的解决方案:

        bw = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        rows, cols = bw.shape
        
        non_empty_columns = np.where(bw.max(axis=0) > 0)[0]
        non_empty_rows = np.where(bw.max(axis=1) > 0)[0]
        cropBox = (min(non_empty_rows) * (1 - padding),
                    min(max(non_empty_rows) * (1 + padding), rows),
                    min(non_empty_columns) * (1 - padding),
                    min(max(non_empty_columns) * (1 + padding), cols))
        
        return img[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
        

        (这是一个调整,因为原始代码希望裁剪掉白色背景而不是黑色背景。)

        【讨论】:

          【解决方案6】:

          Python 3.6 版


          裁剪图像并插入“CropedImages”文件夹

          import cv2
          import os
          
          arr = os.listdir('./OriginalImages')
          
          for itr in arr:
              img = cv2.imread(itr)
              gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
              _,thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
              _, contours, _ = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
              cnt = contours[0]
              x,y,w,h = cv2.boundingRect(cnt)
              crop = img[y:y+h,x:x+w]
              cv2.imwrite('CropedImages/'+itr,crop)
          

          在第 9 行将数字 120 更改为其他并尝试使用您的图像,它会起作用

          【讨论】:

            【解决方案7】:

            我认为这个答案更简洁:

            def crop(image):
                y_nonzero, x_nonzero, _ = np.nonzero(image)
                return image[np.min(y_nonzero):np.max(y_nonzero), np.min(x_nonzero):np.max(x_nonzero)]
            

            【讨论】:

            • 不确定您的图像使用的是什么库,但对于使用 PIL 的任何人,最后一行都可以更改如下:return image.crop((np.min(x_nonzero), np.min(y_nonzero), np.max(x_nonzero), np.max(y_nonzero))) -- 感谢您提供简洁的解决方案!
            • 如果边距在某些地方不是完全黑色而是深灰色(例如 jpg 图像靠近从图像到边距的过渡),您可以使用阈值 th 比如说 20 和 @987654324 @
            • 完美满足我的需要,比其他提交的方法更可靠
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-09-21
            • 2020-03-20
            • 1970-01-01
            • 1970-01-01
            • 2021-12-24
            • 2013-05-18
            • 1970-01-01
            相关资源
            最近更新 更多