机器学习-09-图像处理02-PIL+numpy+OpenCV实践

news2024/11/23 15:46:23

总结

本系列是机器学习课程的系列课程,主要介绍机器学习中图像处理技术。

参考

【人工智能】Python+OpenCV图像处理(一篇全)

一文讲解方向梯度直方图(hog)

【杂谈】计算机视觉在人脸图像领域的十几个大的应用方向,你懂了几分?

开源地理空间基金会中文分会 Pillow (PIL Fork) 10.0.1 文档

python使用opencv对图像添加(高斯/椒盐/泊松/斑点)噪声

【OpenCV实战】简洁易懂的车牌号识别Python+OpenCV实现“超详解”(含代码)

本门课程的目标

完成一个特定行业的算法应用全过程:

懂业务+会选择合适的算法+数据处理+算法训练+算法调优+算法融合
+算法评估+持续调优+工程化接口实现

机器学习定义

关于机器学习的定义,Tom Michael Mitchell的这段话被广泛引用:
对于某类任务T性能度量P,如果一个计算机程序在T上其性能P随着经验E而自我完善,那么我们称这个计算机程序从经验E中学习
在这里插入图片描述

图像数据

pip list

PIL模块

Image模块

PIL提供了大量常用的、基本的图像操作方法。本节将介绍几个图像处理中非常重要的模块。
(1)Image模块
Image模块中定义的Image类是PIL类库中最重要的类,实现了图像文件的读写、显示、颜色空间转换等方法。
Image类的open()方法接收图像文件路径为参数,用于读取图像文件。如果载入文件失败,则会引起一个IOError;载入成功则返回一个Image对象。例如,读取当前目录下文件名为“python.png”的图像:
在这里插入图片描述

#导入PIL类库中的Image模块
from PIL import Image
#加载图像数据到Image对象(Image对象的open()方法可以根据文件扩展名判断图像的格式)
im=Image.open('python.png')
# 利用Image对象的属性打印输出图像的类型、大小和模式:
print(im.format, im.size, im.mode)
# JPEG (256, 256) RGB
# PNG (369, 379) RGB

这里有三个属性,format识别图像的源格式,如果该文件不是从文件中读取的,则被置为 None 值;size是有两个元素的元组,其值为像素意义上的宽和高;mode表示颜色空间模式,定义了图像的类型和像素的位宽。
PIL支持如下模式:

1:1位像素,表示黑和白,但是存储的时候每个像素存储为8bit。
L:8位像素,对应灰度图像,可以表示256级灰度。
P:8位像素,使用调色板映射到其他模式。
RGB:3x8位像素,为真彩色。
RGBA:4x8位像素,有透明通道的真彩色。
CMYK:4x8位像素,颜色分离。
YCbCr:3x8位像素,彩色视频格式。
I:32位整型像素。
F:32位浮点型像素。
PIL也支持一些特殊的模式,包括RGBX(有padding的真彩色)和RGBa(有自左乘alpha的真彩色)。上述print语句的输出结果表示python.jpg文件对应的图像格式为JPEG,宽和高均为256,颜色模式为RGB的彩色图像。

读取的图像数据可以利用Image对象的show()方法进行显示:

im.show()
# 标准版本的show()方法不是很有效率,
# 因为Linux系统下,它先将图像保存为一个临时文件,然后使用xv进行显示。
# 如果没有安装xv,该函数甚至不能工作。Windows系统下该方法调用默认图片查看器打开图像。

输出为:
在这里插入图片描述

# 图像颜色空间转换可以使用convert()方法来实现,例如,将读取的im数据转换为灰度图像:
im_gray=im.convert('L')
im_gray.show()

输出为:
在这里插入图片描述

# 图像数据的保存使用save()方法:
#将im_gray保存为png图像文件,文件名为python_gray.png
im_gray.save('python_gray.png')
#Image对象的crop()方法可以从一副图像中裁剪指定的矩形区域,它接收包含四个元素的元组作为参数,
#各元素的值分别对应裁剪区域在原图像中的左上角和右下角位置的坐标,坐标系统的原点(0, 0)在图像的左上角:
#使用四元组(左,上,右,下)指定裁剪区域
box = (10, 10, 300, 300)
#裁剪图像区域
region = im.crop(box)
#保存裁剪的图像区域数据到图像文件
region.save('python_region.png')
region.show()

输出为:
在这里插入图片描述

#Image对象的transpose()方法通过传入参数Image.ROTATE_#(#代表旋转的角度,可以是90°、180°、270°)可以将图像数据进行旋转;
#也可以传入参数Image.FLIP_LEFT_RIGHT将图像做水平翻转,传入参数Image.FLIP_TOP_BOTTOM做垂直翻转等:
#将图像数据region旋转90°
region_90 = region.transpose(Image.ROTATE_90)
region_90.save('region_90.png')	#保存图像
region_90.show()

输出为:
在这里插入图片描述

# Image对象的rotate ()方法可以将图像数据进行任意角度的旋转:
#将图像数据region逆时针旋转30°
region_30 = region.rotate(30)
region_30.save('region_30.png')	#保存图像
region_30.show()

输出为:
在这里插入图片描述

需要注意的,粘贴的图像数据必须与粘贴区域具有相同的大小,但是,它们的颜色模式可以不同,
paste()方法在粘贴之前自动将粘贴的图像数据转换为与被粘贴图像相同的颜色模式。
读者可以通过将从灰度图像裁剪的区域粘贴在彩色图像,或者将从彩色图像裁剪的区域粘贴在灰度图像进行验证。

# Image对象的paste ()方法可以为图像对象在特定位置粘贴图像数据:
#创建图像对象im的拷贝
im_paste=im
#将region_90贴在图像对象im_paste中box对应的位置
im_paste.paste(region_90, box)
im_paste.show()	#显示图像
im_paste.save('python_region_90_paste.png')	#保存图像

输出为:
在这里插入图片描述

PIL可以对多波段图像的每个波段分别处理。
Image对象的getbands()方法可以获得每个波段的名称,split()方法将多波段图像分解为多个单波段图像,merge()方法可以按照用户指定的颜色模式和单波段图像数据的顺序,将它们组合成新的图像。

#需要注意的,粘贴的图像数据必须与粘贴区域具有相同的大小,
#但是,它们的颜色模式可以不同,
#paste()方法在粘贴之前自动将粘贴的图像数据转换为与被粘贴图像相同的颜色模式。
#读者可以通过将从灰度图像裁剪的区域粘贴在彩色图像,或者将从彩色图像裁剪的区域粘贴在灰度图像进行验证。
#PIL可以对多波段图像的每个波段分别处理。
#Image对象的getbands()方法可以获得每个波段的名称,
#split()方法将多波段图像分解为多个单波段图像,
#merge()方法可以按照用户指定的颜色模式和单波段图像数据的顺序,将它们组合成新的图像
#显示每个波段的名称
print(im.getbands())
# ('R', 'G', 'B')
#将RGB彩色图像对象im分解为三个单波段图像(红、绿、蓝)
r, g, b = im.split()
#显示每个波段图像
r.show(title='r')
# g.show(title='g')
# b.show(title='b')

输出为:
在这里插入图片描述

#按照RGB颜色模式,并将波段按蓝、绿、红顺序组合生成新的图像
im_bgr = Image.merge("RGB", (b, g, r))
im_bgr.show(title='BGR')

输出为:
在这里插入图片描述

#Image对象的point()方法用于对图像每个像素的值进行数值运算,由此可以完成图像反色、线性拉伸、归一化等。
from PIL import Image
im_gray=Image.open('python_gray.png')
#将图像数据im_gray进行反色处理
im_gray_inv = im_gray.point(lambda i: 255-i)
im_gray_inv.save('im_gray_inv.png') 	#保存图像
im_gray_inv.show()

输出为:
在这里插入图片描述

#图像的直方图用来表征该图像的像素值的分布情况。
#Image对象的histogram()方法将像素值的范围分成一定数目的小区间,统计落入每个小区间的像素值数目。
#以下代码可以生成一副图像的直方图:
from PIL import Image
from pylab import *
im=Image.open('lena.png')
im.show()

输出为:
在这里插入图片描述

imin = im.convert('L')
im_hist=imin.histogram()	#获得直方图数据
plot(im_hist)

输出为:
在这里插入图片描述

# 利用PIL模块,我们可以调用Image对象的resize()方法调整一副图像的大小,
#该方法接受一个表示新图像大小的元组为参数,返回原图像缩放后的拷贝:
im_half= im.resize((128, 128))	#将im调整为宽和高均为128像素
im_half.show()
# resize()方法中指定的图像宽度和高度也可以不一致,
#新图像的宽度或高度可以比原图像的大(对应图像放大)也可以比原图像的小(对应图像缩小)。
#给一张特别小的图像,当放大该图像时,通常会出现马赛克,我们希望把细节填充进来。
#resize()方法也可以指定两个参数:resize(size, filter) ,
#其中filter
#Image.Resampling.NEAREST (0), Image.Resampling.LANCZOS (1), Image.Resampling.BILINEAR (2), 
# Image.Resampling.BICUBIC (3), Image.Resampling.BOX (4) or Image.Resampling.HAMMING (5)。

输出为:
在这里插入图片描述

#将im调整为宽和高均为128像素
im_four= im_half.resize((1280, 1280),Image.Resampling.BILINEAR)	
im_four.show()

输出为:
在这里插入图片描述

ImageFilter模块

PIL提供的ImageFilter模块包含一组预先定义的滤波器,可以结合Image对象的filter()方法实现图像平滑和增强。
目前,该模块支持BLUR、CONTOUR、DETAIL、EDGE_ENHANCE、EDGE_ENHANCE_MORE、EMBOSS、FIND_EDGES、SMOOTH、SMOOTH_MORE、SHARPEN、GaussianBlur、RankFilter等。

from PIL import Image
from PIL import ImageFilter
boat = Image.open("lena.png")
boat_blur = boat.filter(ImageFilter.BLUR)		#模糊滤波
boat_blur.save('boat_blur.png')
boat_blur.show()

输出为:
在这里插入图片描述

boat_edge = boat.filter(ImageFilter.FIND_EDGES) 		#边缘检测
boat_edge.save('boat_edge.png')
boat_edge.show()

输出为:
在这里插入图片描述

boat_contour = boat.filter(ImageFilter.CONTOUR) 		#找轮廓
boat_contour.save('boat_contour.png')
boat_contour.show()

输出为:
在这里插入图片描述

boat_rank= boat.filter(ImageFilter.RankFilter(9,3))
boat_rank.save('boat_rank.png')		#每个像素取值为它的3*3邻域中第三大的像素值
boat_rank.show()

输出为:
在这里插入图片描述

ImageEnhance模块

PIL中更高级的图像增强可以借助ImageEnhance模块完成,例如,
ImageEnhance.Contrast()用于调整对比度,
ImageEnhance.Sharpness()用于图像锐化,
ImageEnhance.Color()用于图像颜色均衡,
ImageEnhance.Brightness()用于调整图像亮度等等。

from PIL import Image,ImageEnhance
im=Image.open('lena.png')
# ImageEnhance.Contrast()用于调整对比度
im_contrast = ImageEnhance.Contrast(im)
im_contrast.enhance(0.3).show()

输出为:
在这里插入图片描述

for i in [0.3,1.5,3]:
    temp=im_contrast.enhance(i)
    temp.save('boat_enhance'+str(i)+'.png')
    temp.show()

在这里插入图片描述

# ImageEnhance.Sharpness()用于图像锐化
im=Image.open('lena.png')
im_Sharpness = ImageEnhance.Sharpness(im)
im_Sharpness.enhance(0.3).show()

输出为:
在这里插入图片描述

Numpy图像数据分析示例

PIL提供了大量基本的图像处理模块和方法,但当我们需要完成一些高级图像处理任务,或者自定义一组图像处理流程时,还需要借助于其他模块。首先可以考虑的就是提供了向量、矩阵、方程组等操作方法的Numpy。
PIL模块读取的图像数据不能直接与整型、浮点型等数据类型进行运算,我们可以通过array()方法将图像数据转换成Numpy的数组对象,之后利用Numpy执行任意数学操作,完成一些复杂的图像处理流程。Numpy处理后的数据想要调用PIL提供的方法时,再利用Image对象的fromarray()方法创建图像实例。

from PIL import Image
from numpy import *
import numpy as np
boat=array(Image.open('lena.png'))
#对图像像素值进行二次多项式变换
boat_new=255.0*(boat/255.0)**2
#由boat_new创建图像实例
im_boat_new=Image.fromarray(np.uint8(boat_new))
#调研Image对象的show()方法显示图像
im_boat_new.show()

输出为:
在这里插入图片描述

import matplotlib.pylab as plt

image = plt.imread("lena.png")
plt.imshow(image)
# 依次表示数组的维度,形状,数组本身
print(image.ndim, image.shape, "\n",image[0:1])
plt.show()

输出为:
在这里插入图片描述

# 裁剪图像
# 加载图像后,我们可以对图像执行基本的裁剪操作。对于 NumPy,可以通过数组的切片操作来执行裁剪操作。
import matplotlib.pylab as plt
image = plt.imread("lena.png")
# 只是一个:表示这个维度保持不变,410: 表示从0~1280这个轴的index=410 处切一刀
crop_img = image[:, 410:, :]
print(image.shape, crop_img.shape)
plt.imshow(crop_img)
plt.show()

输出为:
在这里插入图片描述

# 分离颜色

# 因为我们知道每个图像都是由像素值组成的,这些像素值代表三个整数或小数,称为其颜色的 RGB 值。
# 为了将这些颜色的图像分开,我们需要拉出图像数组的正确切片。

import numpy as np
import matplotlib.pylab as plt

image = plt.imread("lena.png")
# 使用plot创建一个1行3列的尺寸为(12,8)的绘图区,相当每个是6英寸宽,4英寸高。
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(12, 8))
for c, ax in zip(range(3), axs):
    # 造一个与image数组一样结构,初始值为0的数组
    # 如果你图片数据值是0~255,类型设为unit8,如果是0~1,设置为float64
    rgb_img = np.zeros(image.shape, dtype="float64")
    # 读取image某一通道的值
    # RGB只拷贝C位,按循环依次取值为0,1,2
    rgb_img[:, :, c] = image[:, :, c]
    ax.imshow(rgb_img)
    ax.set_axis_off()
plt.show()

输出为:
在这里插入图片描述

# 转换

# 在此步骤中,我们将执行颜色转换。为此,我们可以将图像像素视为空间中的一个点。以这种方式处理图像像素使我们能够对色点进行转换。
# 旋转色点可以是上述语句的一个例子。
# 在这里,我们应用了 Numpy 的爱因斯坦符号函数,这是一种将旋转矩阵逐像素应用于图像的方法。
import numpy as np
import matplotlib.pylab as plt


def rotation_matrix(theta):
    return np.c_[
        [1, 0, 0],
        [0, np.cos(theta), -np.sin(theta)],
        [0, np.sin(theta), np.cos(theta)]
    ]


image = plt.imread("lena.png")
img_rot = np.einsum("ijk,lk->ijl", image, rotation_matrix(np.pi*0.5))
plt.imshow(img_rot)
plt.show()

输出为:
在这里插入图片描述

NumPy 中的 np.einsum 函数是一个强大的工具,它允许我们使用爱因斯坦求和约定来表达矩阵的乘法和其他数学操作。在 NumPy 中,爱因斯坦求和约定是通过简化多个索引的重复来简化张量的操作。
np.einsum 的调用 "ijk,lk->ijl" 中,我们有以下含义:

  • ijk 表示输入张量 image 的维度,其中 ijk 是索引,分别代表图像的高度、宽度和颜色通道数。
  • lk 表示第二个操作数 rotation_matrix(np.pi) 的维度,其中 lk 是索引,l 代表旋转矩阵的行数(这里是 3),k 代表旋转矩阵的列数(这里是 3)。
  • ijl 表示输出张量的维度,其中 ij 与输入图像的 ijk 相同,代表旋转后图像的高度和宽度,而 l 代表旋转矩阵的行数(这里是 3),因为输出图像的颜色通道数与输入图像相同。
    因此,np.einsum("ijk,lk->ijl", image, rotation_matrix(np.pi*2)) 这行代码的意义是将 image 中的每个像素(由 ijk 索引)与一个旋转矩阵(由 lk 索引)相乘,并生成一个新的图像,其中每个像素的颜色值是通过应用旋转矩阵得到的。这个操作在图像处理中称为像素级的矩阵乘法,它对应于图像的每个像素点在旋转后的空间位置上的颜色值。
    简而言之,这段代码将每个像素的颜色值乘以一个旋转矩阵,从而实现图像的旋转效果。
# 灰度转换

# 我们还可以使用 NumPy 将图像转换为灰度图像。通过取图像RGB值的加权平均值,我们可以执行此操作。
# 灰度值 = 0.2989 * R + 0.5870 * G + 0.1140 * B

import numpy as np
import matplotlib.pylab as plt
fig,ax = plt.subplots(1,2)

image = plt.imread("lena.png")
# plt.imshow(image)
rgb_weights = [0.1140, 0.5870,0.2989]
# image.shape # (759, 753, 3)
# 0.0876 0.22 0.10 = 
# print(image[0:10])
grayscale_image = np.dot(image[..., :3], rgb_weights)
print(grayscale_image.shape)
print(grayscale_image[0:10])

ax[0].imshow(grayscale_image,cmap='Greys')
ax[1].imshow(grayscale_image)
plt.show()

输出为:
在这里插入图片描述

修改下权重系数

# 灰度转换

# 我们还可以使用 NumPy 将图像转换为灰度图像。通过取图像RGB值的加权平均值,我们可以执行此操作。
# 灰度值 = 0.2989 * R + 0.5870 * G + 0.1140 * B

import numpy as np
import matplotlib.pylab as plt
fig,ax = plt.subplots(1,2)

image = plt.imread("lena.png")
# plt.imshow(image)
rgb_weights = [0.2989, 0.5870,0.1140]
# image.shape # (759, 753, 3)
# 0.0876 0.22 0.10 = 
# print(image[0:10])
grayscale_image = np.dot(image[..., :3], rgb_weights)
print(grayscale_image.shape)
print(grayscale_image[0:10])

ax[0].imshow(grayscale_image,cmap='Greys')
ax[1].imshow(grayscale_image)
plt.show()

输出为:

在这里插入图片描述

# 图像分割

# 这是我们分割图像不同区域的最常用的图像处理步骤之一。
#有多种方法可以做到这一点,例如前景和背景。
#例如,在本文中,我们将了解如何通过将图像转换为灰度并找到阈值来执行分割。
# 图像中高于阈值的像素位于一个区域中,而其他像素位于另一个区域中

import numpy as np
import matplotlib.pylab as plt

image = plt.imread("lena.png")
image = image*255


def simple_threshold(img, threshold=128):
    return ((img > threshold) * 255).astype("uint8")


thresholds = [100, 120, 128, 138, 150]

fig, axs = plt.subplots(nrows=1, ncols=len(thresholds), figsize=(20, 5))

rgb_weights = [0.2989, 0.5870, 0.1140]
gray_im = np.dot(image[..., :3], rgb_weights)

# gray_im = to_grayscale(image)

for t, ax in zip(thresholds, axs):
    ax.imshow(simple_threshold(gray_im, t), cmap='Greys')
    ax.set_title("Threshold: {}".format(t), fontsize=20)
    ax.set_axis_off()
plt.show()

输出为:
在这里插入图片描述

OpenCV

OpenCV 的全称是 Open Source Computer Vision Library, 是一个跨平台的计算机视觉库。OpenCV 是由英特尔公司发起并参与开发, 以 BSD 许可证授权发行, 可以在商业和研究领域中免费使用。 OpenCV 可用于开发实时的图像处理、 计算机视觉以及模式识别程序。 该程序库也可以使用英特尔公司的 IPP 进行加速处理。

# pip install opencv-python==4.9.0.80

一、图像处理基础知识

1、读取、显示、保存图像
img = cv2.imread(文件名,[,参数])

其中参数:
参数(1) cv2.IMREAD_UNCHANGED (图像不可变)
参数(2) cv2.IMREAD_GRAYSCALE (灰度图像)
参数(3) cv2.IMREAD_COLOR (读入彩色图像),这是默认参数,不填的话默认为这个
参数(4) cv2.COLOR_BGR2RGB (图像通道BGR转成RGB)

综合实例:

# 1)读取照片:
"""
OpenCV读取图像
"""
# 导包
import cv2
# 读取照片
img = cv2.imread("lena.png")  # 路径中不能有中文
# 显示图片
cv2.imshow("read_img", img)
# 输入毫秒值,传0就是无限等待
cv2.waitKey(3000)
# 释放内存,由于OpenCV底层是C++写的
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

# 3)OpenCV画图,对图片进行编辑
# OpenCV 的强大之处的一个体现就是其可以对图片进行任意编辑处理。下面的这个函数最后一个参数指定的就是画笔的大小。
"""
OpenCV画图,对图片进行编辑
"""
# 导包
import cv2
img = cv2.imread("lena.png")
# 左上角的坐标是(x,y),矩形的宽度和高度是(w,h)
x, y, w, h = 100, 100, 100, 100
# 绘制矩形
cv2.rectangle(img, (x, y, x + w, y + h), color=(255, 0, 0), thickness=2)
# 绘制圆
x, y, r = 200, 200, 100
cv2.circle(img, center=(x, y), radius=r, color=(255, 0, 0), thickness=2)
# 显示图片
cv2.imshow("rectangle_img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

2、获取图像属性

2.1形状- img.shape
通过shape关键字获取图像的形状,返回包含行数、列数、通道数的元祖。其中:
(1)灰度图像返回行数和列数;
(2)彩色图像返回行数、列数 和 通道数。

2.2像素数目- img.size
通过size关键字获取图像的像素数目,其中

(1)灰度图像返回 [行数 * 列数] ;
(2)彩色图像返回 [行数 * 列数 * 通道数] 。
2.3图像类型- img.dtype
通过 dtype 关键字获取图像的数据类型,通常返回uint8。
通过像素矩阵可以直接获取ROI区域,如img[200:400, 200:400]。

3、图像的通道拆分与合并

3.1 通道拆分- split()

OpenCV读取的彩色图像由B、G、R三原色组成,可以通过下面代码获取不同的通道。

方法一:

# -*- coding:utf-8 -*-

"""
b = img[:, :, 0]
g = img[:, :, 1]
r  = img[:, :, 2]
"""
import cv2
import numpy as np
 
# 读取图片
img = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
 
# 拆分通道
b, g, r = cv2.split(img)
 
# 显示原始图像
cv2.imshow("B", b)
cv2.imshow("G", g)
cv2.imshow("R", r)
 
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()
#注意:不要点击显示图像窗口右上角的X关闭,那样会导致图片关闭了,程序还在运行。
#直接按任意键就可

输出为:
在这里插入图片描述

3.2 通道合并- merge()

图像通道合并主要使用 merge() 函数实现。例如:

m = cv2.merge([b, g, r])
# -*- coding:utf-8 -*-
import cv2
import numpy as np
 
# 读取图片
img = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
 
# 拆分通道
b, g, r = cv2.split(img)
 
# 合并通道
m = cv2.merge([b, g, r])
cv2.imshow("Merge", m)
 
# 等待显示
cv2.waitKey(3000)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

4、图像加法与融合运算 和 图像类型转换

4.1图像加法运算
(1)使用Numpy
在这里插入图片描述
在这里插入图片描述
(2)使用OpenCV—add()
在这里插入图片描述
对比试验:

(1) 两个彩色图像相加
代码如下所示:

# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 读取图片
img = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
# img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #取消注释就为转换为灰度图 
# result = 
img = cv2.resize(img, None, fx=0.5, fy=0.5)  # 缩放
test = img 
 
# 方法一:Numpy加法运算
result1 = img + test
 
# 方法二:OpenCV加法运算
result2 = cv2.add(img, test)
 
# 显示图像
cv2.imshow("original", img)
cv2.imshow("result1_Numpy", result1)
cv2.imshow("result2_OpenCv", result2)
 
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

灰度图加法
在这里插入图片描述

4.2图像融合运算- addWeighted()

图像融合通常是指将2张或2张以上的图像信息融合到1张图像上,融合的图像含有更多的信息,能够更方便人们观察或计算机处理。如下图所示,将两张不清晰的图像融合得到更清晰的图。

在这里插入图片描述在这里插入图片描述
主要调用的函数是 addWeighted() 函数,方法如下:

dst = cv2.addWeighter(scr1, alpha, src2, beta, gamma)
dst = src1*alpha + src2*beta + gamma

其中,参数gamma不能省略。
注:两张融合的图像像素大小需要一致

# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 读取图片
src1 = cv2.imread('lena.png')
src2 = cv2.imread('shuji.jpg')

src1 = cv2.resize(src1, None, fx=0.5, fy=0.5)  # 缩放
src2 = cv2.resize(src2, (src1.shape[1],src1.shape[0]), interpolation=cv2.INTER_LINEAR)  # 缩放

print(type(src1),type(src2))
print(src1.shape,src2.shape)
# # 图像融合
result = cv2.addWeighted(src1, 1, src2, 1, 0)
 
# 显示图像
cv2.imshow("src1", src1)
cv2.imshow("src2", src2)
cv2.imshow("result", result)
 
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

1)设置参数1

# 图像融合
# result = cv2.addWeighted(src1, 1, src2, 1, 0)
result = cv2.addWeighted(src1, 0.8, src2, 0.2, 10)

运行结果如下:
在这里插入图片描述

(2)设置参数2

# 图像融合
# result = cv2.addWeighted(src1, 1, src2, 1, 0)
#result = cv2.addWeighted(src1, 0.8, src2, 0.2, 10)
result = cv2.addWeighted(src1, 0.2, src2, 0.8, 10)

运行结果如下:

在这里插入图片描述

4.3图像类型转换- cvtColor()
图像类型转换是指将一种类型转换为另一种类型,比如彩色图像转换为灰度图像、BGR图像转换为RGB图像。OPenCV提供了200多种不同类型之间的转换,其中最常用的包括3类,如下:

result = cv2.cvtColor(图像, 参数) 

其中,参数有以下三种:

cv2.COLOR_BGR2GRAY #彩色转灰度 类似于Matlab 中的 rgb2gray()
cv2.COLOR_BGR2RGB
cv2.COLOR_GRAY2BGR

图片灰度转换
OpenCV 中有数百种关于在不同色彩空间之间转换的方法。 当前, 在计算机视觉中有三种常用的色彩空间:灰度、BGR以及 HSV(Hue, Saturation, Value)。
灰度转换的作用就是: 转换成灰度的图片的计算强度得以降低。
灰度色彩空间是通过去除彩色信息来将其转换成灰阶, 灰度色彩空间对中间处理特别有效, 比如人脸识别。
BGR 及蓝、 绿、 红色彩空间, 每一个像素点都由一个三元数组来表示, 分别代表蓝、 绿、 红三种颜色。 网页开发者可能熟悉另一个与之相似的颜色空间: RGB 它们只是颜色顺序上不同。
HSV, H(Hue) 是色调, S(Saturation) 是饱和度, V(Value) 表示黑暗的程度(或光谱另一端的明亮程度)。
OpenCV图片灰度转换:

"""
OpenCV进行灰度转换
"""
import cv2
img = cv2.imread("lena.png")
cv2.imshow("BGR_IMG", img)
# 将图片转化为灰度
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 展示图片
cv2.imshow("gray_img", gray_img)
# 设置展示时间
cv2.waitKey(3000)
# 保存图片
cv2.imwrite("./gray_lena.jpg", gray_img)
# 释放内存
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

BRG通道转RGB通道

代码如下:


"""
OpenCV进行BGR2RGB
"""
import cv2
img = cv2.imread("lena.png")
cv2.imshow("BGR_IMG", img)
# 将图片转化为灰度
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 展示图片
cv2.imshow("BGR_img", img)
cv2.imshow("rgb_img", rgb_img)
# 设置展示时间
cv2.waitKey(5000)
# 保存图片
# cv2.imwrite("./rgb_lena.jpg", rgb_img)
# 释放内存
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

5、图像缩放、图像旋转、图像翻转 和 图像平移

5.1图像缩放- resize()
图像缩放主要调用 resize() 函数实现,具体如下:

result = cv2.resize(src, dsize[, result[. fx[, fy[, interpolation]]]])
'''
其中,参数
src 表示原始图像;
dsize 表示缩放大小;
fx和fy 也可以表示缩放大小倍数,他们两个(dsize或fx/fy)设置一个即可实现图像缩放。例如:
(1)result = cv2.resize(src, (160,160))
(2)result = cv2.resize(src, None, fx=0.5, fy=0.5)
'''

(1) cv2.resize(src, (200,100)) 设置的dsize是列数为200,行数为100

result = cv2.resize(src, (200,100))

代码如下:

import cv2
import numpy as np
 
# 读取图片
src = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
 
# 图像缩放
# cv2.resize(src, (200,100)) 设置的dsize是列数为200,行数为100
result = cv2.resize(src, (200,100))
print (result.shape)
 
# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)
 
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

(2)可以获取 原始图像像素\times乘以缩放系数 进行图像变换;

result = cv2.resize(src, (int(cols * 0.6), int(rows * 1.2)))

代码如下所示:

import cv2
import numpy as np
 
# 读取图片
src = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
rows, cols = src.shape[:2]
print(rows, cols)

# 图像缩放 dsize(列,行)
result = cv2.resize(src, (int(cols * 0.6), int(rows * 1.2)))
 
# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)
 
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

3)(fx,fy) 缩放倍数的方法对图像进行放大或缩小。

result = cv2.resize(src, None, fx=0.3, fy=0.3)

代码如下所示:

# encoding:utf-8
import cv2
import numpy as np
 
# 读取图片
src =cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
rows, cols = src.shape[:2]
print(rows, cols)

 
# 图像缩放
result = cv2.resize(src, None, fx=0.3, fy=0.3)
 
# 显示图像
cv2.imshow("src", src)
cv2.imshow("result", result)
 
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述
5.2 图像旋转- getRotationMatrix2D(), warpAffine()
在这里插入图片描述
图像旋转主要调用getRotationMatrix2D() 函数和 warpAffine() 函数实现,绕图像的中心旋转,具体如下:

M = cv2.getRotationMatrix2D((cols/2, rows/2), 30, 1)

其中,参数分别为:旋转中心、旋转度数、scale缩放比例

rotated = cv2.warpAffine(src, M, (cols, rows))

其中,参数分别为:原始图像、旋转参数 和 原始图像宽高

(1)旋转30度

代码如下:

import cv2
import numpy as np
 
# 读取图片
src =  cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
 
# 原图的高、宽 以及通道数
rows, cols, channel = src.shape
 
# 绕图像的中心旋转
# 参数:旋转中心 旋转度数 scale
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 30, 1)
# M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 90, 1)
# 参数:原始图像 旋转参数 元素图像宽高
rotated = cv2.warpAffine(src, M, (cols, rows))
 
# 显示图像
cv2.imshow("src", src)
cv2.imshow("rotated", rotated)
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

5.3 图像翻转- flip()

图像翻转在OpenCV中调用函数 flip() 实现,函数用法如下:

dst = cv2.flip(src, flipCode)
'''
其中,参数:
src 表示原始图像;
flipCode 表示翻转方向,如果flipCode为0,则以X轴为对称轴翻转,如果fliipCode>0则以Y轴为对称轴翻转,如果flipCode<0则在X轴、Y轴方向同时翻转。
'''

代码如下:(注意一个窗口多张图像的用法)

import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 读取图片
img = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
src = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
 
# 图像翻转
# 0以X轴为对称轴翻转 >0以Y轴为对称轴翻转 <0X轴Y轴翻转
img1 = cv2.flip(src, 0)
img2 = cv2.flip(src, 1)
img3 = cv2.flip(src, -1)
 
# 显示图形 (注意一个窗口多张图像的用法)
titles = ['Source', 'Image1', 'Image2', 'Image3']
images = [src, img1, img2, img3]
for i in range(4):
    plt.subplot(2, 2, i + 1)
    plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()
 
# 等待显示
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

5.4 图像平移- warpAffine()

在这里插入图片描述
图像平移首先定义平移矩阵M,再调用 warpAffine() 函数实现平移,函数用法如下:

M = np.float32([[1, 0, x], [0, 1, y]])
shifted = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

代码如下:

# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 读取图片
img = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
 
# 图像平移 下、上、右、左平移
M = np.float32([[1, 0, 0], [0, 1, 100]])
img1 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
 
M = np.float32([[1, 0, 0], [0, 1, -100]])
img2 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
 
M = np.float32([[1, 0, 100], [0, 1, 0]])
img3 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
 
M = np.float32([[1, 0, -100], [0, 1, 0]])
img4 = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
 
# 显示图形
titles = ['Image1', 'Image2', 'Image3', 'Image4']
images = [img1, img2, img3, img4]
for i in range(4):
    plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

输出为:
在这里插入图片描述

二、图像处理进阶

1、图像阈值化-threshold()

灰度转换处理后的图像中,每个像素都只有一个灰度值,其大小表示明暗程度。二值化处理可以将图像中的像素划分为两类颜色,

其中,当灰度Gray小于阈值T时,其像素设置为0,表示黑色;
当灰度Gray大于或等于阈值T时,其Y值为255,表示白色。

Python OpenCV中提供了阈值函数 threshold() 实现二值化处理,其函数形式及参数如下图所示:

retval, dst = cv2.threshold(src, thresh, maxval, type)
'''
其中,参数:
retval:阈值        
dst: 处理结果
src,:原图像
thresh:阈值
maxval:最大值
type:类
'''

在这里插入图片描述
在这里插入图片描述对应OpenCV提供的五张图如下所示,第一张为原图,后面依次为:
二进制阈值化、反二进制阈值化、截断阈值化、反阈值化为0 和 阈值化为0 。
在这里插入图片描述
二值化处理广泛应用于各行各业,比如生物学中的细胞图分割、交通领域的车牌设别等。在文化应用领域中,通过二值化处理将所需民族文物图像转换为黑白两色图,从而为后面的图像识别提供更好的支撑作用。下图表示图像经过各种二值化处理算法后的结果,其中“BINARY”是最常见的黑白两色处理。

指定一个阈值thresh

(1)二进制阈值化–cv2.THRESH_BINARY
像素灰度值>thresh,设为最大灰度值(如:8位灰度值最大为255,以下都以8位灰度图为例)
像素灰度值<thresh,设为0

(2)反二进制阈值化–cv2.THRESH_BINARY_INV
与二进制阈值化相反,>thresh,设为0
<thresh,设为255

(3)截断阈值化–cv2.THRESH_TRUNC
像素灰度值>thresh,设为thresh
像素灰度值<thresh,不变

(4)阈值化为0–cv2.THRESH_TOZERO
像素灰度值>thresh,不变
像素灰度值><thresh,设为0

(5)反阈值化为0–cv2.THRESH_TOZERO_INV
与阈值化为0正相反, >thresh,设为0
<thresh,不变

总代码:

import cv2
import numpy as np
import matplotlib.pyplot as plt
 
#读取图像
img = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)
lenna_img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
GrayImage=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
 
#阈值化处理
ret,thresh1=cv2.threshold(GrayImage,127,255,cv2.THRESH_BINARY)
ret,thresh2=cv2.threshold(GrayImage,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3=cv2.threshold(GrayImage,127,255,cv2.THRESH_TRUNC)
ret,thresh4=cv2.threshold(GrayImage,127,255,cv2.THRESH_TOZERO)
ret,thresh5=cv2.threshold(GrayImage,127,255,cv2.THRESH_TOZERO_INV)
 
#显示结果
titles = ['Gray Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [GrayImage, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

输出为:
在这里插入图片描述

2、图像平滑(均值滤波、中值滤波、高斯滤波)—空域滤波

图像增强是对图像进行处理,使其比原始图像更适合于特定的应用,它需要与实际应用相结合。对于图像的某些特征如边缘、轮廓、对比度等,图像增强是进行强调或锐化,以便于显示、观察或进一步分析与处理。图像增强主要是一个主观过程,而图像复原大部分是一个客观过程。图像增强的方法是因应用不同而不同的,研究内容包括:
在这里插入图片描述

图像平滑是一种区域增强的算法,平滑算法有邻域平均法、中指滤波、边界保持类滤波等。在图像产生、传输和复制过程中,常常会因为多方面原因而被噪声干扰或出现数据丢失,降低了图像的质量(某一像素,如果它与周围像素点相比有明显的不同,则该点被噪声所感染)。这就需要对图像进行一定的增强处理以减小这些缺陷带来的影响。

(1)均值滤波–cv2.blur(原始图像,核大小)
指任意一点的像素值,都是周围 N * M 个像素值的均值
注:
1)随着核大小逐渐变大,会让图像变得更加模糊;
2)如果设置为核大小为(1,1),则结果就是原始图像。

(2)中值滤波–cv2.medianBlur(src, ksize)
这里的核大小ksize必须是奇数
将该点周围的像素点包括本身,按次序排列,取中位数作为点的像素值
注:
1)随着核大小逐渐变大,会让图像变得更加模糊;
2)核必须是大于1的奇数,如3、5、7等;
3)在代码 dst = cv2.medianBlur(src, ksize) 中 填写核大小时,只需填写一个数即可,如3、5、7等,对比均值滤波函数用法。

(3)高斯滤波–cv2.GaussianBlur(src, ksize, sigmaX)
为了克服简单局部平均法的弊端(图像模糊),目前已提出许多保持边缘、细节的局部平滑算法。它们的出发点都集中在如何选择邻域的大小、形状和方向、参数加平均及邻域各店的权重系数等。
图像高斯平滑也是邻域平均的思想对图像进行平滑的一种方法,在图像高斯平滑中,对图像进行平均时,不同位置的像素被赋予了不同的权重。高斯平滑与简单平滑不同,它在对邻域内像素进行平均时,给予不同位置的像素不同的权值,下图的所示的 3x3 和 5x5 邻域的高斯模板。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

生成椒盐噪声

# encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt
#读取图片
image = cv2.imread("lena.png", cv2.IMREAD_UNCHANGED)

#设置添加椒盐噪声的数目比例
s_vs_p = 0.01
#设置添加噪声图像像素的数目
amount = 0.01
noisy_img = np.copy(image)
#添加salt噪声
num_salt = np.ceil(amount * image.size * s_vs_p)
#设置添加噪声的坐标位置
coords = [np.random.randint(0,i - 1, int(num_salt)) for i in image.shape]
noisy_img[coords[0],coords[1],:] = [255,255,255]
#添加pepper噪声
# num_pepper = np.ceil(amount * image.size * (1. - s_vs_p))
#设置添加噪声的坐标位置
# coords = [np.random.randint(0,i - 1, int(num_pepper)) for i in image.shape]
# noisy_img[coords[0],coords[1],:] = [0,0,0]
# 如果去掉上面注释可以添加更多噪声


#保存图片
cv2.imwrite("noisy_lena.png",noisy_img)

# 显示图像
cv2.imshow("noisy_lena", noisy_img)
# 等待显示
cv2.waitKey(5000)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述
去掉注释的更多噪声点
在这里插入代码片

import cv2
import numpy as np
import matplotlib.pyplot as plt
 
# 读取图片
img = cv2.imread('lena.png')
noisy_img = cv2.imread('noisy_lena.png')
source = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
noisy_img = cv2.cvtColor(noisy_img, cv2.COLOR_BGR2RGB)

# 高斯滤波
result = cv2.GaussianBlur(noisy_img, (3, 3), 0) #可以更改核大小
# result = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
 
# 显示图形
titles = ['Source Image', 'noisy_img','GaussianBlur Image (3, 3)']
images = [source, noisy_img,result]

plt.figure(figsize=(30,10),dpi=300)
for i in range(3):
    plt.subplot(1, 3, i + 1)
    plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

输出为:

在这里插入图片描述

注:
1)随着核大小逐渐变大,会让图像变得更加模糊;
2)核大小(N, N)必须是大于1的奇数,如3、5、7等;

3、形态学处理–腐蚀、膨胀、开运算、闭运算、顶帽运算、黑帽运算

形态学运算主要针对的二值图像

(1)腐蚀
卷积核中心逐个遍历图像像素,当卷积核范围内全是原图像时,不改变原像素值,当卷积核范围内有除了原图像以外的区域时,将此范围内的原图像像素值置为0,即腐蚀这个像素点。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)膨胀
卷积核中心逐个遍历图像像素,当卷积核范围内有一个原图像的像素点时,将卷积核范围内设为1,即将该像素点膨胀为卷积核大小的区域
在这里插入图片描述
在这里插入图片描述

(3)开运算–先腐蚀再膨胀
开运算一般会平滑物体的轮廓、断开较窄的狭颈并消除细的突出物。

(4)闭运算–先膨胀再腐蚀
闭运算同样也会平滑轮廓的一部分。但与开操作相反,它通常会弥合较窄的间断和细长的沟壑,消除小的孔洞,填补轮廓线中的断裂。
在这里插入图片描述

(5)顶帽运算–原始减去开运算
在这里插入图片描述

(6)黑帽运算–闭运算减去原始
在这里插入图片描述

总结
腐蚀:cv2.erode(src, kernel, iterations) #iterations表示腐蚀几次
膨胀:cv2.dilate(src, kernel, iterations)
开运算:cv2.morphologyEx(src, cv2.MORPH_OPEN, kernel)
闭运算:cv2.morphologyEx(src, cv2.MORPH_CLOSE, kernel)

import cv2
import numpy as np
 
#读取图片
src = cv2.imread('lena.png', cv2.IMREAD_UNCHANGED)
 
src=cv2.cvtColor(src,cv2.COLOR_BGR2GRAY)
 
#阈值化处理 ret为127 为二值化的结果
ret,src_1=cv2.threshold(src,127,255,cv2.THRESH_BINARY)

#设置卷积核
kernel = np.ones((7,7), np.uint8)
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
 
'''形态学6种操作'''
 
#腐蚀
fushi = cv2.erode(src_1, kernel) 
#膨胀
pengzhang = cv2.dilate(src_1, kernel)
#开运算
kai = cv2.morphologyEx(src_1, cv2.MORPH_OPEN, kernel)
#闭运算
bi = cv2.morphologyEx(src_1, cv2.MORPH_CLOSE, kernel)
#顶帽运算
tophat = cv2.morphologyEx(src_1, cv2.MORPH_TOPHAT, kernel)
#黑帽运算
blackhat = cv2.morphologyEx(src_1, cv2.MORPH_BLACKHAT, kernel)

 
# 显示图形
titles = ['Source Image','threshold Image', 'fushi','pengzhang','kai','bi','tophat','blackhat']
images = [src,src_1, fushi,pengzhang,kai,bi,tophat,blackhat]

plt.figure(figsize=(20,10),dpi=300)
for i in range(8):
    plt.subplot(2, 4, i + 1)
    plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([])
    plt.yticks([])
plt.show()

输出为:
在这里插入图片描述

卷积核的设置

(1)用numpy生成
np.ones((3,3), np.uint8) #一般用这个也够了
np.array([[-1,-1,-1],[-1,8,-1],[-1,-1,-1]])
(2)用opencv自带函数–getStructuringElement
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5)) #十字型
element = cv2.getStructuringElement(cv2.MORPH_RECT,(5,5)) #矩形
element = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5)) #椭圆形

4、灰度直方图

4.1 绘制灰度直方图

(1)matplotlib—hist()函数
使用matplotlib的子库pyplot实现,它提供了类似于Matlab的绘图框架,matplotlib是非常强大基础的一个Python绘图包。其中绘制直方图主要调用 hist() 函数实现,它根据数据源和像素级绘制直方图。
hist()函数形式如下:

hist(数据源, 像素级)
'''
其中,参数:
数据源:必须是一维数组,通常需要通过函数 ravel() 拉直图像
像素级:一般是256,表示[0, 255]
函数 ravel() 将多维数组降为一维数组,其格式为:
一维数组 = 多维数组.ravel()
'''

在这里插入图片描述

import cv2
import numpy as np
import matplotlib.pyplot as plt
 
src = cv2.imread('lena.png')
cv2.imshow("src", src)
 
plt.hist(src.ravel(), 256)
plt.show()
 
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述
(2) opencv----calcHist()函数

使用OpenCV库 中的 calcHist() 函数 计算B、G、R通道的灰度级并绘制图形

hist = cv2.calcHist(images, channels, mask, histSize, ranges, accumulate)
'''
其中,参数:
 hist 表示直方图,返回的是一个二维数组;
 images 表示原始图像;
 channels 表示指定通道,通道编号需要用中括号括起,输入图像是灰度图像时,它的值为[0],
 彩色图像则为[0]、[1]、[2],分别表示B、G、R;
 mask 表示掩码图像,统计整副图像的直方图,设为None,统计图像的某一部分直方图时,需要掩码图像;
 histSize 表示BINS的数量,参数子集的数目,bins=3表示三个灰度级;
 ranges 表示像素值范围,例如[0, 255];
 accumulate 表示累计叠加标识,默认为false,如果被设置为true,则直方图在开始分配时不会被清零,该参数允许从多个对象中计算单个直方图,或者用于实时更新直方图;多个直方图的累积结果用于对一组图像的直方图计算。
'''
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
src = cv2.imread('lena.png')

cv2.imshow("src", src)

histb = cv2.calcHist([src], [0], None, [256], [0,255]) #掩摸处理,求的是掩摸区域的直方图
histg = cv2.calcHist([src], [1], None, [256], [0,255]) #不做掩膜处理,直接求整幅图(G分量灰度图)的直方图
histr = cv2.calcHist([src], [2], None, [256], [0,255])
# print(histr)

 
plt.plot(histb, color='b')
plt.plot(histg, color='g')
plt.plot(histr, color='r')
plt.show()
 
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

调整亮度和对比度
代码如下:

import cv2
import numpy as np
import matplotlib.pyplot as plt
 
src_old = cv2.imread('lena.png')
cv2.imshow("src_old", src_old)

#定义alpha和beta
alpha = 1.5 #对比度控制
beta  = 10  #亮度控制

#调用convertScaleAbs函数
src = cv2.convertScaleAbs(src_old, alpha=alpha, beta=beta)
cv2.imshow("src", src)

histb = cv2.calcHist([src], [0], None, [256], [0,255]) #掩摸处理,求的是掩摸区域的直方图
histg = cv2.calcHist([src], [1], None, [256], [0,255]) #不做掩膜处理,直接求整幅图(G分量灰度图)的直方图
histr = cv2.calcHist([src], [2], None, [256], [0,255])
# print(histr)

 
plt.plot(histb, color='b')
plt.plot(histg, color='g')
plt.plot(histr, color='r')
plt.show()
 
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述
在这里插入图片描述

4.2直方图均衡化-equalizeHist()

(1) 直方图均衡化概念
直方图均衡化 (Histogram Equalization) 就是把一个已知灰度概率密度分布的图像经过一种变换,使之演变为一幅具有均匀灰度概率密度分布的新图像。
如下图所示,过暗和过亮的图像 经过直方图均衡化,使得图像变得清晰。
在这里插入图片描述
直方图均衡化处理的“中心思想”是把原始图像的灰度直方图从比较集中的某个灰度区间变成在全部灰度范围内的均匀分布。直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度范围内的像素数量大致相同。直方图均衡化就是把给定图像的直方图分布改变成“均匀”分布直方图分布。

(2)直方图均衡化的理论基础
前提:如果一幅图像占有全部可能的灰度级,并且均匀分布。
结论:该图像具有高对比度和多变的灰色色调。
外观:图像细节丰富,质量更高。

(3)直方图均衡化的步骤
a)计算累计直方图;
b)将累计直方图进行区间转换;
c)在累计直方图中,概率相近的原始值,会被处理为相同的值。
具体的例子如下:
如下图所示,已知一幅图像的像素分布为 7*7,根据像素值,则可以计算出统计直方图
在这里插入图片描述
根据统计直方图,可以算出归一化直方图(即纵坐标转换为概率)和累计直方图,如下图所示:
在这里插入图片描述将累计直方图进行区间转换,如下图所示:
在这里插入图片描述
由上图的结果可知,原先8个灰度级转变成6个灰度级,那么原始直方图和均衡直方图为:
在这里插入图片描述
上面的灰度级是8,那灰度级转变成256,计算方法类似,如下图所示:
在这里插入图片描述
由上图可以看出,虽然二者相似,但右侧均衡化后的直方图分布更均匀,相邻像素级概率和与高概率近似相等。如果将两张图的灰度级放在同一区间,可以看出差别更大,如下图所示:
在这里插入图片描述

4)直方图均衡化应用场景
(a)医学图像处理
(b)车牌照识别
(c)人脸识别

(5)函数实现-equalizeHist()
OpenCV库下,直方图均衡化使用 equalizeHist() 函数,函数用法如下所示:

dst=cv2.equalizeHist(src)
'''
其中,参数:
         dst 表示处理结果
         src 表示原始图像
'''

代码如下:

#encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE)
equ = cv2.equalizeHist(img)
 

cv2.imshow("src", img)
cv2.imshow("result", equ)

plt.figure(figsize=(20,8))
fig,ax =plt.subplots(1,2)

ax[0].hist(img.ravel(), 256)
ax[0].set_xlim(0,255)
# plt.figure()
ax[1].hist(equ.ravel(), 256)
ax[1].set_xlim(0,255)
plt.show()
 
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述
在这里插入图片描述

matplotlib.pyplot.subplot() 函数
matplotlib.pyplot.subplot() 函数可以将多张图像放在一个窗口内,Pyhton下需要导入 matplotlib.pyplot 绘图包,其用法和Matlab中的subplot()函数用法类似。matplotlib 是一个强大的绘图包。subplot() 函数用法如下所示:

代码如下:

#encoding:utf-8
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('zxp.jpg', cv2.IMREAD_GRAYSCALE)
equ = cv2.equalizeHist(img)
 
plt.subplot(221),plt.imshow(img, 'gray'),plt.title('img'), plt.xticks([]),plt.yticks([])
plt.subplot(222),plt.imshow(equ, 'gray'),plt.title('equ'), plt.xticks([]),plt.yticks([])
plt.subplot(223),plt.hist(img.ravel(),256),plt.title('img_hist')
plt.subplot(224),plt.hist(equ.ravel(),256),plt.title('equ_hist')
 
plt.show()
 
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

matplotlib.pyplot.imshow() 函数
imshow() 函数用法如下所示,同样的,Pyhton下需要导入 matplotlib.pyplot 绘图包。

imshow(X, cmap=None)
'''
其中,参数:
X 表示要绘制的图像;
cmap 表示colormap,颜色图谱,默认为RGB(A)颜色空间:
    1)对于灰度图像,使用参数 “ cmap=plt.cm.gray ”;
    2)对于彩色图像,如果使用opencv读入的图像,默认空间为BRG,需要调整色彩空间为RGB。
'''

下面是分别使用函数读取灰度图像和彩色图像例子。

(1)灰度图像

import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('lena.png')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
plt.subplot(221),plt.imshow(img),plt.title('img'), plt.axis('off') #坐标轴关闭
plt.subplot(222),plt.imshow(img, cmap=plt.cm.gray),plt.title('img_cmap'), plt.axis('off')
 
plt.subplot(223),plt.imshow(img_gray),plt.title('img_gray'), plt.axis('off')
plt.subplot(224),plt.imshow(img_gray, cmap=plt.cm.gray),plt.title('img_gray_cmap'),plt.axis('off')#正确用法
 
plt.show()
 
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

(2)彩色图像

代码如下所示:

import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('lena.png')
b,g,r=cv2.split(img) #通道分割
img_RGB=cv2.merge([r,g,b])#通道组合
 
plt.subplot(121),plt.imshow(img),plt.title('img_BGR'), plt.axis('off') #坐标轴关闭
plt.subplot(122),plt.imshow(img_RGB),plt.title('img_RGB'), plt.axis('off')

输出为:
在这里插入图片描述

5、频域滤波—高通滤波、低通滤波

频率域图像增强首先通过傅里叶变换将图像从空间域转换为频率域,然后在频率域对图像进行处理,最后通过傅里叶反变换转换到空间域。频率域内的图像增强包括低通滤波、高通滤波、同态滤波等等。
5.1 傅里叶变换

傅里叶变换
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

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

5.2 傅里叶变换的实现

(1)numpy实现
f = numpy.fft.fft2(img) :实现傅里叶变换,返回一个复数数组(complex ndarray)
fshift = numpy.fft.fftshift(f) : 将低频分量移到频谱中心(傅里叶变换后的低频是在频谱左上角,为了观看方便,移到中心)
在这里插入图片描述
以上得到的是复数,没法显示为图像,要显示为图像,需要将复数转换为灰度值[0-255]范围,就可以观察频谱图像了
在这里插入图片描述

注意:
(1)傅里叶得到低频、高频信息,针对低频、高频能够实现不同的目的
(2)傅里叶过程是可逆的,图像经过傅里叶变换、逆傅里叶变换,能够恢复到原始图像
(3)在频域对图像进行处理,在频域的处理会反应在逆变换图像上

逆傅里叶变换的实现–从频谱图像恢复到原始图像
numpy.fft.ifft2(): 实现逆傅里叶变换,返回一个复数数组
numpy.fft.ifftshift() :fftshift的逆函数,将低频信息由中心移到左上角
iimg = np.abs(逆傅里叶变换的结果) : 将逆傅里叶变换的结果不能直接显示为图像,需要变为灰度值,显示为图像

代码如下:

import cv2 
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('lena.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 实现傅里叶变换
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
result = 20*np.log(np.abs(fshift))
# 实现傅里叶逆变换


fshift_r = np.fft.ifftshift(fshift)
f_r = np.fft.ifft2(fshift_r)
result_r = np.abs(f_r)
# result = 20*np.log(np.abs(fshift))

plt.subplot(131)
plt.imshow(img, cmap = 'gray')
plt.title('original')
plt.axis('off')
plt.subplot(132)
plt.imshow(result, cmap = 'gray')
plt.title('result')
plt.axis('off')

plt.subplot(133)
plt.imshow(result_r, cmap = 'gray')
plt.title('result_r')
plt.axis('off')


# numpy.fft.ifft2(): 实现逆傅里叶变换,返回一个复数数组
# numpy.fft.ifftshift() :fftshift的逆函数,将低频信息由中心移到左上角
# iimg = np.abs(逆傅里叶变换的结果) : 将逆傅里叶变换的结果不能直接显示为图像,需要变为灰度值,显示为图像

plt.show()

输出为:
在这里插入图片描述

高通滤波:
低频和高频:

低频对应图像内变化缓慢的灰度分量,高频相反。
例如一幅大草原图像,低频对应着颜色大体一致的绿色草原,草原上的狮子边缘对应着高频。

滤波:让低频通过的滤波器称为低通滤波器,反之为高通滤波器。

低通滤波将模糊一幅图像(丢失边缘),高通滤波将增强尖锐的细节,图像的边缘会被保留,细节会丢失掉。但是会导致图像的对比度降低。

频域滤波:修改傅里叶变换以达到特殊目的,然后计算逆傅里叶变换(IDFT)返回到图像域(空域)。可以实现图像增强、图像去燥、边缘检测、特征提取、压缩、加密等等。
在这里插入图片描述
将低频的地方置为0
在这里插入图片描述

import cv2
import numpy as np 
import matplotlib.pyplot as plt
img = cv2.imread('lena.png', 0)

rows, cols = img.shape[:2]

f = np.fft.fft2(img)    #傅里叶变换
fshift = np.fft.fftshift(f)   #将低频移到中心
rows,cols = int(rows/2), int(cols/2)  #获取图像中心点
fshift[rows-30:rows+30, cols-30:cols+30] = 0  #将中心点周围30区域置为0,即将低频信息去掉,实现高通
                                            #滤波
ishift = np.fft.ifftshift(fshift)   #将低频移还原到左上角
iimg = np.fft.ifft2(ishift)   #逆傅里叶变换
iimg = np.abs(iimg)    #将逆傅里叶变换的结果转换为灰度值,显示为图像
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122), plt.imshow(iimg, cmap='gray')
plt.title('iimg'),plt.axis('off')
plt.show()

输出为:
在这里插入图片描述

(2)opencv实现–cv2.dft()

result = cv2.dft(原始图像, 转换标识)
'''
注意:
(1)返回结果是双通道的,第1个通道是结果的实数部分,第2个通道是结果的虚数部分
(2)输入的图像首先要转化成np.float32格式, 语法:np.float32(img)
(3)转换标识:flags = cv2.DFT_COMPLEX_OUTPUT, 输出是一个复数阵列
numpy.fft.fftshift():将低频分量转移到频谱中心
返回值 = cv2.magnitude(参数1, 参数2) : 将傅里叶变换结果转换为灰度值,进行显示
参数1:浮点型X坐标值,也就是实部
参数2:浮点型Y坐标值,也就是虚部
'''

代码示例:

import numpy as np 
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('lena.png', 0)
dft = cv2.dft(np.float32(img), flags = cv2.DFT_COMPLEX_OUTPUT)        #傅里叶变换
dftShift = np.fft.fftshift(dft)                                       #低频移到中心位置
result = 20*np.log(cv2.magnitude(dftShift[:,:,0], dftShift[:,:,1]))   #转换为灰度值,显示
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122),plt.imshow(result, cmap='gray')
plt.title('result'), plt.axis('off')
plt.show()

输出为:
在这里插入图片描述
opencv进行逆傅里叶变换
将经过傅里叶变换并处理的频域图像,进行逆傅里叶变换为原图

返回结果=cv2.idft(原始数据)
'''
返回结果:取决于原始数据的类型和大小
原始数据:实数或者复数均可
'''

代码如下:

import cv2 
import numpy as np
import matplotlib.pyplot as plt
o = cv2.imread('lena.png', 0)

# 傅里叶结果
dft = cv2.dft(np.float32(o), flags=cv2.DFT_COMPLEX_OUTPUT)  #傅里叶变换,flags不能省
dshift = np.fft.fftshift(dft)                         #低频从左上角移到中心
result = 20*np.log(cv2.magnitude(dftShift[:,:,0], dftShift[:,:,1]))   #转换为灰度值,显示
# 傅里叶逆变换
ishift = np.fft.ifftshift(dshift)                     #从中心移到左上角,还原
io = cv2.idft(ishift)                                 #逆傅里叶变换
io = cv2.magnitude(io[:,:,0], io[:,:,1])              #转换为灰度值, [0-255]
 
plt.subplot(131)
plt.imshow(o, cmap='gray')
plt.axis('off')
plt.title('original')

plt.subplot(132)
plt.imshow(result, cmap='gray')
plt.axis('off')
plt.title('fly')

plt.subplot(133)
plt.imshow(io, cmap='gray')
plt.axis('off')
plt.title('inverse')
plt.show()    

输出为:
在这里插入图片描述

6、图像梯度

6.1 sobel算子
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

函数实现

dst = cv2.Sobel(src, ddepth, dx, dy,  [ksize])
'''
dst:计算结果
src:原始图像
ddepth :处理结果图像深度
dx:x轴方向
dy:y轴方向
ksize:核大小
ddepth参数说明:
一般,通常直接设置为-1,表示让处理结果与原始图像保持一致
但是此处需要注意,不能直接置为-1
'''

举例说明

在这里插入图片描述
在256色位图中,白色点像素值为255,黑色点像素值为0
A:右边减去左边为0-255=-255,是负数,会直接截断,处理为0,所以这个边界显示不出来,
B:右边为255-0=255可以显示出来
说明直接计算,只能得到一个边界
故,需要取绝对值才行
在这里插入图片描述
因此,在写ddepth参数的时候,不能让它为-1,需要设置成cv2.CV_64F
表示如果出现负数,先保留负数的值,而不是直接将它截断置为0,那样就找不到边界了
现在怎么处理这个负数呢?

dst = cv2.converScaleAbs(src, [, alpha[, beta]])
'''
作用:把负数的值取绝对值,无论边界是正负,都能获取
dx dy参数说明
计算x方向梯度: dx=1,dy=0
计算y方向梯度:dx=0,dy=1
'''

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

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

6.2 scharr算子 --比sobel算子效果更好

使用3*3的sobel算子时,可能不太清楚吗,使用scharr算子,效果更好。
在这里插入图片描述

函数实现:

dst = Scharr(src, ddpeth, dx, dy)
'''
src:原始图像
ddepth:处理结果图像深度
dx:x轴方向
dy:y轴方向
注意:
ddepth参数:一般在别的图像处理函数中,直接置为-1,表示处理成与原图像一样深度的图像,但是由于此函数处理过程中会出现负数,故与sobel算子一样,将它赋值为cv2.CV_64F
然后再转为unit8正数
'''
dst = Scharr(src, cv2.CV_64F, dx, dy)
dst = cv2.convertScaleAbs(dst)

在这里插入图片描述

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

sobel和 scharr算子的比较
(1)大小是一样的,说明计算起来的工作量是一样的
但是scharr精确度更高,给了临近像素更高的权重
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

6.3 laplacian算子

拉普拉斯算子类似于二阶sobel导数。实际上,在OpenCV中通过调用sobel算子来计算拉普拉斯算子。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

函数实现:

dst = cv2.Laplacian(src, ddepth)

注意:通常情况下,可以将图像深度ddepth的值设置为-1,让处理结果与原始图像保持一致
但是该函数在计算过程中也会出现负数,故
在这里插入图片描述
代码:

import cv2
import numpy as np
o = cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE)
 
laplacian = cv2.Laplacian(o, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)  #转回uint8
 
cv2.imshow('original', o)
cv2.imshow('laplacian', laplacian)
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

7、边缘检测

canny边缘检测

函数实现:

edges =  cv2.Canny(image, threshold1, threshold2)
'''
image: 原始图像
threshold1: 阈值1 minVal
threshold2: 阈值2  maxVal
'''

在这里插入图片描述
threshold值越小,细节越丰富

import cv2
import numpy as np
o = cv2.imread('lena.png', cv2.IMREAD_GRAYSCALE)
o = cv2.resize(o,None,fx=0.5,fy=0.5)

r = cv2.Canny(o,100,200)
# 下面是不同阈值处理结果,可以看出阈值越小细节越丰富
r1 = cv2.Canny(o,64,128)
# laplacian = cv2.Laplacian(o, cv2.CV_64F)
# laplacian = cv2.convertScaleAbs(laplacian)  #转回uint8
 
cv2.imshow('original', o)
cv2.imshow('c', r)
cv2.imshow('c1', r1)
cv2.waitKey(0)
cv2.destroyAllWindows()

输出为:
在这里插入图片描述

8、图像金字塔

在这里插入图片描述
8.1 向下采样
代码为:

import cv2
import numpy as np
o = cv2.imread('lena.png')
# 依次变为原来的四分之一
r1 = cv2.pyrDown(o)
r2 = cv2.pyrDown(r1)
r3 = cv2.pyrDown(r2)
 
cv2.imshow('original', o)
cv2.imshow('PyrDown1', r1)
cv2.imshow('PyrDown2', r2)
cv2.imshow('PyrDown3', r3)
 
cv2.waitKey(0)
cv2.destoryAllWindows()

输出为:
在这里插入图片描述

8.2 向上采样
代码为:

import cv2
import numpy as np
o = cv2.imread('lena.png')
o = cv2.resize(o,None,fx=0.1,fy=0.1)
r1 = cv2.pyrUp(o)
r2 = cv2.pyrUp(r1)
r3 = cv2.pyrUp(r2)
 
cv2.imshow('original', o)
cv2.imshow('PyrUp1', r1)
cv2.imshow('PyrUp2', r2)
cv2.imshow('PyrUp3', r3)
 
cv2.waitKey(0)
cv2.destoryAllWindows()

输出为:
在这里插入图片描述

车牌识别

前面博客介绍了OpenCV图像处理的基础知识,本篇博客利用前面知识完成一个小项目——车牌号码识别。
简洁易懂的车牌号识别Python实现“超详解”(含代码)

1、整体思路
2、代码详解
2.1提取车牌位置
2.2车牌字符的分割
2.3模板匹配识别字符
3、总结
4、参考

1、整体思路

首先附上本次识别的图片:
在这里插入图片描述

基于OpenCV车牌号识别总体分为四个步骤:
(1)提取车牌位置,将车牌从图中分割出来;
(2)车牌字符的分割;
(3)通过模版匹配识别字符;
(4)将结果绘制在图片上显示出来。
与深度学习相比,传统图像处理的识别有好处又有坏处:
好处:不需要大量的数据集训练模型,通过形态学、边缘检测等操作提取特征
坏处:基于传统图像处理的图像识别代码的泛化性较低,当图像的角度,光照不同时,识别效果有时会不尽人意。

2、代码详解

为了方便观察每一步图片的变化,本次代码在Jupyter Notebook上编写,全部代码以上传(可直接运行)。
本次项目中会多次使用到图片显示和图片去噪灰度处理,所以首先定义了显示函数和高斯滤波灰度处理函数,方便后面的调用:

# 导入所需模块
import cv2
from matplotlib import pyplot as plt
import os
import numpy as np
# plt显示彩色图片
def plt_show0(img):
#cv2与plt的图像通道不同:cv2为[b,g,r];plt为[r, g, b]
    b,g,r = cv2.split(img)
    img = cv2.merge([r, g, b])
    plt.imshow(img)
    plt.show()
    
# plt显示灰度图片
def plt_show(img):
    plt.imshow(img,cmap='gray')
    plt.show()

# 图像去噪灰度处理
def gray_guss(image):
    image = cv2.GaussianBlur(image, (3, 3), 0)
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    return gray_image
2.1提取车牌位置

对图片进行阈值化处理、边缘检测及形态学操作,根据得到的轮廓特征识别车牌的具体位置,将车牌分割出来。直接上代码及代码详解:

# 读取待检测图片
origin_image = cv2.imread('./refer1/icar.jpg')
# 复制一张图片,在复制图上进行图像操作,保留原图
image = origin_image.copy()
# 图像去噪灰度处理
gray_image = gray_guss(image)
# x方向上的边缘检测(增强边缘信息)
Sobel_x = cv2.Sobel(gray_image, cv2.CV_16S, 1, 0)
absX = cv2.convertScaleAbs(Sobel_x)
image = absX

# 图像阈值化操作——获得二值化图
ret, image = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU)
# 显示灰度图像
plt_show(image)
# 形态学(从图像中提取对表达和描绘区域形状有意义的图像分量)——闭操作
kernelX = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 10))
image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernelX,iterations = 1)
# 显示灰度图像
plt_show(image)

二值化图以及闭操作(闭合细小的连接,抑制暗细节)的结果如图所示:
输出为:
在这里插入图片描述

# 腐蚀(erode)和膨胀(dilate)
kernelX = cv2.getStructuringElement(cv2.MORPH_RECT, (50, 1))
kernelY = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 20))
#x方向进行闭操作(抑制暗细节)
image = cv2.dilate(image, kernelX)
image = cv2.erode(image, kernelX)
#y方向的开操作
image = cv2.erode(image, kernelY)
image = cv2.dilate(image, kernelY)
# 中值滤波(去噪)
image = cv2.medianBlur(image, 21)
# 显示灰度图像
plt_show(image)
# 获得轮廓
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

for item in contours:
    rect = cv2.boundingRect(item)
    x = rect[0]
    y = rect[1]
    weight = rect[2]
    height = rect[3]
    # 根据轮廓的形状特点,确定车牌的轮廓位置并截取图像
    if (weight > (height * 3.5)) and (weight < (height * 4)):
        image = origin_image[y:y + height, x:x + weight]
        plt_show0(image)

输出为:
在这里插入图片描述

2.2车牌字符的分割
#车牌字符分割
# 图像去噪灰度处理
gray_image = gray_guss(image)
# 图像阈值化操作——获得二值化图   
ret, image = cv2.threshold(gray_image, 0, 255, cv2.THRESH_OTSU)
plt_show(image)

#膨胀操作,使“苏”字膨胀为一个近似的整体,为分割做准备
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
image = cv2.dilate(image, kernel)
plt_show(image)

输出为:
在这里插入图片描述

# 查找轮廓
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
words = []
word_images = []
#对所有轮廓逐一操作
for item in contours:
    word = []
    rect = cv2.boundingRect(item)
    x = rect[0]
    y = rect[1]
    weight = rect[2]
    height = rect[3]
    word.append(x)
    word.append(y)
    word.append(weight)
    word.append(height)
    words.append(word)
# 排序,车牌号有顺序。words是一个嵌套列表
words = sorted(words,key=lambda s:s[0],reverse=False)
i = 0
#word中存放轮廓的起始点和宽高
for word in words:
    # 筛选字符的轮廓
    if (word[3] > (word[2] * 1.5)) and (word[3] < (word[2] * 3.5)) and (word[2] > 25):
        i = i+1
        splite_image = image[word[1]:word[1] + word[3], word[0]:word[0] + word[2]]
        word_images.append(splite_image)
        print(i)
print(words)

for i,j in enumerate(word_images):  
    plt.subplot(1,7,i+1)
    plt.imshow(word_images[i],cmap='gray')
plt.show()

输出为:
在这里插入图片描述

2.3模板匹配识别字符

模板匹配是一个机械性的流程,所以把机械性的操作设定为函数。

#模版匹配
# 准备模板(template[0-9]为数字模板;)
template = ['0','1','2','3','4','5','6','7','8','9',
            'A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z',
            '藏','川','鄂','甘','赣','贵','桂','黑','沪','吉','冀','津','晋','京','辽','鲁','蒙','闽','宁',
            '青','琼','陕','苏','皖','湘','新','渝','豫','粤','云','浙']

# 读取一个文件夹下的所有图片,输入参数是文件名,返回模板文件地址列表
def read_directory(directory_name):
    referImg_list = []
    for filename in os.listdir(directory_name):
        referImg_list.append(directory_name + "/" + filename)
    return referImg_list

# 获得中文模板列表(只匹配车牌的第一个字符)
def get_chinese_words_list():
    chinese_words_list = []
    for i in range(34,64):
        #将模板存放在字典中
        c_word = read_directory('./refer1/'+ template[i])
        chinese_words_list.append(c_word)
    return chinese_words_list
chinese_words_list = get_chinese_words_list()


# 获得英文模板列表(只匹配车牌的第二个字符)
def get_eng_words_list():
    eng_words_list = []
    for i in range(10,34):
        e_word = read_directory('./refer1/'+ template[i])
        eng_words_list.append(e_word)
    return eng_words_list
eng_words_list = get_eng_words_list()


# 获得英文和数字模板列表(匹配车牌后面的字符)
def get_eng_num_words_list():
    eng_num_words_list = []
    for i in range(0,34):
        word = read_directory('./refer1/'+ template[i])
        eng_num_words_list.append(word)
    return eng_num_words_list
eng_num_words_list = get_eng_num_words_list()


# 读取一个模板地址与图片进行匹配,返回得分
def template_score(template,image):
    #将模板进行格式转换
    template_img=cv2.imdecode(np.fromfile(template,dtype=np.uint8),1)
    template_img = cv2.cvtColor(template_img, cv2.COLOR_RGB2GRAY)
    #模板图像阈值化处理——获得黑白图
    ret, template_img = cv2.threshold(template_img, 0, 255, cv2.THRESH_OTSU)
#     height, width = template_img.shape
#     image_ = image.copy()
#     image_ = cv2.resize(image_, (width, height))
    image_ = image.copy()
    #获得待检测图片的尺寸
    height, width = image_.shape
    # 将模板resize至与图像一样大小
    template_img = cv2.resize(template_img, (width, height))
    # 模板匹配,返回匹配得分
    result = cv2.matchTemplate(image_, template_img, cv2.TM_CCOEFF)
    return result[0][0]


# 对分割得到的字符逐一匹配
def template_matching(word_images):
    results = []
    for index,word_image in enumerate(word_images):
        if index==0:
            best_score = []
            for chinese_words in chinese_words_list:
                score = []
                for chinese_word in chinese_words:
                    result = template_score(chinese_word,word_image)
                    score.append(result)
                best_score.append(max(score))
            i = best_score.index(max(best_score))
            # print(template[34+i])
            r = template[34+i]
            results.append(r)
            continue
        if index==1:
            best_score = []
            for eng_word_list in eng_words_list:
                score = []
                for eng_word in eng_word_list:
                    result = template_score(eng_word,word_image)
                    score.append(result)
                best_score.append(max(score))
            i = best_score.index(max(best_score))
            # print(template[10+i])
            r = template[10+i]
            results.append(r)
            continue
        else:
            best_score = []
            for eng_num_word_list in eng_num_words_list:
                score = []
                for eng_num_word in eng_num_word_list:
                    result = template_score(eng_num_word,word_image)
                    score.append(result)
                best_score.append(max(score))
            i = best_score.index(max(best_score))
            # print(template[i])
            r = template[i]
            results.append(r)
            continue
    return results


word_images_ = word_images.copy()
# 调用函数获得结果
result = template_matching(word_images_)
print(result)
# "".join(result)函数将列表转换为拼接好的字符串,方便结果显示
print( "".join(result))

输出为:
在这里插入图片描述
最后,利用PIL库将结果绘制在原图上,

from PIL import ImageFont, ImageDraw, Image

height,weight = origin_image.shape[0:2]
print(height)
print(weight)

image_1 = origin_image.copy()
cv2.rectangle(image_1, (int(0.2*weight), int(0.75*height)), (int(weight*0.9), int(height*0.95)), (0, 255, 0), 5)

#设置需要显示的字体
fontpath = "./refer1/simhei.ttf"
font = ImageFont.truetype(fontpath,64)
img_pil = Image.fromarray(image_1)
draw = ImageDraw.Draw(img_pil)
#绘制文字信息
draw.text((int(0.2*weight)+25, int(0.75*height)),  "".join(result), font = font, fill = (255, 255, 0))
# draw.text((int(0.2*weight)+25, int(0.75*height)),  "".join(result),fill = (255, 255, 0))
bk_img = np.array(img_pil)
print(result)
print( "".join(result))
plt_show0(bk_img)

获得的最终输出图片如下:
在这里插入图片描述

大功告成!!!!!

3、总结

(一) 、OpenCV的车牌号码识别一共分为四步走:
1–提取车牌位置,将车牌从图中分割出来;
2–车牌字符的分割;
3–通过模版匹配识别字符;
4–将结果绘制在图片上显示出来。
(二)、图像处理的识别泛化性较低,对图片的角度光照有要求,所以要理解图像处理每一步的作用,根据自己图像的特点调整参数,更改操作顺序等等,以达到最好的效果。
(三)、车牌号识别的模板连接如下,需要的可以下载,有了模板就可以识别自己的图片了
链接:https://pan.baidu.com/s/1QBjy7c0klv_PBUwJjA8ynA
提取码:v53d
(四)、完整代码已上传SCDN点击查看

针对完全没有基础的同学们
1.确定机器学习的应用领域有哪些
2.查找机器学习的算法应用有哪些
3.确定想要研究的领域极其对应的算法
4.通过招聘网站和论文等确定具体的技术
5.了解业务流程,查找数据
6.复现经典算法
7.持续优化,并尝试与对应企业人员沟通心得
8.企业给出反馈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1590222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

清远某国企IBM服务器Board故障上门维修

接到一台来自广东清远市清城区某水利大坝国企单位报修一台IBM System x3650 M4服务器无法开机的故障&#xff0c;分享给大家&#xff0c;同时也方便有需要的朋友能及时找到我们快速解决服务器问题。 故障服务器型号&#xff1a;IBM System x3650 M4 服务器使用单位&#xff1a;…

只要0.74元的双通道数字隔离器,1T1R,增强型ESD-3.0 kV ,150Kbps数字隔离器

前言: 做和电源打交道的设备&#xff0c;通信时非常危险&#xff0c;一定要使用隔离的USB-232转换器&#xff0c;或者你设备的串口与市电直连的设备通信时&#xff0c;现在需要使用数字隔离器&#xff0c;早期的一般都使用光耦&#xff0c;在这种情况下&#xff0c;速度不搞的…

正则表达式:量词(三)

正则表达式中的量词有以下几种:1. *: 匹配前面的字符0次或多次。2. : 匹配前面的字符1次或多次。3.?: 匹配前面的字符0次或1次。4. {n}: 匹配前面的字符恰好n次。5. {n,}: 匹配前面的字符至少n次。6. {n,m}:匹配前面的字符至少n次&#xff0c;但不超过m次。 以下是使用Python的…

Unity TextMeshProUGUI 获取文本尺寸·大小

一般使用ContentSizeFitter组件自动变更大小 API 渲染前 Vector2 GetPreferredValues(string text)Vector2 GetPreferredValues(string text, float width, float height)Vector2 GetPreferredValues(float width, float height) 渲染后 Vector2 GetRenderedValues()Vector…

idea 卡怎么办

设置内存大小 清缓存重启 idea显示内存全用情况 右下角

解决CSS中鼠标移入到某个元素其子元素被遮挡的问题

我们在开发中经常遇到一种场景&#xff0c;就是给元素加提示信息&#xff0c;就是鼠标移入到盒子上面时&#xff0c;会出现提示信息这一功能&#xff0c;如果我们给盒子加了hover&#xff0c;当鼠标移入到盒子上时&#xff0c;让他往上移动5px&#xff0c;即transform: transla…

网盘——搜索用户

目录 1、搜索用户 1.1、在friend.h里面定义槽函数 1.2、关联槽函数 1.3、搜索用户的时候&#xff0c;会弹出一个对话框来,在friend.cpp里面引入下面的头文件&#xff0c;专门用来输入数据的 1.4、获取输入信息&#xff0c;并使用Qstring来接收它 1.5、将上述代码打包&…

AR地图导览小程序是怎么开发出来的?

在移动互联网时代&#xff0c;AR技术的发展为地图导览提供了全新的可能性。AR地图导览小程序结合了虚拟现实技术和地图导航功能&#xff0c;为用户提供了更加沉浸式、直观的导览体验。本文将从专业性和思考深度两个方面&#xff0c;探讨AR地图导览小程序的开发方案。 编辑搜图 …

时间复杂度详解2——时间复杂度的计算

时间复杂度基本计算规则&#xff1a; 基本操作即只有常数项&#xff0c;认为其时间复杂度为O(1)顺序结构&#xff0c;时间复杂度按加法进行计算循环结构&#xff0c;时间复杂度按乘法进行计算分支结构&#xff0c;时间复杂度取最大值判断一个算法效率时&#xff0c;往往只需要…

HubSpot如何通过自动化和优化客户服务流程?

在当今竞争激烈的市场环境中&#xff0c;提供卓越的客户服务体验已经成为企业赢得客户忠诚、推动业务增长的关键所在。HubSpot&#xff0c;作为一款领先的客户关系管理软件&#xff0c;通过自动化和优化客户服务流程&#xff0c;为企业带来了革命性的服务体验提升。 HubSpot通…

对LSTM的通俗易懂理解--可变权重

RNN的问题&#xff1a;长期依赖&#xff0c;即对短期的数据敏感&#xff0c;对比较远的长期数据不敏感&#xff0c;这是因为RNN隐藏状态权重在不同时刻是共享相同的&#xff0c;随着时间步的增加&#xff0c;梯度会指数级地衰减或者增长&#xff0c;导致梯度消失或者爆炸&#…

稀碎从零算法笔记Day47-LeetCode:找到冠军 I

或许是昨天的每日一题太难了&#xff0c;今天的简单 题型&#xff1a;数组、矩阵 链接&#xff1a;2923. 找到冠军 I - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 一场比赛中共有 n 支队伍&#xff0c;按从 0 到 n - 1 编号。 给你一个下…

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第三套

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第三套 (共9套&#xff0c;有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&#xff09; 部分题目分享&#xff0c;完整版获取&#xff08;WX:didadidadidida313&#xff0c;加我备注&#x…

大数据架构之关系型数据仓库——解读大数据架构(二)

文章目录 前言什么是关系型数仓对数仓的错误认识与使用自上而下的方法关系型数仓的优点关系型数仓的缺点数据加载加载数据的频率如何确定变更数据 关系型数仓会消失吗总结 前言 本文对关系型数据仓库&#xff08;RDW&#xff09;进行了简要的介绍说明&#xff0c;包括什么是关…

《由浅入深学习SAP财务》:第2章 总账模块 - 2.6 定期处理 - 2.6.5 年末操作:维护新财政年度会计凭证编号范围

2.6.5 年末操作&#xff1a;维护新财政年度会计凭证编号范围 财务系统的维护者要在每年年末预先设置好下一年度的会计凭证编号范围&#xff08;number range&#xff09;&#xff0c;以便下一年度会计凭证能够顺利生成。这一操作一定要在下一年度1月1日以前预先完成。 …

C#/.NET/.NET Core拾遗补漏合集(24年4月更新)

前言 在这个快速发展的技术世界中&#xff0c;时常会有一些重要的知识点、信息或细节被忽略或遗漏。《C#/.NET/.NET Core拾遗补漏》专栏我们将探讨一些可能被忽略或遗漏的重要知识点、信息或细节&#xff0c;以帮助大家更全面地了解这些技术栈的特性和发展方向。 GitHub开源地…

【MoS2】应变增强的单层MoS2光电探测器

这篇文章的标题是《Strain-Enhanced Large-Area Monolayer MoS2 Photodetectors》&#xff0c;作者是Borna Radatovic等人&#xff0c;发表在《ACS Applied Materials & Interfaces》期刊的2024年第16卷。文章主要研究了应变增强的大面积单层MoS2光电探测器的性能和应用潜力…

基于SpringBoot的“汉服文化平台网站”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“汉服文化平台网站”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 系统功能界面图 用户登录、用…

Java并发(1)--线程,进程,以及缓存

线程和进程是什么&#xff1f; 进程 进程是程序的一次执行过程&#xff0c;系统程序的基本单位。有自己的main方法&#xff0c;并且主要由主方法运行起来的基本上就是进程。 线程 线程与进程相似&#xff0c;但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以…

ARM64架构栈帧回溯

文章目录 前言一、栈帧简介二、demo演示 前言 请参考&#xff1a;ARM64架构栈帧以及帧指针FP 一、栈帧简介 假设下列函数调用&#xff1a; funb() {func() }funa() {funb() }main() {funa() }main函数&#xff0c;funa函数&#xff0c;funb函数都不是叶子函数&#xff0c;其…