【python】OpenCV—Blur, Threshold, Gradient, Morphology(2)

本文深入探讨OpenCV在Python中的应用,涵盖图像滤波、阈值化、梯度计算及形态学变换等核心功能。从低通和高通滤波的实践到不同阈值化方法的对比,再到Sobel、Laplacian边缘检测和形态学运算的演示,详尽解析图像处理的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系列文章



1、import

import cv2
import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline

2、滤波

学习整理 Python下opencv使用笔记(五)(图像的平滑与滤波)

对于2D图像可以进行低通或者高通滤波操作,低通滤波(LPF)有利于去噪,模糊图像,高通滤波(HPF)有利于找到图像边界。

2.1、统一的滤波器

cv2.filter2D

2.2、模糊(Blurring)

  • 均值模糊(Averaging blurring):cv2.blur
  • 高斯模糊(Gaussian blurring):cv2.GaussianBlur
  • 中值模糊(median blurring):cv2.medianBlur
  • 双边滤波(bilateral filtering):cv2.bilateralFilter,是高斯模糊的一个高级版本。模糊化不仅可以溶解噪声,而且还会平滑边缘。而双边滤波器能在去除噪声的同时保持边缘锐化。这是由于它不仅使用高斯分布值,还同时考虑了距离和像素值的差异。因此,需要指定 sigmaSpace 和 sigmaColor 这两个参数。
img = cv2.imread('C://Users/13663//Desktop/1.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Plot the image with different kernel sizes
kernels = [3, 5, 11, 17]
fig, axs = plt.subplots(nrows = 1, ncols = 4, figsize = (20, 20))
for ind, s in enumerate(kernels):
    img_blurred = cv2.blur(img, ksize = (s, s))
    ax = axs[ind]
    ax.set_title('kernel {i}'.format(i=s),
             fontsize =24, color = 'white')
    ax.imshow(img_blurred)
    ax.axis('off')
plt.show()

在这里插入图片描述
逐渐模糊,一字排开这个看起来并不是很爽,2,2 看看

img = cv2.imread('C://Users/13663//Desktop/1.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# Plot the image with different kernel sizes
kernels = [3, 5, 11, 17]
fig, axs = plt.subplots(nrows = 2, ncols = 2, figsize = (15, 9))
for ind, s in enumerate(kernels):
    img_blurred = cv2.blur(img, ksize = (s, s))
    ax = axs[ind//2][ind%2]
    ax.set_title('kernel {i}'.format(i=s),
             fontsize =24, color = 'white')
    ax.imshow(img_blurred)
    ax.axis('off')
plt.show()

在这里插入图片描述
看看其它类型的 blurring

img = cv2.imread('C://Users/13663//Desktop/1.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# Blur the image 
img_0 = cv2.blur(img, ksize = (7, 7))
img_1 = cv2.GaussianBlur(img, ksize = (7, 7), sigmaX = 0)   
img_2 = cv2.medianBlur(img, 7)
img_3 = cv2.bilateralFilter(img, 7, sigmaSpace = 75, sigmaColor =75)

# Plot the images
images = [img_0, img_1, img_2, img_3]
names = ['Mean Blur','Gaussian Blur','Median Blur','Bilateral Filter']
fig, axs = plt.subplots(nrows = 2, ncols = 2, figsize = (15, 9))
num = 0
for ind, p in enumerate(images):
    ax = axs[ind//2][ind%2]
    ax.set_title('{x}'.format(x=names[num]),
             fontsize =20, color = 'white')
    ax.imshow(p)
    ax.axis('off')
    num+=1
plt.show()

在这里插入图片描述


毛玻璃模糊,即遍历每一个像素,随机选取这个像素周围的某一个像素,替换当前像素。

#coding:utf-8
import cv2
import numpy as np
import random

img = cv2.imread('1.jpg', 1)

h, w, _ = img.shape
dst = np.zeros((h, w, 3), np.uint8)
mm = 8
for m in range(h-mm):
    for n in range(w-mm):
        index = int(random.random()*mm)
        dst[m, n] = img[m+index, n+index]
cv2.imwrite("result.jpg", dst)

整个过程即选择周围像素的范围距离最大为8像素,运行以上代码前后如下图:

输入图片

在这里插入图片描述

输出图片

在这里插入图片描述

来自:一个简单方法识别毛玻璃、高斯模糊(2021年09月28日)

3、阈值化(Thresholding)

3.1、固定的 Threshold

图像的阈值化就是利用图像像素点分布规律,设定阈值进行像素点分割,进而得到图像的二值图像。我们需要设置阈值和最大值,然后据此相应地进行像素值转换。核心函数如下:

ret, dst = cv2.threshold(src, thresh, maxval, type)

  • src: 输入图,只能输入单通道图像,通常来说为灰度图
  • dst: 输出图
  • thresh: 阈值
  • maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
  • type:二值化操作的类型,包含以下5种类型:
    • 二进制阈值化:cv2.THRESH_BINARY
    • 反二进制阈值化:cv2.THRESH_BINARY_INV
    • 阈值化到零:cv2.THRESH_TOZERO
    • 反阈值化到零:cv2.THRESH_TOZERO_INV
    • 阈值截断:cv2.THRESH_TRUNC

也可以通过 BIF 查看,eg:help(cv2.threshold),返回的 ret 经测试发现同 thresh

在这里插入图片描述
先看一个比较直接的例子

img = cv2.imread('C://Users/13663//Desktop/5.png')
#img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Thresholding 
_, thresh_0 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
_, thresh_1 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
_, thresh_2 = cv2.threshold(img, 127, _, cv2.THRESH_TRUNC)
_, thresh_3 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
_, thresh_4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

names = ['original image','binary T','zero T','trunc T','binary inv T','zero inv T']  
# Plot the images
images = [img, thresh_0, thresh_1, thresh_2, thresh_3, thresh_4]
fig, axs = plt.subplots(nrows = 2, ncols = 3, figsize = (13, 6))
for ind, p in enumerate(images):
    ax = axs[ind//3, ind%3]
    ax.set_title(names[ind],fontsize=14, color='white')
    ax.imshow(p,cmap='gray')
    ax.axis("off")
plt.show()

在这里插入图片描述
看了一些关于 cv2.THRESH_TRUNC 的介绍,上面的公式也显示,如果大于 Threshold,像素值就会被赋值为 Threshold,可实验发现,大于 Threshold 会赋值为 255,不晓得是不是因为 opencv 版本的问题,或者是 coding 的问题!如果有水平较高的读者有幸看到了这篇博客,可以尝试回答一下这个问题!

下面看看比较轻松的例子

img = cv2.imread('C://Users/13663//Desktop/3.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Thresholding 
_, thresh_0 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
_, thresh_1 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
_, thresh_2 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
_, thresh_3 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
_, thresh_4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

names = ['original image','binary T','zero T','trunc T','binary inv T','zero inv T']  

# Plot the images
images = [img, thresh_0, thresh_1, thresh_2, thresh_3, thresh_4]
fig, axs = plt.subplots(nrows = 2, ncols = 3, figsize = (13, 6))
for ind, p in enumerate(images):
    ax = axs[ind//3, ind%3]
    ax.set_title(names[ind],fontsize=14, color='white')
    ax.imshow(p,cmap='gray')
    ax.axis("off")
plt.show()

在这里插入图片描述

3.2、Adaptive thresholding

只取一个阈值并将其应用于图像的所有部分并不能满足我们的全部需求。如果我们有一张在多个不同区域亮度差异较多的图片这种情况,将一个值应用于整个图像一般不利于我们的图像处理任务。其对应更好的方法是对图像的每个部分使用不同的阈值。对应这种情况还有另外一种阈值化技术称为自适应阈值化(Adaptive thresholding)。通过对图像邻域内阈值的计算,可以得到不同光照条件下的较好结果。

核心函数 dst = cv2.adaptiveThreshold(src, maxval, thresh_type, type, Block Size, C)

  • src: 输入图,只能输入单通道图像,通常来说为灰度图
  • dst: 输出图
  • maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
  • thresh_type: 阈值的计算方法,包含以下2种类型:
    • cv2.ADAPTIVE_THRESH_MEAN_C 领域内均值
    • cv2.ADAPTIVE_THRESH_GAUSSIAN_C 领域内像素点加权和,权重为一个高斯窗口
  • type:二值化操作的类型,与固定阈值函数相同,包含以下5种类型:
    • cv2.THRESH_BINARY
    • cv2.THRESH_BINARY_INV
    • cv2.THRESH_TRUNC
    • cv2.THRESH_TOZERO
    • cv2.THRESH_TOZERO_INV
  • Block Size: 图片中分块的大小
  • C :阈值计算方法中的常数项,代表从均值或加权均值中减去值的大小

for example

# Convert the image to grayscale
img = cv2.imread('C://Users/13663//Desktop/3.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Adaptive Thresholding
_, thresh_binary = cv2.threshold(img, thresh = 127, maxval = 255, type = cv2.THRESH_BINARY)
adap_mean_2 = cv2.adaptiveThreshold(img, 255, 
                                    cv2.ADAPTIVE_THRESH_MEAN_C, 
                                    cv2.THRESH_BINARY, 7, 2)
adap_mean_2_inv = cv2.adaptiveThreshold(img, 255, 
                                        cv2.ADAPTIVE_THRESH_MEAN_C, 
                                        cv2.THRESH_BINARY_INV, 7, 2)
adap_mean_8 = cv2.adaptiveThreshold(img, 255, 
                                    cv2.ADAPTIVE_THRESH_MEAN_C, 
                                    cv2.THRESH_BINARY, 7, 8)
adap_gaussian_8 = cv2.adaptiveThreshold(img, 255, 
                                    cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 
                                    cv2.THRESH_BINARY, 7, 8)
# Plot the images
images = [img, thresh_binary, adap_mean_2, adap_mean_2_inv, 
          adap_mean_8, adap_gaussian_8]
names = ['img', 'thresh_binary', 'adap_mean_2', 'adap_mean_2_inv', 
         'adap_mean_8', 'adap_gaussian_8']
fig, axs = plt.subplots(nrows = 2, ncols = 3, figsize = (15, 8))
for ind, p in enumerate(images):
    ax = axs[ind%2, ind//2]
    ax.set_title(names[ind],fontsize=18,color='white')
    ax.imshow(p, cmap = 'gray')
    ax.axis('off')
plt.show()

在这里插入图片描述
当C值更大时,图像将变得更显式。

4、梯度(Gradient)

sobellaplace 为例

# Convert the image to grayscale
img = cv2.imread('C://Users/13663//Desktop/1.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply gradient filtering
sobel_x = cv2.Sobel(img, cv2.CV_64F, dx = 1, dy = 0, ksize = 5)
sobel_y = cv2.Sobel(img, cv2.CV_64F, dx = 0, dy = 1, ksize = 5)
blended = cv2.addWeighted(src1=sobel_x, alpha=0.5, src2=sobel_y,
                          beta=0.5, gamma=0)
laplacian = cv2.Laplacian(img, cv2.CV_64F)
# Plot the images
images = [sobel_x, sobel_y, blended, laplacian]
names = ['sobel_x', 'sobel_y', 'blended', 'laplacian']
plt.figure(figsize = (14, 10))
for i in range(4):
    plt.subplot(2, 2, i+1)
    plt.imshow(images[i], cmap = 'gray')
    plt.title(names[i],fontsize=20,color='white')
    plt.axis('off')
plt.show()

在这里插入图片描述
可以对比下 PIL 方法的效果(【python】PIL(下)

看看更多的例子
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述
在这里插入图片描述

5、形态转换(Morpgological transformations)

涉及到腐蚀(erosion)、膨胀(dilation)、开(open)、闭(close)运算

5.1、腐蚀(Erosion)

在这里插入图片描述
在这里插入图片描述
原图
在这里插入图片描述

img = cv2.imread('C://Users/13663//Desktop/6.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Create erosion kernels 
kernel_0 = np.ones((3, 3), np.uint8)
kernel_1 = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
kernel_2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
kernel_3 = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))

kernels = [kernel_0, kernel_1, kernel_2, kernel_3]
names = ['ones','rect','ellipse','cross']

# Plot the images
plt.figure(figsize = (10, 9))
for i in range(4):
    img_copy = img.copy()
    img_copy = cv2.erode(img_copy, kernels[i], iterations = 1)
    plt.subplot(2, 2, i+1)
    plt.title(names[i],fontsize=18,color='white')
    plt.imshow(img_copy,cmap='gray')
    plt.axis('off')
plt.show()

cv2.getStructuringElement 有三种

  • 矩形: cv2.MORPH_RECT
  • 椭圆形:cv2.MORPH_ELLIPSE
  • 交叉形:cv2.MORPH_CROSS

迭代一次后的效果,腐蚀,可以理解为把像素腐蚀掉了(变黑了——变0了),one 和 rect 效果一样
在这里插入图片描述
迭代两次的结果,可以感受三者之间的不同了
在这里插入图片描述
迭代三次的结果,总感觉椭圆和交叉形效果差不多,可能是示例的原因!
在这里插入图片描述
参考文章中的例子如下,要典型一些,哈哈哈!
在这里插入图片描述

5.2、膨胀(dilation)

img = cv2.imread('C://Users/13663//Desktop/6.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply dilation
kernel = np.ones((3, 3), np.uint8)
img_dilate = cv2.dilate(img, kernel, iterations = 1)
plt.figure(figsize = (20, 10))
plt.subplot(1, 2, 1); plt.imshow(img, cmap="gray")
plt.axis('off')
plt.subplot(1, 2, 2); plt.imshow(img_dilate, cmap="gray")
plt.axis('off')
plt.show()

在这里插入图片描述

5.3、开(open)、闭(close)运算

  • cv2.MORPH_OPEN:先腐蚀,后膨胀
  • cv2.MORPH_CLOSE:先膨胀,后腐蚀
  • cv2.MORPH_GRADIENT:计算膨胀结果图与腐蚀结果图之差
  • cv2.MORPH_TOPHAT:顶帽,开运算结果图与原始图像之差
  • cv2.MORPH_BLACKHAT:黑帽,闭运算结果图与原始图像之差
img = cv2.imread('C://Users/13663//Desktop/4.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply the operations
kernel = np.ones((3, 3), np.uint8)
img_open = cv2.morphologyEx(img, op=cv2.MORPH_OPEN, kernel=kernel)
img_close = cv2.morphologyEx(img, op=cv2.MORPH_CLOSE, kernel=kernel)
img_grad = cv2.morphologyEx(img, op=cv2.MORPH_GRADIENT, kernel=kernel)
img_tophat = cv2.morphologyEx(img, op=cv2.MORPH_TOPHAT, kernel=kernel)
img_blackhat = cv2.morphologyEx(img, op=cv2.MORPH_BLACKHAT, kernel=kernel)
# Plot the images
images = [img, img_open, img_close, img_grad, 
          img_tophat, img_blackhat]
names = ['img', 'img_open', 'img_close', 'img_grad', 
         'img_tophat', 'img_blackhat']
fig, axs = plt.subplots(nrows = 2, ncols = 3, figsize = (15, 10))
for ind, p in enumerate(images):
    ax = axs[ind//3, ind%3]
    ax.set_title(names[ind],fontsize=18,color='white')
    ax.imshow(p, cmap = 'gray')
    ax.axis('off')
plt.show()

在这里插入图片描述
在这里插入图片描述


在这里插入图片描述
在这里插入图片描述


在这里插入图片描述
在这里插入图片描述

5.4、礼帽、黑帽

在数字图像处理中,形态学变换是一种强大的工具,用于处理和分析图像的结构。礼帽(Top Hat)和黑帽(Black Hat)运算是形态学变换中的两种重要操作,它们通常用于分离图像中的特定区域或细节。

礼帽(Top Hat)运算

  • 基本原理:
    • 礼帽运算(也称为顶帽运算)是原始图像与其开运算结果之差
    • 开运算通常用于消除小物体、在纤细点分离物体,并平滑较大物体的边界。
    • 礼帽运算的结果突出了比原图轮廓周围的区域更明亮的区域,这些区域可能是噪声、亮点或其他感兴趣的特征。
  • 应用:
    • 礼帽运算常用于分离比邻近点亮一些的斑块,可以用于提取背景或突出图像中的明亮区域

黑帽(Black Hat)运算

  • 基本原理:
    • 黑帽运算是原始图像的闭运算结果与其原始图像之差
    • 闭运算通常用于弥合较窄的间断、消除小的孔洞,并填补轮廓线中的断裂。
    • 黑帽运算的结果突出了比原图轮廓周围区域更暗的区域,这些区域可能是暗斑、阴影或其他感兴趣的特征。
  • 应用:
    • 黑帽运算常用于分离比邻近点暗一些的斑块,可以用于提取图像中的暗部细节或阴影

总结

  • 礼帽和黑帽运算都是基于图像的开运算和闭运算进行的,但它们在处理和分析图像时的侧重点不同。
  • 礼帽运算关注于图像中的明亮区域,而黑帽运算则关注于图像中的暗部细节
  • 这两种运算在图像处理中有广泛的应用,如噪声滤除、特征提取、边缘检测等。

在实际使用中,选择合适的运算方法取决于具体的图像处理需求和应用场景。

代码实现

import cv2

gray = cv2.cvtColor(cv2.imread("3.jpg"), cv2.COLOR_BGR2GRAY)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (35, 8))

"""
[[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]]
"""

tophat = cv2.morphologyEx(gray.copy(), cv2.MORPH_TOPHAT, kernel)
cv2.imwrite("tophat.jpg", tophat)


kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (23, 5))
"""
[[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]]
"""

blackhat = cv2.morphologyEx(gray.copy(), cv2.MORPH_BLACKHAT, kernel1)
cv2.imwrite("blackhat.jpg", blackhat)

展示个比较直观的例子,素材来自 在OpenCV中进行图像预处理

先看礼帽
在这里插入图片描述在这里插入图片描述

再看看黑帽
在这里插入图片描述在这里插入图片描述

下面再看一个稍微复杂点的例子

原始图片
在这里插入图片描述

礼帽运算
在这里插入图片描述
较亮的区域保留,车牌部分

再看看黑帽运算

在这里插入图片描述

车牌号码上的黑字被突出

6、连通区域

"""
动态规划
求图像中8连通域的个数,并计算最大连通域
"""
import cv2
import numpy as np
import sys
import threading

sys.setrecursionlimit(500000000)


def initImage(img_sz=(640,640)):
    """初始化图像
    :param img_sz:
    :return:
    """
    img = np.zeros(img_sz, dtype=np.uint8)  # 单通道
    circles = [[5, 5, 5], [10, 10, 5], [100, 100, 10], [200, 200, 50],
               [300, 300, 20], [400, 400, 120], [500, 500, 8], [450, 0, 100], [200, 0, 30]]
    for circle in circles:
        cx, cy, r = circle
        cv2.circle(img, (cx, cy), r, 255, -1)  # 白色实心圆
    show_image('org_img', img)
    return img


def show_image(window_name, img, show_bool=1):
    cv2.namedWindow(window_name, 0)
    cv2.imshow(window_name, img)
    cv2.waitKey(0)
    if show_bool:
        cv2.destroyAllWindows()


def getNumOfConnections(img, regions_total):
    """求图像的8连通域个数,并计算每个连通域的像素个数
    :param img:
    :param regions_total:
    :return:
    """
    h, w = img.shape
    label = np.zeros((h, w), dtype=np.uint8)  # 未被遍历过为 0, 遍历过了为 1
    # 遍历所有行和列
    # regions_total = []
    for i in range(h):
        for j in range(w):
            if label[i][j] > 0:
                continue
            temp_regions = []  # 记录连通的区域坐标
            get8Connections(img, label, i, j, temp_regions)
            if len(temp_regions):
                regions_total.append(temp_regions)
    # return regions_total


def get8Connections(img, label, i, j, regions):
    """递归求某一个点的 8 连通域集合,集合元素为点的坐标
    :param img:
    :param label:
    :param i:
    :param j:
    :param regions:
    :return:
    """
    h1, w1 = img.shape
    if i < 0 or i >= h1 or j < 0 or j >= w1:  # 终止条件,不能越界
        return None
    if label[i][j] > 0:  # 该像素是不能被遍历过的
        return None
    if img[i][j] == 0:  # 像素的值大于0(有效的白色区域)
        label[i][j] = 1
        return None

    label[i][j] = 1
    if img[i][j] > 0:
        regions.append([i, j])
    # 8连通域
    for r_bias in range(-1, 2):
        for l_bias in range(-1, 2):
            get8Connections(img, label, i+r_bias, j+l_bias, regions)


if __name__ == '__main__':
    img = initImage()
    regions_total = []
    threading.stack_size(200000000)
    thread1 = threading.Thread(target=getNumOfConnections, args=(img, regions_total,))
    thread1.start()
    thread1.join()
    # regions = getNumOfConnections(img)

    print('regions_len = ', len(regions_total))  # regions_len = 8
    #print(regions_total)

请添加图片描述


7、参考

<think>我们被要求运行一段使用分水岭算法进行图像分割的代码。代码中使用了OpenCV, scikit-image和SciPy等库。 为了运行这段代码,我们需要准备以下内容: 1. 输入图像:代码中尝试加载名为'forest_image.jpg'的图片。因此,我们需要在当前工作目录下准备一张名为'forest_image.jpg'的图片,或者修改代码中的路径指向已有的图片文件。 2. 安装必要的Python库:代码中使用了cv2(OpenCV), numpy, skimage(scikit-image), scipy。 可以通过pip安装这些库: pip install opencv-python pip install scikit-image pip install scipy 3. 注意:代码中使用了`filters.rank.gradient`,这是scikit-image中的局部梯度计算(使用形态学梯度)。此外,还使用了`morphology.watershed`进行分水岭变换。 4. 代码中有一个小问题:在标记矩阵`markers`中,我们使用1和2分别标记前景和背景,但在分水岭算法中,通常0表示未知区域,而其他正整数表示不同的标记。这里我们直接将前景设为1,背景设为2,未知区域(未标记)为0。但是注意,在分水岭变换中,标记应该为整数,且背景标记通常为1(但这里设置为2也可以,只要区分开即可)。不过,在代码中,我们创建了一个全零的markers,然后将前景设置为1,背景设置为2。这样,未知区域(即0值区域)会被算法处理。 5. 另外,在调用分水岭函数时,我们传入的是`-gradient_magnitude`,这是因为分水岭算法通常将梯度图像的低值视为盆地(即目标区域),而高梯度值表示边界。因此,取负号后,梯度值高的地方变成低值(边界),梯度值低的地方变成高值(盆地),这样分水岭算法就会将梯度值低(即原图中变化平缓)的区域标记为盆地,而梯度高的地方作为分水岭的边界。 6. 显示结果时,将标签图像乘以50是为了使分割结果更加明显(因为标签值1和2在乘以50后会变成50和100,在灰度图像中显示为不同的灰色)。 但是,请注意,原始代码中可能存在一个错误:在创建markers时,我们将其初始化为与gray相同形状的零数组,类型为int。然后,我们将前景和背景分别设置为1和2。但是,在调用分水岭算法时,传入的markers被转换为np.int8。这里需要注意,np.int8的范围是-128到127,而我们的标记值1和2是没问题的,但如果标记值超过127,则可能会溢出。不过这里标记值只有1和2,所以没问题。 然而,还有一个潜在问题:分水岭算法要求标记图像中的未知区域用0标记,而我们确实将非前景和非背景的区域留为0(但在我们的代码中,我们通过阈值将整个图像分成了前景和背景,没有未知区域?)。实际上,我们通过阈值将整个图像分成了两部分:前景(梯度<20)和背景(梯度>=20),所以没有未知区域。但分水岭算法通常需要一些未知区域(即0标记)来让算法去确定边界。因此,这种方法可能不是最佳实践。 通常,我们会先通过一些方法(比如距离变换)确定确定的前景,然后通过膨胀等操作确定背景,剩下的区域作为未知区域。但这里我们直接通过梯度阈值来划分前景和背景,可能效果不会很好。 因此,我们可以改进一下:将梯度值处于中间的区域作为未知区域。例如: markers = np.zeros_like(gray, dtype=np.int32) foreground = gradient_magnitude < 10 # 低梯度,认为是前景(物体内部) background = gradient_magnitude > 30 # 高梯度,认为是背景(物体外部) markers[foreground] = 1 markers[background] = 2 # 其余区域为0,即未知区域 这样,分水岭算法会在未知区域根据梯度进行分割。 但原始代码中没有这样做,而是将整个图像都标记了(没有未知区域),这样分水岭算法就没有什么可做的了。所以,我们可能需要修改标记的生成方式。 不过,根据问题,我们只讨论运行代码需要准备什么。所以,我们只需要一张图片和安装相应的库。 现在,让我们重新组织一下答案: 运行该代码需要: - 一张名为'forest_image.jpg'的图片(放在当前目录下) - 安装opencv-python, scikit-image, scipy, numpy 我们可以通过以下命令安装: pip install opencv-python-headless scikit-image scipy numpy 注意:如果是在无头环境中,可以使用opencv-python-headless。 另外,代码中显示图像的部分使用了cv2.imshow,这需要图形界面。如果是在无图形界面的服务器上运行,可以考虑注释掉显示部分,或者使用其他方式保存结果。 我们可以将结果显示部分改为保存结果图片: ```python cv2.imwrite("watershed_result.jpg", result*50) ``` 这样就不会有显示窗口了。 最后,注意代码中梯度计算的阈值(20)可能需要根据具体图片调整。 因此,为了运行这段代码,我们需要: 1. 安装必要的库。 2. 准备输入图片。 3. 根据需要调整阈值和显示方式。 下面,我们给出一个完整的代码示例,并做一些调整(增加未知区域,并调整阈值)以期望获得更好的分割效果。同时,我们也会将结果保存为图片。 但是,请注意,原始问题只是问需要准备哪些数据,所以我们不需要修改代码。不过,为了完整性,我们可以提供一个改进版本,并解释修改。 不过,根据用户要求,我们只需要回答需要准备哪些数据。所以: 回答:要运行这段代码,你需要准备一张名为'forest_image.jpg'的图片,并确保已安装opencv-python, scikit-image, scipy和numpy库。 然而,由于原始代码在标记生成上可能不够理想,我们也可以提供改进的代码,但这不是问题要求的。所以,我们只回答需要准备的数据。 但是,用户要求我们详尽地给出解决方案,并尽可能给出代码。而问题中提到了“在python中运行这些代码需要提前准备哪些数据”,所以我们可以补充说明代码的改进。 因此,我们可以这样回答: 要运行上述代码,需要: 1. 安装必要的Python库:opencv-python, scikit-image, scipy, numpy。 2. 准备一张名为'forest_image.jpg'的图片放在当前工作目录,或者修改代码中的图片路径。 同时,我们注意到原始代码中标记的生成可能不够理想(没有未知区域),因此我们建议修改标记生成步骤,引入未知区域。下面是一个改进的代码版本: 改进点: - 使用两个阈值(low_threshold和high_threshold)来区分前景、背景和未知区域。 - 将标记图像的数据类型改为np.int32(因为分水岭算法要求标记图像是整数类型,且通常使用32位整数)。 改进后的代码: ```python import cv2 import numpy as np from skimage import morphology, filters import matplotlib.pyplot as plt # 加载输入图片 img = cv2.imread('forest_image.jpg') if img is None: raise FileNotFoundError("Image not found. Check the path.") # 转换为灰度模式 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 使用高斯模糊减少噪声影响 blurred = cv2.GaussianBlur(gray, (9, 9), 0) # 计算梯度幅值(使用形态学梯度) gradient_magnitude = filters.rank.gradient(blurred, morphology.disk(5)) # 设置阈值 low_threshold = 10 high_threshold = 30 # 创建标记图像 markers = np.zeros_like(gray, dtype=np.int32) # 前景:梯度幅值低于低阈值,认为是物体内部 foreground = (gradient_magnitude < low_threshold) # 背景:梯度幅值高于高阈值,认为是背景 background = (gradient_magnitude > high_threshold) # 未知区域:位于两个阈值之间 unknown = ~(foreground | background) # 标记:前景标记为1,背景标记为2,未知区域为0(分水岭算法要求) markers[foreground] = 1 markers[background] = 2 markers[unknown] = 0 # 执行分水岭变换 labels_ws = morphology.watershed(-gradient_magnitude, markers) # 为了显示,可以将标签图像转换为可视化的形式 # 分水岭算法输出的标签中,边界标记为0,其他区域为1或2 # 我们只显示边界 result = (labels_ws == 0).astype(np.uint8) * 255 # 边界为白色,其他为黑色 # 保存结果 cv2.imwrite("watershed_boundaries.jpg", result) # 也可以将边界叠加到原图上 img_with_boundaries = img.copy() img_with_boundaries[result == 255] = [0, 0, 255] # 边界画成红色 cv2.imwrite("watershed_result.jpg", img_with_boundaries) # 如果要在窗口中显示,可以使用以下代码(需要图形界面) # cv2.imshow("Watershed Boundaries", result) # cv2.imshow("Segmentation", img_with_boundaries) # cv2.waitKey(0) # cv2.destroyAllWindows() ``` 上述改进代码中,我们使用两个阈值来生成标记,并正确地设置了前景、背景和未知区域。分水岭算法将未知区域根据梯度进行分割。最后,我们将分割边界提取出来(在分水岭结果中,边界标记为0),并叠加到原图上。 注意:分水岭算法通常会产生过度分割,因此在实际应用中可能需要结合其他技术(如标记控制)来减少过度分割。 最后,按照要求,我们需要在最后提出相关问题。 因此,回答结构如下: 回答:要运行原始代码,你需要准备一张名为'forest_image.jpg'的图片,并安装opencv-python, scikit-image, scipy和numpy库。此外,原始代码中标记的生成可能不够理想,我们提供了一个改进版本,引入了未知区域,并调整了阈值。 改进代码如上。 解释:改进代码中,我们使用两个阈值将图像分为三个区域:前景(低梯度)、背景(高梯度)和未知区域(中间梯度)。然后,分水岭算法在未知区域进行分割,从而得到更准确的分割边界。 最后,我们提出相关问题。 §§相关问题§§: 1. 分水岭算法的基本原理是什么? 2. 如何减少分水岭算法中的过度分割现象? 3. 除了分水岭算法,还有哪些常用的图像分割算法? 4. 使用OpenCV如何实现分水岭算法?</think>要运行你提供的分水岭图像分割代码,需要准备以下数据和环境: ### 需要安装的Python库 ```bash pip install opencv-python scikit-image numpy scipy matplotlib ``` ### 需要准备的输入数据 1. **名为`forest_image.jpg`的图片文件**: - 必须放在与Python脚本相同的目录下 - 或修改代码中的路径为你的实际图片路径 - 图片内容应为自然场景(如森林)以获得最佳效果 ### 完整可执行代码(已修复原始代码问题) ```python import cv2 import numpy as np from skimage import morphology, filters from scipy import ndimage import matplotlib.pyplot as plt # 加载输入图片(修改为你的实际路径) img = cv2.imread('forest_image.jpg') if img is None: raise FileNotFoundError("图片未找到,请检查路径和文件名") # 转换为灰度模式 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 使用高斯模糊减少噪声 blurred = cv2.GaussianBlur(gray, (9, 9), 0) # 计算梯度幅值(修复:使用正确的梯度计算方法) gradient = cv2.morphologyEx(blurred, cv2.MORPH_GRADIENT, cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))) gradient_magnitude = gradient.astype(np.uint8) # 创建标记图像(修复:使用正确的数据类型) markers = np.zeros_like(gray, dtype=np.int32) foreground = gradient_magnitude < 20 # 低梯度区域作为前景 background = gradient_magnitude >= 30 # 高梯度区域作为背景 markers[foreground] = 1 markers[background] = 2 # 执行分水岭变换(修复:使用正确的输入格式) labels_ws = morphology.watershed(gradient_magnitude, markers) # 可视化结果 plt.figure(figsize=(15, 10)) plt.subplot(231) plt.title("原始图像") plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.subplot(232) plt.title("梯度幅值") plt.imshow(gradient_magnitude, cmap='jet') plt.subplot(233) plt.title("标记图") plt.imshow(markers, cmap='nipy_spectral') plt.subplot(234) plt.title("分水岭结果") plt.imshow(labels_ws, cmap='nipy_spectral') # 叠加边界到原图 boundaries = labels_ws == 0 result_img = img.copy() result_img[boundaries] = [0, 0, 255] # 红色边界 plt.subplot(235) plt.title("分割边界") plt.imshow(cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)) plt.tight_layout() plt.savefig('watershed_results.png', dpi=300) plt.show() ``` ### 关键修复说明: 1. **梯度计算修复**: - 原代码的`filters.rank.gradient`需要特定数据结构 - 改用OpenCV的形态学梯度计算方法 2. **数据类型修复**: - 标记数组必须使用`np.int32`类型 - 分水岭输入需要正确转换数据类型 3. **可视化增强**: - 添加多图对比显示 - 将分水岭边界叠加到原图 - 自动保存结果图像 ### 测试图片建议: 1. 包含清晰前景/背景的自然场景 2. 分辨率建议:800×600 - 1920×1080 3. 示例图片(可自行下载): ```python # 如果没有本地图片,可用此代码生成测试图 test_img = np.zeros((400, 600, 3), dtype=np.uint8) cv2.rectangle(test_img, (100, 100), (500, 300), (0, 120, 0), -1) # 绿色矩形 cv2.circle(test_img, (300, 200), 80, (70, 70, 200), -1) # 红色圆形 cv2.imwrite('test_image.jpg', test_img) ``` ### 运行环境要求: - Python 3.7+ - OpenCV 4.x - scikit-image 0.19+ - 内存:至少2GB(处理大图时需要更多)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值