大三专业课上机实验碰到的问题合集
Python问题
令人感叹,虽然有C++和Java的编程基础,但是Python没有系统地学过,所以很多常用的,好用的库最基本的东西都不会,于是在此记录
可能都是一些最基本的问题,但是我确实不会,不得不碰到一个记录一个
我觉得真得系统地学习一门编程语言,因为这门语言可能会有一些你没接触过的新语法,你没见过的牛逼库
pandas库记录
这个库总体上是用于数据分析的,可以用于读取csv文件
常用的数据结构:Series和DataFrame
Series
pandas.Series( data, index, dtype, name, copy)
Pandas Series 类似表格中的一个列,类似于一维数组,带有可以自定义的索引(类似于C++里的键值对,索引是键,元素是值)
在不指定索引的情况下,索引就从0开始,类似于数组的下标
也可以指定索引,如:
1 | import pandas as pd |
索引对齐问题
先来看我机器学习求岭回归时中出现的一个问题:
1 | # 计算岭回归系数 |
输出结果如下:
1 | Beta: |
我在这里想实现的操作是Beta_Final = Beta / x_std = Beta / x_std
,即把Beta
的每一项与x_std
的每一项做除法,然后存在Beta_Final
中。这两个向量都是8x1的,所以绝对可以计算
但是结果输出的Beta_Final
却不是我想要的结果:可以看到每一项都是NaN
,且维数变成了16x1,怎么会是呢?
我去问了bing:
我注意到你的
x_std
的索引是你的特征名(‘Sex’,‘Length’,'Diameter’等),而你的Beta
的索引是数字(0到7)。这就是为什么你在计算Beta_Final
时得到了全是NaN
的结果。为了解决这个问题,你需要确保在计算之前,你的
Beta
和x_std
有相同的索引。你可以在计算Beta_Final
之前,使用以下代码来更新Beta
的索引:
1 >Beta.index = x_std.index然后再计算
Beta_Final = Beta / x_std
, 这样应该就可以得到正确的结果了。在Pandas中,当对两个Series对象进行操作时,Pandas会尝试对齐它们的索引。如果在一个Series中找不到对应的索引,那么结果就会被设置为
NaN
。这就是为什么你在计算Beta_Final = Beta / x_std
时得到了全是NaN
的结果,因为Beta
和x_std
的索引不匹配。这种行为是Pandas的设计决策之一,目的是为了处理实际数据分析中常见的问题,比如数据缺失、数据顺序不一致等等。当然,这也意味着我们在使用Pandas进行计算时,需要注意保持索引的一致性。
如果你想要按照位置(而不是按照索引)来进行计算,你可以使用Numpy数组来代替Pandas Series。Numpy数组没有索引,所以它们之间的操作都是基于位置的。例如,你可以这样计算
Beta_Final
:
1 >Beta_Final = Beta.values / x_std.values这里,
.values
属性会返回一个Numpy数组,包含了Series中的值。
太详细了,给出的解决方案太可行了,真得给bing磕大头吧
元素级别的乘法
我现在想实现对一个样本每一维的元素和Beta_Final
的每一个元素进行乘法:
有一个4177x8的Dataframe: x
,一个8x1的Series:
Beta_Final
,想要对x
的每一行和Beta_Final
进行对应元素的乘法运算。具体来说,就是x
的第一行的第一个元素乘以Beta_Final
的第一个元素,x
的第一行的第二个元素乘以Beta_Final
的第二个元素,以此类推
用x * Beta_Final
就可以实现
DataFrame
pandas.DataFrame( data, index, columns, dtype, copy)
DataFrame 是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型值)
DataFrame 既有行索引也有列索引,它可以被看做由 Series 组成的字典(共同用一个索引)
矩阵乘法
DataFrame可以直接与numpy二维数组做矩阵乘法:
1 | import pandas as pd |
df
是一个3x2的DataFrame,arr
是一个2x3的numpy数组
使用@
运算符,我们可以得到一个3x3的结果DataFrame
使用dot
方法,我们可以得到一个3x3的结果DataFrame
numpy库记录
生成序列
生成序列可以用到numpy的两个函数:
np.linspace
和np.arange
np.arange(start, stop, step)
:这个函数会生成一个从start开始,到stop结束(但不包含stop),步长为step的数列
例如,np.arange(0, 10, 1)
会生成一个从0开始,到10结束(但不包含10),步长为1的数列:[0,
1, 2, 3, 4, 5, 6, 7, 8, 9]
np.linspace(start, stop, num)
:这个函数会生成一个从start开始,到stop结束(默认包含stop),总共有num个元素的数列
例如,np.linspace(0, 10, 11)
会生成一个从0开始,到10结束(包含10),总共有11个元素的数列:[0.,
1., 2., 3., 4., 5., 6., 7., 8., 9., 10.]
总结一下,主要的区别在于:
np.arange()
是按照指定的步长来生成数列,而且不包含结束值
np.linspace()
是按照指定的元素个数来生成数列,而且默认包含结束值
axis
axis
可以指定对一个二维数组进行指定方向的操作,即可以按行方向(axis=1
),也可以按列方向(axis=0
)
例子如下
1 | import numpy as np |
得到的输出如下:
1 | # print(arr)的输出 |
可见,在求和函数中指定axis=0
是对每一列求和,axis=1
是对每一行求和
找最小值和对应的下标
比如找一个岭参数使误差最小,我建立了一个岭参数数组lambdaList = np.arange(1, 10.1, 0.1)
,再遍历其中每一个元素计算一个对应的误差存入数组Errors = []
中,我要找到最小的误差以及对应的下标,这样就能得知对应的岭参数了
其他语言里写一个循环就能实现,但是这里是Python,要会用轮子:np.argmin()
和np.min()
1 | print(np.min(Errors)) #找到Errors最小值 |
同理,还有max
和argmax
函数
np.zeros()
生成指定数组
numpy.zeros
函数用于创建指定形状和数据类型的数组,该数组填充为零。以下是其基本用法:
1 >numpy.zeros(shape, dtype=float, order='C')参数说明:
shape
:整数或整数的元组。新数组的形状,例如(2, 3)
或2
。dtype
:数据类型,可选。期望的数组数据类型,例如numpy.int8
。默认是numpy.float64
。order
:'C'
或'F'
,可选,默认为'C'
。是否以行主(C风格)或列主(Fortran风格)顺序在内存中存储多维数据。返回值:
- 返回一个填充有零的具有给定形状、数据类型和顺序的数组。
示例:
1
2
3
4
5
6
7
8
9
10 >import numpy as np
># 创建一个长度为5的零向量
>print(np.zeros(5))
># 创建一个2x2的零矩阵
>print(np.zeros((2, 2)))
># 创建一个自定义数据类型的零数组
>print(np.zeros((2,), dtype=[('x', 'i4'), ('y', 'i4')]))
我现在需要一个二维矩阵,我已知了行数和列数,可以用numpy
来产生一个矩阵
1 | #生成A矩阵:num行degree+1列 |
注意这里的参数是有两个括号:
在NumPy的
np.zeros()
函数中,我们需要使用两个括号,是因为这个函数的参数shape
需要一个元组来表示数组的形状。元组是Python中的一种数据类型,用于存储一组有序的元素。元组是不可变的,也就是说,一旦创建了元组,就不能更改它的内容。在
np.zeros()
函数中,如果我们想创建一个二维数组,我们需要提供一个包含两个元素的元组,其中第一个元素表示行数,第二个元素表示列数。例如,np.zeros((3, 4))
会创建一个3行4列的二维数组。所以,当我们写
np.zeros((3, 4))
时,外面的一对括号是函数调用的括号,里面的一对括号则是创建元组的括号。
矩阵相关
如我求岭参数用到的运算:
1 | Beta = np.linalg.inv(x_scaled.T @ x_scaled + lambda_i * np.eye(x_scaled.shape[1])) @ x_scaled.T @ y |
逆矩阵
np.linalg.inv()
为求逆运算
如果矩阵是numpy.matrix
类型,可以使用.I
属性来求逆矩阵,如:
1 | # 定义一个可逆矩阵 |
转置矩阵
x_scaled.T
是求x_scaled
的转置矩阵
也可以用np.transpose(x_scaled)
实现
矩阵乘法
@
是求矩阵乘积
也可以用np.dot()
函数实现
np.dot()
函数:
np.dot
是 numpy 库中的一个函数,主要用于矩阵的乘法运算,包括:向量内积、多维矩阵乘法、矩阵与向量的乘法。向量内积:向量是一维矩阵,两个向量进行内积运算时,需要保证两个向量包含的元素个数是相同的。
1
2
3
4
5 >import numpy as np
>x = np.array([1,2,3,4,5,6,7])
>y = np.array([2,3,4,5,6,7,8])
>result = np.dot(x, y)
>print(result) # 输出结果:168多维矩阵乘法:两个矩阵(x,y)如果可以进行乘法运算,需要满足以下条件:x为mxn阶矩阵,y为nxp阶矩阵,则相乘的结果result为mxp阶矩阵。
1
2
3
4
5 >import numpy as np
>x = np.array([[1,2,3], [4,5,6]])
>y = np.array([[2,3], [4,5], [6,7]])
>result = np.dot(x, y)
>print(result) # 输出结果:[[28 34] [64 79]]矩阵与向量乘法:矩阵x为mxn阶,向量y为n阶向量,则矩阵x和向量y可以进行乘法运算,结果为m阶向量。
1
2
3
4
5 >import numpy as np
>x = np.array([[1,2,3], [4,5,6]])
>y = np.array([1,2,3])
>result = np.dot(x, y)
>print(result) # 输出结果:[14 32]
获取矩阵行列信息
x_scaled.shape[1]
是获取x_scaled
的列数
同理,x_scaled.shape[0]
是获取行数
reshape()
函数
1 | y = np.array([ |
reshape(20, 1)
将y
这个一维数组转换成了一个二维数组,其中20
是行数,1
是列数。具体来说,
y
原本是一个包含20个元素的一维数组:
1
2
3
4 >y = np.array([
3, 4, 5, 5, 2, 4, 7, 8, 11, 8, 12,
11, 13, 13, 16, 17, 18, 17, 19, 21
>])通过
reshape(20, 1)
,y
被转换成了一个二维数组,有20
行和1
列。
1
2
3
4 >y = np.array([
[3], [4], [5], [5], [2], [4], [7], [8], [11], [8],
[12], [11], [13], [13], [16], [17], [18], [17], [19], [21]
>])
reshape
函数在使用时有一些限制。它要求每个维度的乘积必须等于原来的元素个数。也就是说,不能随意地改变数组的形状,必须保证改变后的形状与原始形状的元素总数相同。例如,如果你有一个包含12个元素的一维数组,你可以将其重塑为形状为(3, 4)或(2, 6)的二维数组,因为这些形状的元素总数仍然是12。但是,你不能将其重塑为形状为(5, 5)的数组,因为这需要25个元素,而原始数组只有12个元素。
此外,
reshape
函数允许使用-1
作为形状的一个维度。在这种情况下,-1
所在的维度的大小将根据数组的长度和其他维度的大小自动计算。例如,如果你有一个包含12个元素的一维数组,你可以使用reshape(-1, 4)
来将其重塑为一个有4列的二维数组,行数会根据元素总数自动计算。
也就是说,np.reshape
函数可以改变一个数组的形状,但是它不能增加数组的元素数量
numpy特有的下标访问方法
在Python的正常语法中,访问一个二维数组的元素与C++类似:
Mat[i][j]
,指定行列下标的方式
但是在numpy中(即用numpy创建的二维数组),可以用Mat[i, j]
的语法来访问第i
行第j
列的元素;这种方法称为“多维索引”
特殊下标
-1
这个下标可以表示最后一个元素(这个下标不止在numpy库中可以用,Python的列表也可以用,只是在numpy使用过程中遇见了,记录在此)
例如:
1 | # 创建一个列表 |
还可以使用:
表示一整行或一整列,这个叫“切片功能”,具体介绍如下
:
切片功能
例如:
Temp[:, i]
里的:
表示选中全部行,表示了Temp
的第i
列
Temp[i, :]
里的:
表示选中全部列,表示了Temp
的第i
行
比如编写克莱姆法则求解方程时就用到了这个,可以直接替换其中一列:
1 | #A为方阵,b为n维向量 |
再比如我有个矩阵A
,一个n维向量b
,我现在要创建一个关于A, b
的增广矩阵:
1 | #创建增广矩阵 |
切片功能的详细解释:
在numpy中,
:
操作符可以用来选择数组的一部分,这被称为切片(slicing)。在二维数组中,你可以使用两个:
操作符来分别选择行和列。切片的基本语法是
[start:stop:step]
:
start
:开始索引,即切片的起始位置。如果我们省略start
,则切片从第一个元素开始。
stop
:结束索引,即切片的结束位置。需要注意的是,结束索引是不包含在切片内的。如果我们省略stop
,则切片一直到数组的最后一个元素。
step
:步长,即切片中每两个元素之间的间隔。如果我们省略step
,则默认步长为1。例如,
arr[1:5]
表示从索引1
开始到索引5
(不包含)的元素,arr[0:8:2]
表示从索引0
开始到索引8
(不包含),步长为2
的元素,arr[3:]
表示从索引3
开始到最后的所有元素,arr[:]
表示数组的所有元素。在二维数组中,你可以使用逗号
,
来分隔行和列的切片。例如,arr[1:3, 2:4]
表示选择第1行到第2行(不包含第3行),第2列到第3列(不包含第4列)的元素。在之前的代码
Augmented_Mat[:, :A.shape[1]] = A
中,:
表示选择所有行,:A.shape[1]
表示选择从第一列到第n
列(即下标从0到A.shape[1] - 1,即前n列,不包括下标为A.shape[1]的列)。A.shape[1]
是矩阵A
的列数。所以,Augmented_Mat[:, :A.shape[1]]
表示的是增广矩阵的前n
列。
引用赋值
我在列主元高斯消元法的实现时碰到了这样一个问题:
我现在需要交换矩阵的两行,我这样写:
1 | #交换最大行和当前行(把主元放在对角线上) |
很常规的想法对吧,跟C++里交换两个变量一样,拿个Temp
存储即可
但是出问题了:我尝试输出每一次换行后的增广矩阵,输出结果出现了这样:
1 | [[429. 137. 398. 440. 382. -16.] |
竟然出现了全是0的几行,不可能啊?
就算会出现线性相关的两行,那概率应该是很低的,毕竟我生成随机数是从-100到500,运行了好几次都是这样的结果
于是我就把这段代码扔给bing,果然还是我对Python理解不够的问题:
可能的原因是你的代码在进行行交换时可能出现了问题。在你的代码中,你使用了一个临时变量
Temprow
来存储一行的值,然后交换两行。但是,你需要注意的是,在numpy
中,当你把一行赋值给一个变量时,这个变量实际上是指向那一行的一个引用,而不是那一行的一个副本。所以,当你修改这个变量的值时,原始矩阵的那一行的值也会被修改。在Python中,当你把一个对象(比如一个列表或者一个
numpy
数组的一行)赋值给一个变量时,这个变量实际上是指向那个对象的一个引用,而不是那个对象的一个副本。这意味着,如果你修改这个变量的值,原始对象的值也会被修改。让我们通过一个简单的例子来理解这个概念:
1
2
3
4
5
6
7
8
9
10
11
12
13 >import numpy as np
># 创建一个二维数组
>A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
># 把A的第一行赋值给row
>row = A[0, :]
># 修改row的值
>row[0] = 10
># 打印A
>print(A)这段代码会输出:
1
2
3 >[[10 2 3]
[ 4 5 6]
[ 7 8 9]]你可以看到,尽管我们只修改了
row
的值,但是A
的值也被修改了。这是因为row
是A
第一行的一个引用,而不是一个副本。所以,在你的代码中,当你写
Temprow = Augmented_Mat[col,:]
时,Temprow
实际上是Augmented_Mat[col,:]
这一行的一个引用。然后,当你写Augmented_Mat[col,:] = Augmented_Mat[maxRow_index,:]
时,Augmented_Mat[col,:]
这一行的值被修改了,Temprow
的值也会跟着改变,因为它是Augmented_Mat[col,:]
这一行的一个引用。为了避免这个问题,你可以使用
copy
函数来创建一个行的副本,然后进行交换。例如,你可以这样写:
1
2
3
4 >#交换最大行和当前行(把主元放在对角线上)
>Temprow = Augmented_Mat[col,:].copy()
>Augmented_Mat[col,:] = Augmented_Mat[maxRow_index,:]
>Augmented_Mat[maxRow_index,:] = Temprow这样,
Temprow
就是那一行的一个副本,而不是一个引用,你就可以安全地进行行交换了。
概括来说,我因为没能了解到“对象赋值是传递引用”这个细节而导致了出现两行完全一样的结果,于是一减就成一行全0了
好在numpy的数组都有.copy()
方法,可以直接创建一个副本,就直接解决问题了
matplotlib.pyplot库记录
重置一张图片
plt.cla()
:这个函数可以清除当前的图形。
plt.clf()
:这个函数可以清除当前的图形,并删除所有的子图。
plt.figure()
:这个函数会创建一个新的图形。如果在绘制新图形之前调用这个函数,那么新图形将会在一个新的空白图形上绘制。
用来正常显示负号和中文
1 | plt.rcParams['font.sans-serif'] = ['SimHei'] # SimHei是一种支持中文的字体 |
指定坐标轴
1 | # 设置x轴和y轴的图例 |
保存图片
保存图片用到plt.savefig()
函数
注意plt.savefig()
函数要用在plt.show()
前,否则不能正常保存
例如
1 | #画图 |
感觉不如查看图的时候点左下角的保存
sympy库记录
符号计算库真是太好用了,点名感谢如何用一行代码打败修炼三十年的高数老师 [南京大学 IT 侠 iTalk]这个视频,带给我符号计算的启蒙
subs()
函数替换
sympy
中,可以用变量来代换符号变量,用到subs()
函数,从而可以求值
subs()
的替换规则如下:
符号表达式.subs(待替换的变量,替换的值)
如f.subs(x,2)
1 | import sympy as sp |
例如我想给一个数组X
里每一个值代入符号式中,我可以写一个循环遍历这个数组,也可以直接代换:
1 | # 创建一个numpy数组 |
在这个例子中,
result.subs(x, val)
会将result
中的x
替换为val
,然后[result.subs(x, val) for val in X]
会为X
中的每个值执行这个替换操作,从而得到一个新的列表result_subs
也算是记录了Python的特殊写法吧
integrate()
积分
sympy的积分可以用integrate
计算:
sp.integrate(符号表达式,对谁积分(也可带范围表示定积分))
如:sp.integrate(f, x)
为不定积分,sp.integrate(f, (x, 0, 1))
为定积分
1 | import sympy as sp |
evalf()
求数值结果
在求积分过程中,我遇到了这样的情况:
1 | f1 = x/(6+5*x**2) |
输出的结果是定积分结果为:-log(6)/10 + log(726)/10
这代表的意思是: \(-\dfrac{\ln{6}}{10}+\dfrac{\ln{726}}{10}\)
可是我想直接输出数值结果,我就可以用evalf()
函数
1 | f1 = x/(6+5*x**2) |
这样写的输出结果就是:
用sympy求解的定积分结果为: 0.479579054559674
SymPy的
evalf
函数用于将符号表达式的结果转换为具体的数值,这个函数的基本用法如下:
1 >result = some_expression.evalf()在这里,
some_expression
是你想要求值的SymPy表达式,evalf
函数会返回这个表达式的数值结果。
evalf
函数还可以接受一个可选的参数,用于指定结果的精度。例如,some_expression.evalf(50)
会返回一个精确到50位有效数字的结果。此外,
evalf
函数还可以接受一个名为subs
的参数,这是一个字典,用于指定符号变量的值。例如,如果你的表达式中包含符号变量x
,你可以这样使用evalf
函数:
1 >result = some_expression.evalf(subs={x: 3.14})在这里,
subs={x: 3.14}
指定了x
的值为3.14。evalf
函数会先将x
替换为3.14,然后计算表达式的值。
可以看到,evalf
函数是用来算数值结果的,一定程度上类似于subs
,但与subs
的区别在于:
subs
和evalf
函数在SymPy库中都用于替换和求值,但它们的工作方式有所不同。
subs
函数用于替换表达式中的符号变量。例如,f.subs(x, 3)
会将表达式f
中的所有x
替换为3
。subs
函数返回的是一个新的表达式,这个表达式中的x
已经被替换为3
。需要注意的是,subs
函数不会改变原来的表达式。
evalf
函数用于将符号表达式的结果转换为具体的数值。例如,f.evalf()
会返回f
的数值结果。evalf
函数还可以接受一个名为subs
的参数,这是一个字典,用于指定符号变量的值。例如,f.evalf(subs={x: 3})
会先将f中的x
替换为3
,然后计算表达式的值。
subs
和evalf
函数的主要区别在于,subs
函数只进行替换,不进行求值,而evalf
函数则会进行求值。此外,evalf
函数在处理数值精度时比subs
函数更加精确。
再比如计算一个很复杂的积分:\(\int_0^{1}\sqrt{x}\ln{x}dx\)
1 | f2 = sp.sqrt(x) * sp.ln(x) |
注:此处用到了sympy的latex
函数,可以把一个数值结果转为\(\LaTeX\)表达式,参数仅仅只需要一个符号表达式即可,此处不过多展开
输出的东西就长这样,我不认识:
$$ \begin{align*} \int\limits_{0}^{1} \begin{cases} 2 \sqrt{x} \log{\left(x \right)} & \text{for}\: \frac{1}{x} < 1 \wedge x < 1 \\\sqrt{x} \log{\left(x \right)} & \text{for}\: \frac{1}{x} < 1 \vee x < 1 \\\frac{{G_{3, 3}^{0, 3}\left(\begin{matrix} \frac{3}{2}, \frac{5}{2}, 1 & \\ & \frac{3}{2}, \frac{3}{2}, 0 \end{matrix} \middle| {x} \right)} + \frac{3 {G_{3, 3}^{0, 3}\left(\begin{matrix} \frac{5}{2}, \frac{5}{2}, 1 & \\ & \frac{3}{2}, \frac{3}{2}, 0 \end{matrix} \middle| {x} \right)}}{2}}{x} - \frac{{G_{3, 3}^{2, 1}\left(\begin{matrix} 0 & \frac{5}{2}, \frac{5}{2} \\\frac{3}{2}, \frac{3}{2} & 0 \end{matrix} \middle| {x} \right)}}{x} & \text{otherwise} \end{cases}\, dx \end{align*} $$我明明就只想要一个数值结果,你给我这玩意,我咋看啊?
于是就可以用这个evalf
函数直接求出数值结果:
1 | f2 = sp.sqrt(x) * sp.ln(x) |
输出的结果就是我们想要的:-0.444444444444444
数字图像处理
jpg和png
虽然早都知道jpg有损压缩,但是一直只是当成一个小知识点记住的
你妈逼,第二次数字图像处理实验狠狠地给我上了一课,很几把生气
要求很简单,统计灰度值的直方图
1 | def draw_histogram(img): |
原图的直方图长这样:

很不错的开始对吧,画的很对是吧?
然而我处理完的图却他妈长这样:

图片,很神奇吧?
我百思不得其解啊,处理完的图像结果跟PPT上的一样,原图的直方图也画的很好,不可能是我函数错了啊?
最后问同学要了他图像处理的结果,一看图片后缀名:.png

就在这一刻,jpg有损压缩这个知识点像他妈闪击波兰的德国一样闪击了我的大脑,脑子里开始有闪电划过,名叫“一个啥b改了两个小时代码”的大纲和分镜如同寒武纪生命大爆发一样缓慢形成,然而愤怒逐渐超过理智占据了上风,于是这个分镜分着分着就变成了他妈的磁场转动100万匹,海虎爆裂拳,狠狠地锤向了桌子。
这是因为PNG和JPG这两种图像格式使用了不同的压缩算法。PNG使用的是无损压缩,这意味着保存为PNG格式的图像不会丢失任何原始图像数据。而JPG使用的是有损压缩,这意味着保存为JPG格式的图像在压缩过程中会丢失一些图像信息。
当你将同一张图像保存为PNG和JPG格式时,由于JPG的有损压缩,可能会导致一些像素值发生改变,这就可能导致两个直方图不完全相同。
此外,JPG的压缩算法在处理高频信息(例如边缘、纹理等)时,可能会引入一些压缩噪声,这也可能影响直方图的结果。
所以,即使你使用的是同一个算法,由于PNG和JPG的压缩方式的差异,得到的直方图可能会有所不同。
所以在我把处理的图像改为.png
以后,终于正常了
那么代价是什么呢?也就是我周六晚上的2个小时吧

读取彩色图像
我需要读取一张彩色图:
1 | Mat img4 = imread("实验指导书所用图像和数据\\experiment 3\\experm03_dairyGoatFace.png", CV_8UC3); |
得到的输出是1
在OpenCV中,
imread
函数的第二个参数是一个标志,用于指定应如何读取图像。CV_8UC3
并不是一个有效的标志。如果你想以彩色模式读取图像,你应该使用IMREAD_COLOR
标志。如果你想以灰度模式读取图像,你应该使用IMREAD_GRAYSCALE
标志。如果你没有指定标志,或者指定了一个无效的标志,那么
imread
函数将尝试以彩色模式读取图像。然而,如果图像本身就是一个灰度图像,那么即使你尝试以彩色模式读取它,你得到的仍然会是一个单通道图像。所以,如果你的图像
experm03_dairyGoatFace.png
本身就是一个灰度图像,那么无论你如何读取它,你得到的都会是一个单通道图像。你可以通过调用channels
函数来检查图像的通道数:
1
2 >Mat img4 = imread("实验指导书所用图像和数据\\experiment 3\\experm03_dairyGoatFace.png", IMREAD_COLOR);
>cout << "Number of channels: " << img4.channels() << endl;这段代码将输出图像的通道数。如果输出的是1,那么说明你的图像是一个灰度图像。如果输出的是3,那么说明你的图像是一个彩色图像。
前几次实验中也没碰到这种情况,以后知道了:
如果读灰度图,指定参数为IMREAD_GRAYSCALE
如果读彩色图,指定参数为IMREAD_COLOR
而CV_8UC3
则可以用来生成一张新的彩色图,同理CV_8UC1
可以生成一张新的灰度图
模板卷积范围
看看这个钝化掩膜和高提升滤波的结果,有点难绷对吧

这一步之前做了高斯滤波,给图像做了平滑,没有问题,所以问题出在接下来的一步:高提升滤波
关键问题就在于没有保证最后每个通道都在0-255
因为模板卷积的结果很可能超出[0,255]
这个区间,所以在最后赋值的时候需要做一步判断:超过255的赋值为255,低于0的赋值0
就是这样的操作:min(max((int)(src.at<Vec3b>(i, j)[0] + k * Masking_B[i][j]), 0), 255)
其中min()
和max()
函数用到了cmath
库
这块处理的代码如下
1 | //生成掩膜 |
C++中指针释放问题
我现在有一个函数:
1 | //对一个一维MyComplex数组傅里叶变换 |
这个函数内部实现了MyComplex* result = new MyComplex[N];
最后把这个指针return
了,给另外的函数计算用
现在我需要在一个新函数中调用这段函数,我应该在初始化这个指针时赋值为空:
1 | void MyFourier_image(int** image, int width, int height, int axis = 0) |
释放后记得置为空
关于NULL
和nullptr
的问题:
在C++中,
NULL
和nullptr
都可以用来表示空指针,但它们并不完全相同。
NULL
是C语言中的一个宏,通常被定义为整数0。在C++中,NULL
也可以用来表示空指针,但因为它实际上是一个整数,所以在某些情况下可能会引发问题。例如,如果你有一个函数重载,一个接受int
参数,一个接受指针参数,那么使用NULL
作为参数可能会调用错误的函数。
nullptr
是C++11引入的一个新关键字,用来表示空指针。它的类型是std::nullptr_t
,可以隐式转换为所有指针类型,但不能转换为其他类型,如int
。因此,使用nullptr
可以避免上述NULL
可能引发的问题。总的来说,如果你正在编写C++代码,推荐使用
nullptr
来表示空指针。如果你正在维护旧的C++代码,或者需要与C语言代码兼容,那么可能会看到NULL
。但是,你应该尽量避免在新的C++代码中使用NULL
。如果你需要表示空指针,应该使用nullptr
。这是一种更安全,更具表现力的方式。
频域
先说直观感受:我真是草了,频域真几把麻烦
DFT与IDFT
我还是没搞明白,挺麻烦的,这里只记录能够实现功能的代码
DFT:
1 | //DFT,把中间的img_DFT也传出去 |
如果不加Magnitude.convertTo(Magnitude, CV_8U, 255.0);
,最后在程序中的imshow
能正常显示,但是imwrite
以后却是全黑的
计算了傅立叶变换的幅度,并将其转换为对数尺度。
这是因为傅立叶变换的结果是复数,而我们通常关心的是每个频率分量的幅度。
然后,你进行了归一化和转换到0到255的范围,这是为了能够将结果显示为图像。
IDFT:
1 | //IDFT,传入的应该是img_DFT |
不能直接把刚刚return
的图片直接进来IDFT:
在你的代码中,你首先对图像进行了傅立叶变换(DFT),然后计算了幅度,并将其转换为对数尺度。这个过程改变了原始DFT结果的复数结构,因此你不能直接对这个结果进行逆傅立叶变换(IDFT)。
当你调用
dft(img_float, img_DFT);
时,img_DFT
保存了傅立叶变换的结果,这是一个复数矩阵。而当你计算幅度和进行对数转换后,Magnitude
只包含了这个复数矩阵的幅度信息,丢失了相位信息。因此,你不能直接对Magnitude
进行IDFT。
频域滤波
紧接着刚刚的DFT与IDFT,现在又加了几个新要求:
首先对其进行频率域平移的傅里叶变换,接着再分别进行低通滤波、高通滤波和巴特沃斯滤波,最后分别对滤波的结果再进行逆傅里叶变换,得到滤波结果
频域平移还是很好实现的:
1 | //四角移动,频域移动 |
关于滤波,我一开始是这样写的:
1 | //这是错误的写法,得到的是错误的结果 |
得到了一张很奇怪的图:

太几把怪了,我就去问了bing,解答如下:
你的代码中,低通滤波的部分可能存在一些问题。你在傅立叶变换的结果上应用了
blur()
函数,这可能会导致结果不正确。因为blur()
函数是在空间域进行操作的,而不是在频率域。在频率域进行滤波,通常的做法是创建一个滤波器,然后将其与傅立叶变换的结果相乘。例如,你可以创建一个低通滤波器,它在中心附近的值为1,在远离中心的地方逐渐变为0。然后,你可以将这个滤波器与
img2_shift_DFT
相乘,从而实现低通滤波。
问题就在于我的滤波是对空间域滤波,而不是对频率域滤波
正确的写法如下:
1 | Mat img2 = imread("实验指导书所用图像和数据\\experiment 5\\experm05_flower.png", IMREAD_GRAYSCALE); |
得到了一看就很正常的结果:

DCT
离散余弦变换
很几把搞笑
1 | //笑死我了这个函数 |
我他妈用img.convertTo(img, CV_32F, 1.0 / 255);
的时候,imshow
正确imwrite
全黑
我用img.convertTo(img, CV_32F);
的时候imwrite
正确imshow
花屏
给哥们整不会了
我直接左右开弓,他妈的你不是又要imshow
又要imwrite
吗?我就给你对应正确的
有了DCT,紧接着就是IDCT
经过测试,只有idct(img_DCT_imshow, img_IDCT)
才能正常显示,idct(img_DCT_imwrite, img_IDCT)
得到的是全白图片