2012-11-24 463 views
23

我认为这应该是一个非常简单的问题,但我无法找到解决方案或有效的关键字进行搜索。用OpenCV裁剪黑色边缘

我只有这张图片。所以,我要削减他们,只留下Windows图标(和蓝色背景)

the original image

黑边是无用的。

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

回答

31

我不确定你的所有图像是否都是这样的。但是对于这个图像,下面是一个简单的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) 

下面是结果:

enter image description here

+0

谢谢。你的意思是OpenCV没有提供建立的功能来削减边缘。 – Gqqnbig

+2

+1好的答案。是的,@LoveRight,这正是他的意思。处理这个问题的另一种方法是[在此讨论](http://stackoverflow.com/a/10317919/176769)。 – karlphillip

+0

只是想指出,如果它不能完全达到你想要的水平,你可以稍微调整一下门槛,我不得不将1提高到大约10.'''_,thresh = cv2.threshold(灰色, 10,255,cv2.THRESH_BINARY)''' – deweydb

7
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 
+1

为什么要删除颜色通道? '''flatImage = np.max(image,2)''' –

+0

由于使用了灰色阈值。像往常一样有多种合适的实现,这只是其中之一。 – fviktor

0

怎么样一个漂亮的小递归函数?

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

OK,所以对于完整性,我实现了各上面的建议,增加了递归算法的迭代版本(一旦纠正)并做了一组性能测试。

TLDR:递归可能是最好的平均情况下(但使用下面的 - OP有几个错误),autocrop是最好的图像,你期望几乎是空的。

一般发现: 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 
0

在情况下,它可以帮助任何人,我去这个调整@ 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 , :] 

(这是一个调整,原代码预计将裁剪掉白色背景而不是黑色背景。)