记录一下我的图像处理学习路线,后续有其他想法了再补充。
目录
1.图像处理基础知识
1.1 数字图像处理公开课
2.利用opencv进行实现
2.1 在pycharm里安装opencv库。
2.2 图像处理基础操作
2.2.1 读取图片
2.2.2 将图片转换成自己想要的大小
2.2.3 输出图片矩阵
2.2.4 绘制灰度直方图
2.2.5 直方图均衡化
2.2.6 空间域增强&滤波
2.2.7 频域增强&滤波
2.2.8 图片复原与重建:给图片增加噪声
2.2.9 彩色图像处理:图像读取
2.2.10 彩色图像处理:伪彩色图像处理
2.2.11 小波变换
2.2.12 图像压缩
3. 图像形态学
3.1 腐蚀与膨胀
3.2 图像分割
3.2.1 阈值分割法:
3.2.2 边缘分割法:
3.2.3 霍夫变换Hough:
3.2.4 基于区域的图像分割法
4. 视频处理
4.1 视频基础知识
4.2 按帧读取视频并合成视频
https://www.bilibili.com/video/BV157411J7H8?from=search&seid=11616479247431589441
https://www.bilibili.com/video/BV1Kh411X7Qv?p=1
看完上面的东西,了解基础概念,准备在之后真正操作时,再慢慢消化。
参考这篇:https://blog.csdn.net/qq_38328871/article/details/84842381?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162159869616780255213928%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162159869616780255213928&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-84842381.first_rank_v2_pc_rank_v29&utm_term=pycharm+%E5%AE%89%E8%A3%85opencv&spm=1018.2226.3
import cv2
img = cv2.imread("F:/picture/cat.jpg", cv2.IMREAD_GRAYSCALE)
cv2.namedWindow("mypicture")
cv2.imshow("mypicture", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
先要准备一张图片放在你的路径中,然后imread(“路径”,读取方式)根据自己需要来调整,pycharm自动补全的提示里也有,可以都试试。这里我后面写的_GRAYSCALE 是以灰度图的方式读取出来。
输出结果:
因为想要把图片转换成自己想要的标准格式,所以需要重新定义大小,这里我们要用到resize函数,将其转换成100*100 的格式。这里参考这一篇resize参数详解。https://blog.csdn.net/weixin_40922744/article/details/103395227
这里采用默认的双线性插值。
size = (100, 100)
myImg = cv2.resize(img, size, interpolation=cv2.INTER_LINEAR)
cv2.namedWindow("my resize picture")
cv2.imshow("my resize picture", myImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果:
缩小后的图像
可以直接print出来,因为将图片在imread时就已经是用灰度图的格式打开的,所以直接输出就是图片的灰度矩阵
size = (100, 100)
myImg = cv2.resize(img, size, interpolation=cv2.INTER_LINEAR)
cv2.namedWindow("my resize picture")
cv2.imshow("my resize picture", myImg)
print(myImg) # 添加的 输出图片灰度矩阵
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果:
[[ 88 82 83 ... 61 62 97]
[ 82 82 75 ... 62 79 115]
[ 85 85 84 ... 79 109 134]
...
[238 220 233 ... 89 87 90]
[227 210 234 ... 120 135 124]
[220 228 228 ... 120 107 110]]
如果看了图像处理的基础课程会知道,因为在进行图像分割时需要有一个灰度的阈值,所以我们需要通过绘制灰度直方图来找到这个阈值(即灰度直方图的波谷)。
import matplotlib.pyplot as plt #导入绘图工具
plt.rcParams['font.sans-serif'] = ['SimHei'] #显示中文
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
width = 100 #要转换的宽度
length = 100#要转换的长度
size = (width, length)
myImg = cv2.resize(img, size, interpolation=cv2.INTER_LINEAR) #将之前的图片转换成设置的大小
cv2.namedWindow("my resize picture")
cv2.imshow("my resize picture", myImg) #展示转换后的图像
# 绘制直方图
#1 准备数据
x = range(255)#横轴显示图像的灰度等级0-255
y = range(width*length)# y轴是灰度的个数 width*length
num = [] #num元组声明,每个灰度对应的个数
imgGray = myImg.flatten()#把图像灰度数据由矩阵拉平成一行的形式
for i in x:#从灰度0-255开始轮流查有多少个,每个灰度遍历
t = 0 #每个灰度级像素个数统计用的临时变量,检测下一个灰度级时置零。
for j in y: #每个像素遍历
if imgGray[j] == i:#如果这个位置的灰度等于当前要检索的灰度级
t = t+1 #那么当前灰度级的个数+1
num.append(t) #将查出来的每个灰度级的个数添加到灰度统计数组中
#2 设置画布大小
plt.figure(figsize=(12, 8), dpi=80)
plt.plot(x, num)
#3 设置刻度及步长
z = range(300) #按照检测出的分布,设置图像能显示灰度级的最大值
plt.xticks(x[::20])
plt.yticks(z[::20]) #20是步长,根据自己需要调整
#4 添加网格信息
plt.grid(True, linestyle='--', alpha=0.5) #默认是True,风格设置为虚线,alpha为透明度
#5 添加标题(中文在plt中默认乱码,不乱码的方法就是导入包之后的那两行)
plt.xlabel('灰度级')
plt.ylabel('灰度个数')
plt.title('cat灰度直方图')
#6 保存图片,并展示
plt.savefig('C:/Users/Administrator/Desktop/pic/gray_zhifangtu.png')
plt.show()
cv2.waitKey(0)
cv2.destroyAllWindows()
输出结果:
或者使用hinst函数可以直接绘制直方图,具体参考这篇:https://blog.csdn.net/weixin_37988176/article/details/109421535
imgGray = myImg.flatten()#把图像灰度数据拉平
plt.figure("简单方法绘制直方图") #设置直方图的名字
n, bins, patches = plt.hist(imgGray, bins=100, facecolor='green', alpha=0.75)
plt.show()
输出结果:
因为直方图是否均衡和视觉效果有着直接的关系,所以可以通过修正直方图来进行图像增强。我们所要做的直方图均衡化就是把直方图弄的平整一些。
我参考了这一篇:https://blog.csdn.net/missyougoon/article/details/81632166?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162168723916780255276528%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162168723916780255276528&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-81632166.first_rank_v2_pc_rank_v29&utm_term=%E7%9B%B4%E6%96%B9%E5%9B%BE%E5%9D%87%E8%A1%A1%E5%8C%96python&spm=1018.2226.3001.4187
#直方图均衡化
imgEqu = cv2.equalizeHist(myImg)
imgEquflat = imgEqu.flatten()#把图像灰度数据拉平
plt.figure("直方图均衡化") #设置直方图的名字
cv2.namedWindow("均衡过的图片")
cv2.imwrite("C:/Users/Administrator/Desktop/pic/imgEqu.png", imgEqu)
cv2.imshow("均衡过的图片", imgEqu)
n, bins, patches = plt.hist(imgEquflat, bins=100, facecolor='green', alpha=0.75)
plt.show()
这里跑完一遍发现直方图很怪,输出的图片中间有一块白的,然后我调查了一下发现,原来我在以100 100的大小来resize图片的时候,出了问题。所以我重新resize了一下。
print(img.shape) #打印出图片的尺寸
width, length = img.shape[0:2] #将图片尺寸给宽和长
size = (int(width/2), int(length/2))
myImg = cv2.resize(img, size, interpolation=cv2.INTER_LINEAR)
cv2.namedWindow("my resize picture")
cv2.imwrite("C:/Users/Administrator/Desktop/pic/myImg.png", myImg)
cv2.imshow("my resize picture", myImg)
然后resize后的图片和均衡过后的图片对比如下:左边是原始图像灰度化缩小后的,右边是直方图均衡后的图像。
均衡过后的直方图:
空间滤波器分为平滑滤波器(消除噪声&模糊图像)和锐化滤波器(反差增加,边缘明显)。
参考了这一篇:https://blog.csdn.net/Seven_WWW/article/details/108442513?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162177868716780255216992%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162177868716780255216992&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-2-108442513.first_rank_v2_pc_rank_v29&utm_term=python+opencv+%E7%A9%BA%E9%97%B4%E5%9F%9F%E6%BB%A4%E6%B3%A2&spm=1018.2226.3001.4187
这里试一下sobel算子滤波:
#空间域增强
x = cv2.Sobel(imgEqu,cv2.CV_16S,1,0)
y = cv2.Sobel(imgEqu,cv2.CV_16S,0,1)
# 转uint8
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
imgSobel = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
# 显示图形
titles = [u'原始图像', u'Sobel算子']
images = [imgEqu, imgSobel]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
输出结果:
通过傅里叶变换,将图像由空间域函数转换成频率域函数,然后在频率域对不同频率进行滤波处理,在通过傅里叶逆变换转换成增强后的图像。
低通滤波起平滑图像、消除噪声的作用。
高通滤波起锐化图像、增强细节的作用。
参考这篇文章:https://blog.csdn.net/qq_40507857/article/details/107609844?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162178281016780271576997%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162178281016780271576997&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-107609844.first_rank_v2_pc_rank_v29&utm_term=python+%E9%A2%91%E5%9F%9F%E5%A2%9E%E5%BC%BA&spm=1018.2226.3001.4187
这里试一下butterworth高频增强滤波:
#频域增强
f = np.fft.fft2(imgEqu)
fshift = np.fft.fftshift(f)
# 取绝对值后将复数变化为实数
# 取对数的目的是将数据变换到0~255
s1 = np.log(np.abs(fshift))
def ButterworthPassFilter(image, d, n):
"""
Butterworth 高通滤波器
"""
f = np.fft.fft2(image)
fshift = np.fft.fftshift(f)
def make_transform_matrix(d):
transform_matrix = np.zeros(image.shape)
center_point = tuple(map(lambda x: (x - 1) / 2, s1.shape))
for i in range(transform_matrix.shape[0]):
for j in range(transform_matrix.shape[1]):
def cal_distance(pa, pb):
from math import sqrt
dis = sqrt((pa[0] - pb[0]) ** 2 + (pa[1] - pb[1]) ** 2)
return dis
dis = cal_distance(center_point, (i, j))
transform_matrix[i, j] = 1 / (1 + (dis / d) ** (2 * n))
return transform_matrix
d_matrix = make_transform_matrix(d)
d_matrix = d_matrix+0.5
new_img = np.abs(np.fft.ifft2(np.fft.ifftshift(fshift * d_matrix)))
return new_img
plt.subplot(221)
plt.axis('off')
plt.title('Original')
plt.imshow(imgEqu, cmap='gray')
plt.subplot(222)
plt.axis('off')
plt.title('Butter D=100 n=1')
butter_100_1 = ButterworthPassFilter(imgEqu, 100, 1)
plt.imshow(butter_100_1, cmap='gray')
plt.subplot(223)
plt.axis('off')
plt.title('Butter D=30 n=1')
butter_30_1 = ButterworthPassFilter(imgEqu, 30, 1)
plt.imshow(butter_30_1, cmap='gray')
plt.subplot(224)
plt.axis('off')
plt.title('Butter D=30 n=5')
butter_30_5 = ButterworthPassFilter(imgEqu, 30, 5)
plt.imshow(butter_30_5, cmap='gray')
plt.show()
输出结果:其中 d为截止频率,n为阶数,具体的参数调整效果在那篇文章里有。
这里参考了这一篇:https://blog.csdn.net/qq_38395705/article/details/106311905?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162182736316780262527891%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162182736316780262527891&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-5-106311905.first_rank_v2_pc_rank_v29&utm_term=opencv+python+%E5%A2%9E%E5%8A%A0%E5%99%AA%E5%A3%B0&spm=1018.2226.3001.4187
试一下添加椒盐噪声:
# 增加噪声
#添加椒盐噪声
def sp_noise(image, prob):
'''
添加椒盐噪声
prob:噪声比例
'''
output = np.zeros(image.shape, np.uint8)
thres = 1 - prob
for i in range(image.shape[0]):
for j in range(image.shape[1]):
rdn = np.random.random()
if rdn < prob:
output[i][j] = 0
elif rdn > thres:
output[i][j] = 255
else:
output[i][j] = image[i][j]
return output
imgSp = sp_noise(imgEqu, 0.02)
cv2.namedWindow("imgSp")
cv2.imwrite("C:/Users/Administrator/Desktop/pic/imgSp.png", imgSp)
cv2.imshow("imgSp", imgSp)
输出结果为:
参考了这一篇:https://blog.csdn.net/huanglu_thu13/article/details/52332695?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162183003516780274112204%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162183003516780274112204&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-52332695.first_rank_v2_pc_rank_v29&utm_term=python+opencv+%E5%BD%A9%E8%89%B2%E5%9B%BE%E5%83%8F&spm=1018.2226.3001.4187
先将图片读取成RGB格式,要注意需要转化一下:
#读取彩色图像
#首先读出来的是BGR图像
imgBgr = cv2.imread("F:/picture/cat.jpg", cv2.IMREAD_COLOR)
#转换成RGB
imgRgb = cv2.cvtColor(imgBgr, cv2.COLOR_BGR2RGB)
plt.subplot(121)# 一行 两列 第一个
plt.axis('off')
plt.title('IMG_BGR')
plt.imshow(imgBgr)
plt.subplot(122)
plt.axis('off')
plt.title('IMG_RGB')
plt.imshow(imgRgb)
plt.show()
输出结果:
文中说到还有一种方式是直接按照三通道进行读取,就省去了转化的步骤:
cat = cv2.imread("F:/picture/cat.jpg")
#直接按照通道读取
b, g, r = cv2.split(cat)
"""
cv2.imshow("Blue 1", b)
cv2.imshow("Green 1", g)
cv2.imshow("Red 1", r)
"""
plt.subplot(131)
plt.axis('off')
plt.title('IMG_R')
plt.imshow(r)
plt.subplot(132)
plt.axis('off')
plt.title('IMG_G')
plt.imshow(g)
plt.subplot(133)
plt.axis('off')
plt.title('IMG_B')
plt.imshow(b)
plt.show()
输出结果:
将图像中的一个或多个通道的数据用其他传感器的数据代替。
也是一种滤波方法。
防止吉布斯效应: 一个很小的波动需要许多三角波来拟合。而用小波变换可以消除这种效果,减少了运算量。
小波变换原理参考了知乎这一篇:形象易懂讲解算法I——小波变换 - 咚懂咚懂咚的文章 - 知乎 https://zhuanlan.zhihu.com/p/22450818
霍夫曼编码:分级逐次合并概率最小的灰度级。
参考了这一篇:https://blog.csdn.net/wsp_1138886114/article/details/100761284?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162184043816780255283899%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162184043816780255283899&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-1-100761284.first_rank_v2_pc_rank_v29&utm_term=opencv+%E5%9B%BE%E5%83%8F%E5%8E%8B%E7%BC%A9+python+&spm=1018.2226.3001.4187
最简单的就是resize函数。
腐蚀与膨胀:通过用一个n维矩阵扫描图片,来判断是该区域像素是该置0还是置255,通过调整filter矩阵参数,二者结合使用能起到滤波器的效果。
腐蚀(erode):用于消除小而无意义的物体。
膨胀(dilate):用于扩大目标像素。
参考:https://blog.csdn.net/LaoYuanPython/article/details/109441709?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162209027716780265430437%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162209027716780265430437&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-2-109441709.first_rank_v2_pc_rank_v29&utm_term=%E8%85%90%E8%9A%80%E4%B8%8E%E8%86%A8%E8%83%80+python&spm=1018.2226.3001.4187
全局阈值:通过直方图的波谷进行图像分割。如果多个波峰波谷,可以使用多阈值分割(代表不同部位)。
最小误差阈值:有目标和背景的分布函数,对误差函数(阈值导致的错误分辨的部分(目标&背景)的概率和)再求极值,得到阈值。
最大方差阈值:利用直方图选取初始阈值,将像素分为两组,求两组像素的方差,方差最大点就是阈值点。(otsu 大津阈值法)
Canny算子:
1.采用滤波器平滑图像
2. 采用方向检测算子计算梯度
3.沿着梯度方向寻找局部极大值
4.采用双阈值检测跟踪边缘
解决什么问题:在找到一堆边界点时,想要找到最符合这些边界点的直线方程。
方法:将位置xy平面,转换到对应方程y=ax+b 的参数ab平面,求 a=mb+n这些线的公共点。就是y与x对应关系最频繁的一个点,所以就能确定参数。
功能:通过变换极坐标方程,多维参数的形式,即可以检测直线,也可以检测圆等曲线。
优点与缺点:
优点:抗噪声能力强,解析直线与曲线。
缺点:需要先二值化以及边缘检测,损失原图很多信息。
区域生长法:灰度差生长法,小于一定灰度阈值T的都归为一个区域。
分类和并法:将图像分成许多小份,根据相邻方差是否为0(或者是某个阈值)来判断是否属于同一区域。
帧:frame,视频是由帧构成的,视频可以看做根据时间顺序依次播放的画面
fps:帧率,一秒播放多少帧
... ...待后续补充。
将一个视频按照每一帧读取出来,并存为jpg文件,然后对其进行处理,处理后再将所有图片合成一个新的视频,最后将用到的图片删除,只保留视频。
下列代码生成的视频很大,具体怎么变小还在研究中,应该是将图片质量调低就好了。
import 包有些可能不用,不过我懒省事就都import了。可以自行删除。
import cv2
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import os
video = cv2.VideoCapture("F:/PY_WORK/test/test01.avi") #打开原始视频
fps = video.get(cv2.CAP_PROP_FPS) #读取帧率
frameCount = video.get(cv2.CAP_PROP_FRAME_COUNT) #视频总帧数
size = (int(video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))) #读取视频size
success, frame = video.read()
print(fps, frameCount, size)
#读取视频图片
i = 0 # 图片顺序编号
while success:
success, frame = video.read()#读出一帧图片
if success:
filename = 'F:/PY_WORK/test/image' + str(i) + '.jpg'
cv2.imwrite(filename, frame, [cv2.IMWRITE_JPEG_QUALITY, 100]) # 确定图片质量,100算是高的
i = i+1
print('图片读取end!')
"""
在这里可以插入对img图片的处理,例如逐个图片边缘检测之类的。
"""
#图片写入视频
img = cv2.imread('F:/PY_WORK/test/image0.jpg')#读取第一个图片 看看大小
imginfo = img.shape
size = (imginfo[1], imginfo[0]) #与默认不同,opencv使用 height在前,width在后,所有需要自己重新排序
print(size)
#创建写入对象,包括 新建视频名称,每秒钟多少帧图片(fps张) ,size大小
#一般人眼最低分辨率为19帧/秒
transpath = 'F:/PY_WORK/test/test02.mp4' #输出视频位置
videoWriter = cv2.VideoWriter(transpath, cv2.VideoWriter_fourcc(*"mp4v"), fps, size)#fourcc参数如果报错的话,就直接用-1替换,它会自己选择。这个我也感觉很迷糊
for i in range(int(frameCount)):#将 总帧数 幅图片写入
filename = 'F:/PY_WORK/test/image' + str(i) + '.jpg'
img = cv2.imread(filename, 1) # 1 表示彩图,0表示灰度图
# 直接写入图片对应的数据
videoWriter.write(img)
videoWriter.release()
print('视频生成end')
#生成完毕删除图片
for i in range(int(frameCount)):
filename = 'F:/PY_WORK/test/image' + str(i) + '.jpg'
os.remove("filename")
print("图片删除end!")
我中间插入Canny滤波后的输出结果:
视频用的bilibili下载的舞蹈区视频做(~ ̄▽ ̄)~https://www.bilibili.com/video/BV1Vb411C7wk?from=search&seid=11355300764444585028。
视频下载可以再链接里的bilibili后面加上jj,用唧唧下载视频。
生成的视频就不传了,放个截图。