You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

递归实现图像连通像素识别时触发SIGSEGV错误的排查求助

问题描述

我尝试识别图像中所有相邻的同色像素,为此使用递归函数:检查单个像素时,判断其上下左右相邻像素颜色是否相近(通过COLOR_TOLERANCE阈值判定),若相近则加入数组。

代码如下:

import tkinter as tk
from PIL import Image

import sys
sys.setrecursionlimit(200000)

## WINDOWS
# to launch in debug mode
imgToDraw = Image.open('assets-test\\smile-face.png')
# to launch normaly
# imgToDraw = Image.open('..\\assets-test\\smile-face.png')
## LINUX
# imgToDraw = Image.open('../assets-test/smile-face.png')

imgPixels = imgToDraw.load()

imgWidth = imgToDraw.size[0]
imgHeight = imgToDraw.size[1]

# an element is a part of the image, it's a bunch of pixels with approximately the same color
# and each pixel touch at least one other pixel of the same element
elements = [];

isPixelChecked = [[ False for y in range( imgWidth ) ] for x in range( imgHeight )]

# min tolerable difference between two colors to consider them the same
# the higher the value is the more colors will be considered the same
COLOR_TOLERANCE = 10

reccursionCount = 0

class Element:
    def __init__(self, color):
        self.pixels = [];
        self.color = color;
    
    def addPixel(self, pixel):
        self.pixels.append(pixel);

class Pixel:
  def __init__(self, x, y, color):
    self.x = x # x position of the pixel
    self.y = y # y position of the pixel
    self.color = color # color is a tuple (r,g,b)

def cutImageInElements():
    global element
    completeElement(element.pixels)

def completeElement(elemPixels):
    global reccursionCount
    global isPixelChecked
    reccursionCount += 1

    nbPixels = len(elemPixels);
    xIndex = elemPixels[nbPixels - 1].x
    yIndex = elemPixels[nbPixels - 1].y
    xRightIdx = elemPixels[nbPixels - 1].x + 1
    xLeftIdx = elemPixels[nbPixels - 1].x - 1
    yBottomIdx = elemPixels[nbPixels - 1].y + 1
    yTopIdx = elemPixels[nbPixels - 1].y - 1

    isPixelChecked[xIndex][yIndex] = True
    if((xRightIdx < imgWidth) and isPixelChecked[xRightIdx][yIndex] == False):
        if(isColorAlmostSame(imgPixels[elemPixels[0].x, elemPixels[0].y], imgPixels[xRightIdx, yIndex])):
            pixelAppended = Pixel(xRightIdx, yIndex, imgPixels[xRightIdx, yIndex])
            elemPixels.append(pixelAppended)
            
            completeElement(elemPixels)
    
    if((xLeftIdx >= 0) and isPixelChecked[xLeftIdx][yIndex] == False):
        if(isColorAlmostSame(imgPixels[elemPixels[0].x, elemPixels[0].y], imgPixels[xLeftIdx, yIndex])):
            pixelAppended = Pixel(xLeftIdx, yIndex, imgPixels[xLeftIdx, yIndex])
            elemPixels.append(pixelAppended)

            completeElement(elemPixels)

    if((yBottomIdx < imgHeight) and isPixelChecked[xIndex][yBottomIdx] == False):
        if(isColorAlmostSame(imgPixels[elemPixels[0].x, elemPixels[0].y], imgPixels[xIndex, yBottomIdx])):
            pixelAppended = Pixel(xIndex, yBottomIdx, imgPixels[xIndex, yBottomIdx])
            elemPixels.append(pixelAppended)

            completeElement(elemPixels)
    
    if((yTopIdx >= 0) and isPixelChecked[xIndex][yTopIdx] == False):
        if(isColorAlmostSame(imgPixels[elemPixels[0].x, elemPixels[0].y], imgPixels[xIndex, yTopIdx])):
            pixelAppended = Pixel(xIndex, yTopIdx, imgPixels[xIndex, yTopIdx])
            elemPixels.append(pixelAppended)

            completeElement(elemPixels)
    

def isColorAlmostSame(pixel1, pixel2):
    redDiff = abs(pixel1[0] - pixel2[0])
    greenDiff = abs(pixel1[1] - pixel2[1])
    blueDiff = abs(pixel1[2] - pixel2[2])
    if(redDiff < COLOR_TOLERANCE and greenDiff < COLOR_TOLERANCE and blueDiff < COLOR_TOLERANCE):
        return True
    else:
        return False

def printPixelsArr(pixelsArr):
    for x in range(0, len(pixelsArr)):
        print(pixelsArr[x].x, pixelsArr[x].y, pixelsArr[x].color)
        
if __name__ == '__main__':
    pixel = Pixel(0, 0, imgPixels[0, 0]);
    element = Element(pixel.color);
    element.addPixel(pixel);
    cutImageInElements();
    print("NbReccursive call: ", reccursionCount)

这段代码处理100x100的小图像时正常,但处理400x400图像时,在WSL2环境下触发错误:terminated by signal SIGSEGV (Address boundary error);在Windows的cmd或PowerShell中直接崩溃,无错误提示。我无法理解为何图像大小会影响运行,怀疑内存不足,但任务管理器显示程序内存占用极低,特此求助。


问题分析与解决方案

核心问题1:递归栈溢出

你设置了sys.setrecursionlimit(200000),但Python的递归深度实际受操作系统线程栈大小限制,并非完全由该参数控制。400x400图像的连通区域可能包含上万像素,递归调用深度会远超Windows(默认1MB栈空间)或WSL2下Linux的线程栈上限,最终触发栈溢出错误(SIGSEGV是栈溢出的典型表现)。

核心问题2:数组索引越界

原代码中isPixelChecked的维度定义错误:

isPixelChecked = [[ False for y in range( imgWidth ) ] for x in range( imgHeight )]

该数组的外层长度是imgHeight(对应图像的行/纵坐标y),内层长度是imgWidth(对应图像的列/横坐标x),但代码中却用isPixelChecked[xIndex][yIndex]访问——xIndex是像素的横坐标(范围0~imgWidth-1),当xIndex >= imgHeight时,就会触发数组越界,这也是导致SIGSEGV的直接原因之一。

解决方案:用迭代替代递归 + 修复索引错误

改用迭代式的深度优先搜索(DFS)或广度优先搜索(BFS)彻底避免递归栈问题,同时修复数组索引错误:

import tkinter as tk
from PIL import Image
import sys

## WINDOWS
# imgToDraw = Image.open('assets-test\\smile-face.png')
# imgToDraw = Image.open('..\\assets-test\\smile-face.png')
## LINUX
# imgToDraw = Image.open('../assets-test/smile-face.png')

imgPixels = imgToDraw.load()
imgWidth = imgToDraw.size[0]
imgHeight = imgToDraw.size[1]

elements = []
# 修复索引:外层是y(行),内层是x(列)
isPixelChecked = [[False for _ in range(imgWidth)] for _ in range(imgHeight)]
COLOR_TOLERANCE = 10

class Element:
    def __init__(self, color):
        self.pixels = []
        self.color = color
    
    def addPixel(self, pixel):
        self.pixels.append(pixel)

class Pixel:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color

def isColorAlmostSame(pixel1, pixel2):
    redDiff = abs(pixel1[0] - pixel2[0])
    greenDiff = abs(pixel1[1] - pixel2[1])
    blueDiff = abs(pixel1[2] - pixel2[2])
    return redDiff < COLOR_TOLERANCE and greenDiff < COLOR_TOLERANCE and blueDiff < COLOR_TOLERANCE

def cutImageInElements():
    global element
    # 用栈实现迭代式DFS
    stack = element.pixels.copy()
    while stack:
        pixel = stack.pop()
        x, y = pixel.x, pixel.y
        # 修复索引访问:y对应行,x对应列
        if isPixelChecked[y][x]:
            continue
        isPixelChecked[y][x] = True
        
        # 遍历四个相邻方向
        for dx, dy in [(1,0), (-1,0), (0,1), (0,-1)]:
            nx = x + dx
            ny = y + dy
            # 边界检查 + 未检查标记 + 颜色匹配
            if 0 <= nx < imgWidth and 0 <= ny < imgHeight and not isPixelChecked[ny][nx]:
                if isColorAlmostSame(element.color, imgPixels[nx, ny]):
                    new_pixel = Pixel(nx, ny, imgPixels[nx, ny])
                    element.addPixel(new_pixel)
                    stack.append(new_pixel)

if __name__ == '__main__':
    pixel = Pixel(0, 0, imgPixels[0, 0])
    element = Element(pixel.color)
    element.addPixel(pixel)
    cutImageInElements()
    print("Total pixels in element:", len(element.pixels))

额外优化建议

  1. 减少对象开销:对于大图像,Pixel对象会增加内存负担,可以直接用元组(x, y, color)代替,甚至只存储坐标(颜色可通过图像直接获取)。
  2. 遍历所有连通区域:原代码仅处理了从(0,0)开始的区域,可扩展为遍历整个图像的未检查像素,提取所有连通区域。

内容的提问来源于stack exchange,提问作者Moa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 04:06:13