如何尽可能快地将像素与其周围的像素进行比较?

Posted

技术标签:

【中文标题】如何尽可能快地将像素与其周围的像素进行比较?【英文标题】:How to compare a pixel with its surounding as fast as possible? 【发布时间】:2021-12-21 05:47:38 【问题描述】:

我正在尝试找到一种快速算法,例如:

输入:Image (width w x height h)radius R

内容:对于每个像素 (x,y) 为

x in [R, w-R] y in [R, h-R]

在半径 R 和中心 (x,y) 的圆中找到最具代表性的颜色。 (如图)

输出:Image (w-2R x h-2R) 根据结果构建的图像

目前,我已经在 python 中实现了该算法的基本算法,其复杂度为O(n*R^2)(与n = w*h)。

我现在想知道是否可能存在复杂度 O(n) 的算法。对我来说这听起来可能,但我无法构建一个。

所以:

你认为这样的算法可能存在吗? 如果是这样,他将使用什么/他将如何工作? 否则,为什么? 如何加快算法的执行速度?(以算法的方式,即不考虑并行化)

编辑

我使用带有二维数组的图像表示。每个像素(即数组的单元格)是一个整数元组:红色、绿色、蓝色,介于 0 到 255 之间。 在运行此算法之前,我对图像进行“平整”:减少图像中不同颜色的数量(通过一种聚类颜色和接近度) 如果每个像素在周围都不同,那么它应该保持相同的颜色(现在通过赋予原始像素颜色更重要的“权重”来实现)

注意:我不确定“将像素与其周围进行比较”是描述我想要做什么的最佳方式,所以如果您有改进标题的想法,请在 cmets 中告诉我

【问题讨论】:

不确定,但您已经需要n,因为您肯定需要检查整个图像,但您还需要检查特定像素是否在进行两次检查的半径内,所以我无法完全想象O(n) 的算法 我可以想象一种方式,每个像素在被访问时都会“传播”它的信息,这样下一个像素就不需要实际检查它的值(有点像那个背包问题) .但我无法在脑海中建立一些可能有用的东西。 您如何准确地表示图像?您是否考虑过为此类任务寻找特定于图像的工具? 我没有想到,对不起。对于圆形过滤器,您通常将它们分解为行。对于均值滤波器,当您将圆圈移动到一个像素上时,每条线都有一个像素退出,并且有一个像素进入。因此,您可以通过减去左侧退出的值并添加右侧输入的值来更新圆上的平均值。 (1/2) 中值滤波器可以通过将内核下的所有值放在直方图中进行优化;当你移动内核时,你更新直方图的方式与你更新平均值的方式相同,删除和添加值到直方图。在你的情况下,你可能会做类似的事情。我建议您将过滤器应用于图像,其中每个像素都是集群 ID,而不是与该集群关联的颜色。这将使其他一切变得容易得多。 (2/2) 【参考方案1】:

基于Cris Luengo cmets,我通过以下方式改进了我的算法:

将数据结构的内容从 int 的元组替换为集群的 id 使用“移动内核”:从像素 (x,y) 到 (x+1, y),仅更新有关细胞离开和进入内核的内容 随着R 半径的增加,这种改进更加明显

所以,输入:imageradius

    用 id 替换颜色

    colors = 
    id_counter = 0
    raw = np.zeros((image.width, image.height))
    data = image.load()
    for x in range(image.width):
        for y in range(image.height):
            color = data[x,y]
            id = colors.get(color, -1)
            if id == -1:
                id = id_counter
                id_counter += 1
                colors[color] = id
            raw[x,y] = id
    

    构建 kerneldelta kernel。 Delta内核包含细胞离开和进入的相对位置,以及它们是离开还是进入

    kernel = []
    for dx in range(-radius, radius+1):
        for dy in range(-radius, radius+1):
            if dx*dx + dy*dy <= radius*radius:
                kernel.append((dx,dy))
    
    delta_kernel = []
    for dx in range(-radius, radius+1):
        mini = None
        for dy in range(-radius, radius+1):
            if dx*dx + dy*dy <= radius*radius:
                mini = dy - 1
                break
        delta_kernel.append((dx, mini, -1))
    
    for dx in range(-radius, radius+1):
        maxi = -9999
        for dy in range(-radius, radius+1):
            if dx*dx + dy*dy <= radius*radius:
                maxi = max(dy, maxi)
        delta_kernel .append((dx, maxi, 1))
    

    注意:这实际上是合并在一个循环中,但为了清楚起见,我将步骤分开

    实际算法

    counter =  # Map counting occurrences
    new_raw = np.zeros((raw.shape[0] - radius*2, raw.shape[1] - radius*2))
    
    direction = +1 # Y direction +/-1
    y = radius
    for x in range(radius, raw.shape[0]-radius):
        if x == radius: # First time: full kernel
            for (dx, dy) in kernel:
                key = raw[x+dx, y+dy]
                counter[key] = counter.get(key, 0) + 1
        else: # move to the right (x++): delta kernel horizontally
            for (dy, dx, sign) in delta_kernel:
                key = raw[x + dx, y + direction*dy]
                new_val = counter.get(key,0) + sign
                if new_val <= 0:
                    counter.pop(key) # Remove key to useless key values in the map
                else:
                    counter[key] = new_val
    
        for i in range(raw.shape[1]-2*radius):
            if i > 0:
                # y moves forward: delta radius (on y=0, x moved forward not y)
                for (dx, dy, sign) in delta_kernel:
                    key = raw[x + dx, y + direction*dy]
                    new_val = counter.get(key,0) + sign
                    if new_val <= 0:
                        counter.pop(key)
                    else:
                        counter[key] = new_val
    
            # Find most represented value
            winner_item = max(counter.items(), key=lambda i:i[1])
            if winner_item[1] == 1: # Every pixels are different: take the center one by default.
                winner_key = raw[x,y]
            else:
                winner_key = winner_item[0]
            new_raw[x-radius, y-radius] = winner_key
            y += direction
        y -= direction # Prevent y to go out from range
    
        direction *= -1
    

    重建图像

    reversed_color_map = 
    for key, value in colors.items():
        reversed_color_map[value] = key
    
    result = Image.new(mode=image.mode, size=(image.width-2*radius, image.height-2*radius))
    out_data = result.load()
    for x in range(raw.shape[0]):
        for y in range(raw.shape[1]):
            out_data[x,y] = reversed_color_map[raw[x,y]]
    

欢迎评论、评论、改进意见:)

【讨论】:

以上是关于如何尽可能快地将像素与其周围的像素进行比较?的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中尽可能快地将数组复制到结构数组

为啥我的 Android GridView 周围有多余的像素?

OpenGL - 将像素绘制到屏幕上?

如何从imageview中的png获取特定颜色的所有像素

图像处理检测方法 — ORB(Oriented FAST and Rotated BRIEF)

像素格式