如何有效地从 3d numpy 数组中删除行和列?

How do you efficiently remove rows and columns from a 3d numpy array?

我的目标是在将图像转换为 numpy 数组后从图像中删除黑色水平线和垂直线。我不想为此使用预定义的图像模块,因为我想对阈值等参数进行精细控制。

我的逻辑是这样的:

  1. 使用 cv2.imread.
  2. 将彩色图像转换为 3D numpy 数组 image (BGR)
  3. 迭代行索引并使用 row = image[row_index,:,:].
  4. 提取每一行
  5. 在每一行中,根据所有 3 个通道值是否都低于定义的阈值,计算有多少像素是“黑色像素”。
  6. 如果一行中有足够数量(或比率)的像素满足上述条件,则将此行索引存储到列表中remove_rows
  7. 所有迭代后,根据列表remove_rows.
  8. 确定要保留的行,存入preserve_rows
  9. 行删除后的新图像可以估计为image = image[preserve_rows,:,:]
  10. 也对列重复该过程。

程序成功了,但是需要很长时间。我认为时间复杂度是 O(rows * columns * 3) 因为必须访问每个值并与阈值进行比较。该程序对单个图像大约需要 9 秒,这是不可接受的,因为我最终计划在 ImageDataGenerator 函数中使用该函数在 Keras 中进行预处理,我不确定该函数是否在神经网络训练期间使用 GPU .完整代码如下:

def edge_removal(image, threshold=50, max_black_ratio=0.7):
    num_rows, _, _ = image.shape

    remove_rows = []
    threshold_times = []
    start_time = time.time()
    for row_index in range(num_rows):
        row = image[row_index,:,:]
        pixel_count = 0
        black_pixel_count = 0
        for pixel in row:
            pixel_count += 1
            b,g,r = pixel
            pre_threshold_time = time.time()
            if all([x<=threshold for x in [b,g,r]]):
                black_pixel_count += 1
            threshold_times.append(time.time()-pre_threshold_time)
            
            
        if pixel_count > 0 and (black_pixel_count/pixel_count)>max_black_ratio:
            remove_rows.append(row_index)
    
    
    time_taken = time.time() - start_time

    print(f"Time taken for thresholding = {sum(threshold_times)}")
    print(f"Time taken till row for loop = {time_taken}")
    preserve_rows = [x for x in range(num_rows) if x not in remove_rows]
    
    image = image[preserve_rows,:,:]
    
       
    _, num_cols, _ = image.shape

    remove_cols = []

    for col_index in range(num_cols):
        col = image[:,col_index,:]
        pixel_count = 0
        black_pixel_count = 0
        for pixel in col:
            pixel_count += 1
            b,g,r = pixel
            if all([x<=threshold for x in [b,g,r]]):
                black_pixel_count += 1
            
        if pixel_count > 0 and (black_pixel_count/pixel_count)>max_black_ratio:
            remove_cols.append(col_index)
    
    preserve_cols = [x for x in range(num_cols) if x not in remove_cols]
    
    image = image[:,preserve_cols,:]
    
    time_taken = time.time() - start_time
    print(f"Total time taken = {time_taken}")
    return image

而代码的输出是:

Time taken for thresholding = 3.586946487426758
Time taken till row for loop = 4.530229091644287
Total time taken = 8.74315094947815

我试过以下方法:

  1. 使用多线程替换外部for循环,其中线程函数的参数是threadnumber(线程数=图像中的行数)。但是,这并没有加快程序的速度。这可能是因为 for 循环是一个 CPU-bound 进程,由于 Global Interpreter Lock,无法加速,as described by this SO answer.
  2. 寻找其他建议如何降低程序的时间复杂度。 This answer 对我帮助不大,因为从输出中可以看出,瓶颈不是删除。执行阈值的比较次数是导致该程序变慢的原因。

有什么建议或启发式方法可以减少计算量从而缩短程序的处理时间吗?

由于您的代码由完成相同工作的两个部分组成,只是在图像的不同维度上,所以我将所有逻辑移动到一个函数中,该函数告诉您“像素系列”(行或列,无关紧要)提供的是高于或低于阈值。

我用 len 调用替换了所有手动计数。

各种生成器(r, g, b = pixel; x <= threshold for x in (r, g, b))被替换为直接numpy数组比较,如pixel <= threshold和python的all被numpy的替换.all().

旧代码和新代码分别在 5.9 秒和 37 毫秒内处理我的测试图像,增加了可读性。


def edge_removal(image, threshold=50, max_black_ratio=0.7):

    def pixels_should_be_conserved(pixels) -> bool:
        black_pixel_count = (pixels <= threshold).all(axis=1).sum()
        pixel_count = len(pixels)
        return pixel_count > 0 and black_pixel_count/pixel_count <= max_black_ratio

    num_rows, num_columns, _ = image.shape
    preserved_rows    = [r for r in range(num_rows)    if pixels_should_be_conserved(image[r, :, :])]
    preserved_columns = [c for c in range(num_columns) if pixels_should_be_conserved(image[:, c, :])]
    image = image[preserved_rows,:,:]
    image = image[:,preserved_columns,:]
    return image

为了进一步解释为我们节省最多时间的更改(计算黑色像素),让我们看一个简化的示例。

red = np.array([255, 0, 0])
black = np.array([0, 0, 0])
pixels = np.array([red, red, red, black, red]) # Simple line of 5 pixels.
threshold = 50

pixels <= threshold
# >>> array([[False,  True,  True],
#            [False,  True,  True],
#            [False,  True,  True],
#            [True,   True,  True],
#            [False,  True,  True]])

(pixels <= threshold).all(axis=1)
# >>> array([False,
#            False,
#            False,
#            True,
#            False])
# We successfully detect that the fourth pixel has all its rgb values below the threshold.

(pixels <= threshold).all(axis=1).sum()
# >>> 1
# Summing a boolean area is a handy way of counting how many element in the
# array are true, i.e. dark enough in our case.

备选方案 1:单纯疱疹病毒。

我们可以考虑的另一件事是使用 HSV 颜色系统,因为您只担心问题中的亮度。这将允许使用检查每个像素是否 s <= threshold,所以比较一次而不是三个。

尽管与 HSV 进行了两次转换,但图像的处理时间为 18 毫秒而不是 37 毫秒。

def edge_removal(image, threshold=50, max_black_ratio=0.7):

    def pixels_should_be_conserved(pixels) -> bool:
        black_pixel_count = (pixels[:,2] <= threshold).sum() # Notice the change here.
        pixel_count = len(pixels)
        return pixel_count > 0 and black_pixel_count/pixel_count <= max_black_ratio

    image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    num_rows, num_columns, _ = image.shape
    preserved_rows    = [r for r in range(num_rows)    if pixels_should_be_conserved(image[r, :, :])]
    preserved_columns = [c for c in range(num_columns) if pixels_should_be_conserved(image[:, c, :])]
    image = image[preserved_rows,:,:]
    image = image[:,preserved_columns,:]

    image = cv2.cvtColor(image, cv2.COLOR_HSV2BGR)
    return image

选项 2:灰度。

我们还可以在灰度模式下工作,并将“像素”(现在是单个值)直接与阈值进行比较。与 HSV 替代方案相比,我们节省了一次转换,但使用了更多的内存。

它在 14 毫秒内运行。

def edge_removal(image, threshold=50, max_black_ratio=0.7):

    def pixels_should_be_conserved(pixels) -> bool:
        black_pixel_count = (pixels <= threshold).sum()
        pixel_count = len(pixels)
        return pixel_count > 0 and black_pixel_count/pixel_count < max_black_ratio
    
    image_grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    num_rows, num_columns, _ = image.shape
    preserved_rows    = [r for r in range(num_rows)    if pixels_should_be_conserved(image_grayscale[r, :])]
    preserved_columns = [c for c in range(num_columns) if pixels_should_be_conserved(image_grayscale[:, c])]
    image = image[preserved_rows,:,:]
    image = image[:,preserved_columns,:]
    return image

基准测试:

RGB (OP) RGB HSV Gray
5900 ms 37 ms 18 ms 14 ms