【问题标题】:How can I crop an object from surrounding white background in python numpy?python - 如何在python numpy中从周围的白色背景中裁剪对象?
【发布时间】:2021-01-10 18:07:58
【问题描述】:

我有一个图像数据集,都是这样的。

任务是尽可能裁剪图像周围的空白区域,并返回包含较少白色周围图像的图像:

def crop_object(img):
    lst = []
    # hold min and max of height
    for i in range(img.shape[0]):
        r = img[:,i,0]
        g = img[:,i,1]
        b = img[:,i,2]

        if (np.min(r) != 255) or (np.min(g) != 255) or (np.min(b) != 255):
            lst.append(i)
    a1 = min(lst)
    a2 = max(lst)

    for i in range(img.shape[1]):
        r = img[i,:,0]
        g = img[i,:,1]
        b = img[i,:,2]

        if (np.min(r) != 255) or (np.min(g) != 255) or (np.min(b) != 255):
            lst.append(i)
    a3 = min(lst)
    a4 = max(lst)

    return img [a3:a4, a1:a2, :]

我想要一种更 Pythonic 的方式来处理这个问题。比如更少的代码和更快的运行。

你们能帮帮我吗?

【问题讨论】:

  • 请注意,您应该使用 img [a3:a4+1, a1:a2+1, :] 而不是使用完整范围。

标签: python image numpy opencv optimization


【解决方案1】:

Crop black border of image using NumPy启发,这里有两种裁剪方式-

# I. Crop to remove all black rows and columns across entire image
def crop_image(img):
    mask = img!=255
    mask = mask.any(2)
    mask0,mask1 = mask.any(0),mask.any(1)
    return img[np.ix_(mask1,mask0)]

# II. Crop while keeping the inner all black rows or columns
def crop_image_v2(img):
    mask = img!=255
    mask = mask.any(2)
    mask0,mask1 = mask.any(0),mask.any(1)
    colstart, colend = mask0.argmax(), len(mask0)-mask0[::-1].argmax()+1
    rowstart, rowend = mask1.argmax(), len(mask1)-mask1[::-1].argmax()+1
    return img[rowstart:rowend, colstart:colend]

使用容差

正如链接的帖子中提到的,我们可能想要使用一些容忍度。同样,掩码创建步骤将修改为 -

tol = 255 # tolerance value
mask = img<tol

时间安排 -

# Read in given image
In [119]: img = cv2.imread('9Aplg.jpg')

# With original soln
In [120]: %timeit crop_object(img)
5.46 ms ± 401 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [121]: %timeit crop_image(img)
923 µs ± 4.96 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [122]: %timeit crop_image_v2(img)
672 µs ± 53.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

【讨论】:

  • 正是我所需要的。如此简单,如此干净和速度性能比较使其完美。
【解决方案2】:

这是一种类似的方式,但在我的 Python 代码中使用了更多的 OpenCV。我的 Mac Mini 上的三个运行时间显示在底部。我注意到你的图像是 JPG,所以白色不是纯白色,特别是在物体附近,由于 JPG 压缩。所以我使用 cv2.inRange() 进行颜色阈值处理。或者,可以转换为灰度,然后在 220 处做一个简单的阈值。然而,我的时间是相似的,但稍微长一点。

import cv2
import numpy as np
import time

start = time.time()

# load image
img = cv2.imread("object2crop.jpg")

# get color bounds of white background
lower =(220,220,220) # lower bound for each channel
upper = (255,255,255) # upper bound for each channel

# create the mask
mask = cv2.inRange(img, lower, upper)

# get bounds of black pixels
black = np.where(mask==0)
xmin, ymin, xmax, ymax = np.min(black[1]), np.min(black[0]), np.max(black[1]), np.max(black[0])
print(xmin,xmax,ymin,ymax)

# crop the image at the bounds
crop = img[ymin:ymax, xmin:xmax]

# write result to disk
cv2.imwrite("object2crop_cropped.jpg", crop)

end = time.time()
elapsed_time = end - start
print("time:",elapsed_time)

# display it
cv2.imshow("mask", mask)
cv2.imshow("crop", crop)
cv2.waitKey(0)

# time: 0.0021338462829589844
# time: 0.002237081527709961
# time: 0.0021467208862304688

【讨论】:

    【解决方案3】:

    这个方法比我的第一个方法稍微快一点。它在 Python 中使用了更多的 OpenCV。在这种方法中,我得到阈值后的最大轮廓,然后是它的边界框。如果背景不是 JPG 压缩的,则不需要找到最大的轮廓,因为阈值处理后留下的无关像素将不存在。所以只有一个外部轮廓。

    import cv2
    import numpy as np
    import time
    
    start = time.time()
    
    # load image
    img = cv2.imread("object2crop.jpg")
    
    # get color bounds of white background
    lower =(220,220,220) # lower bound for each channel
    upper = (255,255,255) # upper bound for each channel
    
    # create the mask
    mask = cv2.inRange(img, lower, upper)
    mask = cv2.bitwise_not(mask)
    
    # get the largest contour
    contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if len(contours) == 2 else contours[1]
    big_contour = max(contours, key=cv2.contourArea)
    
    # get bounding box
    x,y,w,h = cv2.boundingRect(big_contour)
    
    # crop the image at the bounds
    crop = img[y:y+h, x:x+w]
    
    # write result to disk
    cv2.imwrite("object2crop_cropped3.jpg", crop)
    
    end = time.time()
    elapsed_time = end - start
    print("time:",elapsed_time)
    
    # display it
    cv2.imshow("mask", mask)
    cv2.imshow("crop", crop)
    cv2.waitKey(0)
    
    time: 0.002028942108154297
    time: 0.0019147396087646484
    time: 0.0021567344665527344
    

    【讨论】:

      【解决方案4】:

      我们可以使用我几天前写的 splitImageAtXvalues() 函数。它将一个图像和 n 个 x 值作为输入并返回 n+1 个子图像。例如,如果 xvals=[20] 并且您的图像宽度为 40 像素,它会返回图像的两个子集,一个从 x=0 到 x=20,另一个从 x=21 到 x=40。因此,对于您的情况,我们只需找到非白色像素从左侧 (x1) 和右侧 (x2) 开始的 x 值,然后返回 splitImageAtXvalues 返回的中间图像。 我将阈值作为参数包含在内,因为在您的情况下,图像内容周围有一些不是纯白色的像素。

      def splitImageAtXvalues(img, xvals):
          subimages = []
          xvals = [0] + xvals + [img.shape[0]]
          for j in range(len(xvals)):
              if j == len(xvals)-1:
                  break
              subimg= img[:, xvals[j]:xvals[j+1]]
              subimages.append(subimg)
          return subimages
      
      def crop_object_by_whitespace(img, threshold):
          x1 = None
          x2 = None
          img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # convert to grayscale
          img_b = cv2.threshold(img_gray,threshold,255,cv2.THRESH_BINARY)[1] # convert to binary
          # Loop from left:
          for i in range(len(img_b)):
              column = img_b[:,i]
              uniqueValues = np.unique(column) # if there are other pixels than 255
              if len(uniqueValues) > 1:
                  x1 = i
                  break
          # Loop from right:
          for i in range(len(img_b),-1,-1):
              column = img_b[:,i-1]
              uniqueValues = np.unique(column) # if there are other pixels than 255
              if len(uniqueValues) > 1:
                  x2 = i
                  break
          return splitImageAtXvalues(img, [x1, x2])[1]   
      
      crop_object_by_whitespace(img, 240) # 240 seems to fit good for your image
      

      阈值 = 240 的结果

      阈值 = 254 的结果

      【讨论】:

      • 好主意。但我真正需要的是拥有更多的pythonic,当然还有更短的代码,但这个答案比我原来的方法要长。但是我从答案中学到了很多东西。谢谢
      猜你喜欢
      • 2023-01-27
      • 2018-07-01
      • 2015-03-17
      • 1970-01-01
      • 2012-12-15
      • 2015-06-05
      • 2019-03-10
      • 2019-07-19
      • 2018-03-01
      相关资源
      最近更新 更多