【问题标题】:How to crop or remove white background from an image如何从图像中裁剪或删除白色背景
【发布时间】:2018-07-01 21:38:41
【问题描述】:

我正在尝试使用 OpenCV 和 Python 比较图像。

考虑这些图像:




两者都有一双相同的鞋子,设置为白色背景。唯一的区别是第一个的背景比第二个高。

我想知道如何以编程方式裁剪两者的白色背景,这样我就只剩下那双鞋了。

我必须补充一点,我无法手动裁剪背景。

【问题讨论】:

  • 对图像设置阈值得到二值图像,使用 findContours() 找到最大轮廓并裁剪
  • @ZdaR ,我认为检测到的轮廓不会那么精确!
  • 物体似乎有一个清晰的边界,所以使用 findContours() 来查找和最大的轮廓,如果你选择最大的轮廓它应该足以解决所需的问题
  • @UbdusSamad 为什么不呢? OP 说“两者都有一双相同的鞋子”。轮廓的精度无关紧要。
  • 你自己试试吧,findcontours() 会打乱界限,我很确定,虽然对于这个特定的图像它可能会很好,但它不会工作每个图像。 (我可能是错的,但据我所知,我是这么说的,cv2 可能已经更新或什么的)

标签: python opencv python-imaging-library scikit-image


【解决方案1】:

This 链接非常适合我解决类似的问题,尽管它使用 PIL。请注意,它将生成一个矩形图像,由非白色的顶部/右侧/底部/最左侧像素限制。在您的情况下,它应该提供相同大小的相同图像。

我猜代码可以很容易地适应 OpenCV 函数。

【讨论】:

    【解决方案2】:

    您在评论中的要求:The shoes are on a white background. I would like to completely get rid of the border; as in be left with a rectangular box with either a white or a transparent background, having the length and width of the shoes in the picture.

    然后是我裁剪目标区域的步骤:

    1. 转换为灰度和阈值
    2. Morph-op 去除噪音
    3. 找到最大面积轮廓
    4. 裁剪并保存
    #!/usr/bin/python3
    # Created by Silencer @ Stackoverflow 
    # 2018.01.23 14:41:42 CST
    # 2018.01.23 18:17:42 CST
    import cv2
    import numpy as np
    
    ## (1) Convert to gray, and threshold
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    th, threshed = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
    
    ## (2) Morph-op to remove noise
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
    morphed = cv2.morphologyEx(threshed, cv2.MORPH_CLOSE, kernel)
    
    ## (3) Find the max-area contour
    cnts = cv2.findContours(morphed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
    cnt = sorted(cnts, key=cv2.contourArea)[-1]
    
    ## (4) Crop and save it
    x,y,w,h = cv2.boundingRect(cnt)
    dst = img[y:y+h, x:x+w]
    cv2.imwrite("001.png", dst)
    

    结果:

    【讨论】:

    • @TuhinSah 很高兴它有帮助,那么你欠我一个投票和接受。
    • 如何在 Java 中实现相同的功能?
    • 我收到一个错误gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) TypeError: Expected cv::UMat for argument 'src'
    • @kylefoley76 有同样的错误 - 使用 img = cv2.imread(file_path)
    • 这是一种非常简单、优雅且不错的方法。请注意,可能需要使用阈值,但原则上这可以用于各种用例。非常感谢!
    【解决方案3】:

    Kinght 的solution 运行良好。就我而言,我也有 CMYK 图像。当我裁剪它们时,我得到不正确的(鲜艳的颜色)输出。而且似乎 OpenCV 不支持 CMYK。所以我需要一种将 CMYK 图像转换为 RGB 的方法,然后用 OpenCV 打开它。我是这样处理的:

    import cv2
    import numpy
    
    from PIL import Image
    from PIL import ImageCms
    
    # force opening truncated/corrupt image files
    from PIL import ImageFile
    ImageFile.LOAD_TRUNCATED_IMAGES = True
    
    img = "shoes.jpg"
    
    img = Image.open(img)
    if img.mode == "CMYK":
        # color profiles can be found at C:\Program Files (x86)\Common Files\Adobe\Color\Profiles\Recommended
        img = ImageCms.profileToProfile(img, "USWebCoatedSWOP.icc", "sRGB_Color_Space_Profile.icm", outputMode="RGB")
    # PIL image -> OpenCV image; see https://stackoverflow.com/q/14134892/2202732
    img = cv2.cvtColor(numpy.array(img), cv2.COLOR_RGB2BGR)
    
    ## (1) Convert to gray, and threshold
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    th, threshed = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
    
    ## (2) Morph-op to remove noise
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
    morphed = cv2.morphologyEx(threshed, cv2.MORPH_CLOSE, kernel)
    
    ## (3) Find the max-area contour
    cnts = cv2.findContours(morphed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
    cnt = sorted(cnts, key=cv2.contourArea)[-1]
    
    ## (4) Crop and save it
    x,y,w,h = cv2.boundingRect(cnt)
    dst = img[y:y+h, x:x+w]
    
    # add border/padding around the cropped image
    # dst = cv2.copyMakeBorder(dst, 10, 10, 10, 10, cv2.BORDER_CONSTANT, value=[255,255,255])
    
    cv2.imshow("image", dst)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    # create/write to file
    # cv2.imwrite("001.png", dst)
    

    【讨论】:

      【解决方案4】:

      我在 github 上找到了这个。

      https://imagemagick.org/script/download.php

      import pgmagick
      
      def remove_background(image, background=None):
          """Returns a copy of `image` that only contains the parts that is distinct
             from the background. If background is None, returns parts that are
             distinct from white."""
          if background is None:
              background = pgmagick.Image(image.size(), 'white')
          elif isinstance(background, pgmagick.Image):
              blob = pgmagick.Blob()
              background.write(blob)
              background = pgmagick.Image(blob, image.size())
          else:
              background = pgmagick.Image(image.size(), background)
          background.composite(image, 0, 0, pgmagick.CompositeOperator.DifferenceCompositeOp)
          background.threshold(25)
          blob = pgmagick.Blob()
          image.write(blob)
          image = pgmagick.Image(blob, image.size())
          image.composite(background, 0, 0, pgmagick.CompositeOperator.CopyOpacityCompositeOp)
          return image
      

      【讨论】:

        【解决方案5】:

        虽然这个问题已经得到了彻底的解答,但我想分享一个仅依赖于 numpy 的简单版本:

        import numpy as np
        
        def remove_background(image, bg_color=255):
            # assumes rgb image (w, h, c)
            intensity_img = np.mean(image, axis=2)
        
            # identify indices of non-background rows and columns, then look for min/max indices
            non_bg_rows = np.nonzero(np.mean(intensity_img, axis=1) != bg_color)
            non_bg_cols = np.nonzero(np.mean(intensity_img, axis=0) != bg_color)
            r1, r2 = np.min(non_bg_rows), np.max(non_bg_rows)
            c1, c2 = np.min(non_bg_cols), np.max(non_bg_cols)
        
            # return cropped image
            return image[r1:r2+1, c1:c2+1, :]
        

        【讨论】:

          【解决方案6】:

          通过 PIL,您可以将白色背景转换为透明:

          from PIL import Image
            
          def convertImage():
              img = Image.open("hi.png")
              img = img.convert("RGBA")
            
              datas = img.getdata()
            
              newData = []
            
              for item in datas:
                  if item[0] == 255 and item[1] == 255 and item[2] == 255:
                      newData.append((255, 255, 255, 0))
                  else:
                      newData.append(item)
            
              img.putdata(newData)
              img.save("./New.png", "PNG")
              print("Successful")
            
          convertImage()
          

          这是输出示例:

          【讨论】: