系列文章
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
学习整理
对于2D图像可以进行低通或者高通滤波操作,低通滤波(LPF)有利于去噪,模糊图像,高通滤波(HPF)有利于找到图像边界。
cv2.filter2D
cv2.blur
cv2.GaussianBlur
cv2.medianBlur
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()
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()
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()
图像的阈值化就是利用图像像素点分布规律,设定阈值进行像素点分割,进而得到图像的二值图像。我们需要设置阈值和最大值,然后据此相应地进行像素值转换。核心函数如下:
ret, dst = cv2.threshold(src, thresh, maxval, type)
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()
下面看看比较轻松的例子
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()
只取一个阈值并将其应用于图像的所有部分并不能满足我们的全部需求。如果我们有一张在多个不同区域亮度差异较多的图片这种情况,将一个值应用于整个图像一般不利于我们的图像处理任务。其对应更好的方法是对图像的每个部分使用不同的阈值。对应这种情况还有另外一种阈值化技术称为自适应阈值化(Adaptive thresholding)。通过对图像邻域内阈值的计算,可以得到不同光照条件下的较好结果。
核心函数 dst = cv2.adaptiveThreshold(src, maxval, thresh_type, type, Block Size, C)
cv2.ADAPTIVE_THRESH_MEAN_C
领域内均值cv2.ADAPTIVE_THRESH_GAUSSIAN_C
领域内像素点加权和,权重为一个高斯窗口cv2.THRESH_BINARY
cv2.THRESH_BINARY_INV
cv2.THRESH_TRUNC
cv2.THRESH_TOZERO
cv2.THRESH_TOZERO_INV
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()
以 sobel
和 laplace
为例
# 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()
看看更多的例子
涉及到腐蚀(erosion)、膨胀(dilation)、开(open)、闭(close)运算
原图
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 效果一样
迭代两次的结果,可以感受三者之间的不同了
迭代三次的结果,总感觉椭圆和交叉形效果差不多,可能是示例的原因!
参考文章中的例子如下,要典型一些,哈哈哈!
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()
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()
在数字图像处理中,形态学变换是一种强大的工具,用于处理和分析图像的结构。礼帽(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)
展示个比较直观的例子,素材来自
先看礼帽
再看看黑帽
下面再看一个稍微复杂点的例子
原始图片
礼帽运算
较亮的区域保留,车牌部分
再看看黑帽运算
车牌号码上的黑字被突出
"""
动态规划
求图像中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)