递归实现图像连通像素识别时触发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))
额外优化建议
- 减少对象开销:对于大图像,
Pixel对象会增加内存负担,可以直接用元组(x, y, color)代替,甚至只存储坐标(颜色可通过图像直接获取)。 - 遍历所有连通区域:原代码仅处理了从(0,0)开始的区域,可扩展为遍历整个图像的未检查像素,提取所有连通区域。
内容的提问来源于stack exchange,提问作者Moa

