一、实验目的与要求
1.了解图像分割的意义和常用方法
2.掌握常用的图像分割方法
二、实验相关知识
图像分割就是把图像分成互不重叠的区域并提取出感兴趣目标的技术,是由图像处理到图像分析的关键步骤。
和本实验有关的常用Matlab函数:
edge:检测灰度或二值图像的边缘,返回一个二值图像,1像素是检测到的边缘,0像素是非边缘
用法:BW=edge(I,'sobel',thresh,direction); %I为检测对象;
边缘检测算子可用sobel、roberts、prewitt、zerocross、log、canny;
thresh指定阈值,检测时忽略所有小于阈值的边缘,默认自动选择阈值;
direction指定方向,可选项有horizontal、vertical或both,在指定的方向上用算子进行边缘检测
三、实验内容
1、分别用Roberts、Prewitt、Sobel三种边缘检测算子,对图像wire.bmp进行水平、垂直及各个方向的边界检测,并将检测结果转化为白底黑线条的方式显示出来;
2、使用手动阈值分割法对bottle图像进行分割,显示分割结果,并分析其优缺点。
3、对下图A进行处理,得到类似图B的样子;
图A
图B
四、实验代码及结果
1、边界检测
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
import argparse
parser = argparse.ArgumentParser("")
parser.add_argument("--method", type=int, default=1, choices=[1, 2, 3], help="1:Sobel\t2:Prewitt\t3:Roberts")
parser.add_argument("--file", type=str, default="img.png")
args = parser.parse_args()
if __name__ == "__main__":
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
plt.subplot(221)
origin = cv.imread(args.file, 0)
plt.imshow(origin, cmap="gray")
plt.title("原图像")
plt.axis(False)
if args.method == 1:
# OpenCV文档中对Sobel算子的介绍:“in the case of 8-bit input images it will result in truncated derivatives”。
# 即Sobel函数求完导数后会有负值,还有会大于255的值,所以使用16位有符号数表示梯度
# 然后用 cv.convertScaleAbs转回uint8(8位无符号数)形式
# 索贝尔算子
# 边界类型选择REPLICATE,与MATLAB edge函数文档中默认方式一致
x = cv.Sobel(origin, cv.CV_16S, 1, 0, borderType=cv.BORDER_REPLICATE)
y = cv.Sobel(origin, cv.CV_16S, 0, 1, borderType=cv.BORDER_REPLICATE)
absX = cv.convertScaleAbs(x)
ret, Sobel_X = cv.threshold(absX, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
plt.subplot(222)
plt.imshow(255 - Sobel_X, cmap="gray")
plt.title("垂直方向检测边缘")
plt.axis(False)
absY = cv.convertScaleAbs(y)
ret, Sobel_Y = cv.threshold(absY, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
plt.subplot(223)
plt.imshow(255 - Sobel_Y, cmap="gray")
plt.title("水平方向检测边缘")
plt.axis(False)
Sobel = cv.addWeighted(Sobel_X, 0.5, Sobel_Y, 0.5, 0)
plt.subplot(224)
plt.imshow(255 - Sobel, cmap="gray")
plt.axis(False)
plt.suptitle("Sobel算子", fontsize=20)
plt.show()
if args.method == 2:
# prewitt算子
# 自定义x, y方向卷积核,然后通过卷积实现
prewitt_kernel_x = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=int)
prewitt_kernel_y = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], dtype=int)
x = cv.filter2D(origin, cv.CV_16S, prewitt_kernel_x, borderType=cv.BORDER_REPLICATE)
y = cv.filter2D(origin, cv.CV_16S, prewitt_kernel_y, borderType=cv.BORDER_REPLICATE)
absX = cv.convertScaleAbs(x)
ret, Prewitt_X = cv.threshold(absX, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
plt.subplot(222)
plt.imshow(255 - Prewitt_X, cmap="gray")
plt.title("垂直方向检测边缘")
plt.axis(False)
absY = cv.convertScaleAbs(y)
ret, Prewitt_Y = cv.threshold(absY, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
plt.subplot(223)
plt.imshow(255 - Prewitt_Y, cmap="gray")
plt.title("水平方向检测边缘")
plt.axis(False)
Prewitt = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
plt.subplot(224)
plt.imshow(255 - Prewitt, cmap="gray")
plt.axis(False)
plt.suptitle("Prewitt算子", fontsize=20)
plt.show()
if args.method == 3:
# matlab edge方法中的Roberts算子实际上有除以2,[[-1, 0], [0, 1]]/2,[[0, -1], [1, 0]]/2
# 但是在这里加上/2效果依然比MATLAB函数要好
roberts_kernel_x = np.array([[-1, 0], [0, 1]], dtype=int)
roberts_kernel_y = np.array([[0, -1], [1, 0]], dtype=int)
x = cv.filter2D(origin, cv.CV_16S, roberts_kernel_x)
y = cv.filter2D(origin, cv.CV_16S, roberts_kernel_y)
absX = cv.convertScaleAbs(x)
ret, Roberts_X = cv.threshold(absX, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
plt.subplot(222)
plt.imshow(255 - Roberts_X, cmap="gray")
plt.title("垂直方向检测边缘")
plt.axis(False)
absY = cv.convertScaleAbs(y)
ret, Roberts_Y = cv.threshold(absY, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
plt.subplot(223)
plt.imshow(255 - Roberts_Y, cmap="gray")
plt.title("水平方向检测边缘")
plt.axis(False)
Roberts = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
plt.subplot(224)
plt.imshow(255 - Roberts, "gray")
plt.title("Roberts算子")
plt.axis(False)
plt.show()
实验结果:
2、阈值分割
import math
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
from numba import cuda
@cuda.jit()
def segment(img, height, width, low_ind):
i = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x
j = cuda.blockIdx.y * cuda.blockDim.y + cuda.threadIdx.y
if i < height and j < width:
if img[i, j] > low_ind:
img[i, j] = 255
else:
img[i, j] = 0
if __name__ == "__main__":
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
img = cv.imread("bottle.png", 0)
plt.subplot(131)
plt.imshow(img, cmap="gray")
plt.title("原图像")
plt.axis(False)
# 使用cv库函数统计灰度直方图
greyScale_map = cv.calcHist([img], [0], None, [256], [0, 256])
greyScale_map = np.array(greyScale_map).reshape(256)
plt.subplot(132)
plt.bar(range(256), greyScale_map, width=1)
plt.title("直方图")
# 将灰度直方图拟合为3次曲线
z = np.polyfit(range(256), greyScale_map, 5)
y = np.poly1d(z)(range(256))
# print(y)
plt.plot(range(256), y, color="red")
# 找到极小值点,即波谷
low_ind = signal.argrelextrema(y, np.less)[0]
print(low_ind)
# 进行阈值分割
threadsperblock = (32, 32)
blockspergrid_x = int(math.ceil(img.shape[0] / threadsperblock[0]))
blockspergrid_y = int(math.ceil(img.shape[1] / threadsperblock[1]))
blockspergrid = (blockspergrid_x, blockspergrid_y)
cuda.synchronize()
cuda_img = cuda.to_device(img)
segment[blockspergrid, threadsperblock](cuda_img, img.shape[0], img.shape[1], low_ind[0])
plt.subplot(133)
plt.imshow(cuda_img.copy_to_host(), cmap="gray")
plt.axis(False)
plt.title("阈值分割后")
plt.show()
3、
import math
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
from numba import cuda
from scipy import signal
@cuda.jit()
def segment(img, height, width, low_ind):
i = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x
j = cuda.blockIdx.y * cuda.blockDim.y + cuda.threadIdx.y
if i < height and j < width:
if img[i, j] > low_ind:
img[i, j] = 255
else:
img[i, j] = 0
if __name__ == "__main__":
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
img = cv.imread("particle.png", 0)
plt.subplot(221)
plt.imshow(img, cmap="gray")
plt.axis(False)
plt.title("原图像")
greyScale_map = cv.calcHist([img], [0], None, [256], [0, 256])
greyScale_map = np.array(greyScale_map).reshape(256)
plt.subplot(222)
plt.bar(range(256), greyScale_map, width=1)
plt.title("直方图")
# 阈值分割
threadsperblock = (32, 32)
blockspergrid_x = int(math.ceil(img.shape[0] / threadsperblock[0]))
blockspergrid_y = int(math.ceil(img.shape[1] / threadsperblock[1]))
blockspergrid = (blockspergrid_x, blockspergrid_y)
cuda.synchronize()
cuda_img = cuda.to_device(img)
segment[blockspergrid, threadsperblock](cuda_img, img.shape[0], img.shape[1], 110)
new_image = cuda_img.copy_to_host()
del cuda_img
plt.subplot(223)
plt.imshow(new_image, cmap="gray")
plt.axis(False)
plt.title("阈值分割后")
# Sobel算子进行边缘检测
x = cv.Sobel(new_image, cv.CV_16S, 1, 0, borderType=cv.BORDER_REPLICATE)
y = cv.Sobel(new_image, cv.CV_16S, 0, 1, borderType=cv.BORDER_REPLICATE)
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
# 二值化
ret, Sobel_X = cv.threshold(absX, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
ret, Sobel_Y = cv.threshold(absY, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
Sobel = cv.addWeighted(Sobel_X, 0.5, Sobel_Y, 0.5, 0)
plt.subplot(224)
plt.imshow(255 - Sobel, cmap="gray")
plt.axis(False)
plt.title("Sobel算子边缘检测")
plt.show()