本章導(dǎo)航:
- 介紹 SymPy 的基礎(chǔ)語法窗市。
- 舉了兩個(gè)實(shí)例介紹如何使用 SymPy 做數(shù)學(xué)研究先慷。
9.1 Python 中的數(shù)學(xué):符號(hào)計(jì)算
Python 有許多優(yōu)秀的處理數(shù)學(xué)的工具,本章僅僅介紹符號(hào)計(jì)算庫:SymPy咨察。
9.1.1 使用 SymPy 表示數(shù)
Sympy 庫完全由 Python 寫成论熙,支持符號(hào)計(jì)算、高精度計(jì)算摄狱、模式匹配脓诡、繪圖、解方程媒役、微積分祝谚、組合數(shù)學(xué)、離散 數(shù)學(xué)酣衷、幾何學(xué)交惯、概率與統(tǒng)計(jì)、物理學(xué)等方面的功能穿仪。
復(fù)數(shù)一般使用 的形式進(jìn)行表示席爽。其中
,而
是虛數(shù)的單位啊片。在 SymPy 中針對這些特定的“數(shù)”拳昌,有專門的符號(hào)表示。
使用 I
表示 钠龙,使用
Rational
表示分?jǐn)?shù)。
from sympy import I, Rational, sqrt, pi, E, oo
3 + 4I, Rational(1, 3), sqrt(8), sqrt(-1), pi, E**2, oo
輸出的結(jié)果是:
可以看出,SymPy 可以完美的表示我們熟悉的各種數(shù)學(xué)中定義的“數(shù)”碴里。
9.1.2 使用 SymPy 提升您的數(shù)學(xué)計(jì)算能力
為什么要使用 SymPy 呢沈矿?直接使用 Python 的 math
庫不就可以了嗎?您也許會(huì)有諸如此類的疑問咬腋。在解釋原因之前羹膳,我們來看一個(gè)例子:
import math
math.sqrt(8)
輸出的結(jié)果是:
2.8284271247461903
2.8284271247461903
?您沒有看出根竿,由于計(jì)算機(jī)是以二進(jìn)制方式進(jìn)行數(shù)學(xué)運(yùn)算的陵像,故而計(jì)算虛數(shù)的值總是不精確的,而 SymPy 的計(jì)算結(jié)果便是精確的寇壳。
import math, sympy
math.sqrt(8) ** 2, sympy.sqrt(8) ** 2
輸出結(jié)果是:
(8.000000000000002, 8)
9.1.2.1 對數(shù)學(xué)表達(dá)式進(jìn)行簡化與展開
在 SymPy 中使用 symbols
定義數(shù)學(xué)中一個(gè)名詞:變量醒颖。使用 symbols
創(chuàng)建多個(gè)變量名時(shí),請使用空格或者逗號(hào)進(jìn)行隔開壳炎,即 symbols('x y')
或者 symbols('x,y')
的形式泞歉。
from sympy import symbols
x, y = symbols('x y')
expr = x + 2*y
expr
輸出結(jié)果:
這樣,我們便可以將 expr
當(dāng)作數(shù)學(xué)中的表達(dá)式了:
expr + 1, expr - x
輸出結(jié)果:
,
既然談到數(shù)學(xué)表達(dá)式匿辩,總會(huì)涉及的其的展開與化簡腰耙。比如 ,可以這樣:
from sympy import expand, factor
expanded_expr = expand(x*expr)
expanded_expr
輸出結(jié)果是:
我們還可以將其展開式轉(zhuǎn)換為因子的乘積的形式:
factor(expanded_expr)
輸出的結(jié)果是:
SymPy 不僅僅如此铲球,它還可以支持 LATEX 公式:
alpha, beta, nu = symbols('alpha beta nu')
alpha, beta, nu
輸出結(jié)果是:
需要注意的是 symbols
定義的變量 x
, y
等不再是字符串挺庞,您將其看作是數(shù)學(xué)中的變量即可。
9.1.2.2 求導(dǎo)稼病,微分选侨,定積分,不定積分溯饵,極限
先載入一些會(huì)用到的函數(shù)侵俗,方法,符號(hào):
from sympy import symbols, sin, cos, exp, oo
from sympy import expand, factor, diff, integrate, limit
x, y, t = symbols('x y t')
在 SymPy 中使用 diff
對數(shù)學(xué)表達(dá)式求導(dǎo)丰刊,比如:隘谣,使用 SymPy,則有:
diff(sin(x)*exp(x), x)
輸出結(jié)果是:
在 SymPy 中使用 integrate
求解不定積分與定積分啄巧。比如寻歧,,即:
integrate(exp(x)*sin(x) + exp(x)*cos(x), x)
輸出結(jié)果:
定積分
integrate(sin(x**2), (x, -oo, oo))
輸出結(jié)果:
在 SymPy 中使用 limit
求極限秩仆,比如 码泛,即:
limit(sin(x)/x, x, 0)
輸出結(jié)果:
1
9.1.2.3 解方程
from sympy import solve, dsolve, Eq, Function
可以使用 SymPy 的 solve
求解 這種類型的方程。比如澄耍,求解
:
solve(x**2 - 2, x)
輸出結(jié)果是:
您還可以使用 SymPy 的 dsolve
函數(shù)求解微分方程:
y = Function('y')
dsolve(Eq(y(t).diff(t, t) - y(t), exp(t)), y(t))
輸出 的結(jié)果:
小貼士:您可以使用 sympy.latex
將您的運(yùn)算結(jié)果使用 LATEX 的形式打印出來:
from sympy import latex
latex(Integral(cos(x)**2, (x, 0, pi)))
輸出結(jié)果:
\int\limits_{0}^{\pi} \cos^{2}{\left(x \right)}\, dx
9.1.3 使用 SymPy 求值
在 9.4.2 中我們已經(jīng)介紹了 SymPy 的大多數(shù)符號(hào)運(yùn)算機(jī)制噪珊,接下來將討論如何通過“賦值”(數(shù)學(xué)中的變量賦值)來獲取表達(dá)式的值晌缘。如果您想要為表達(dá)式求值,可以使用 subs
函數(shù)(Substitution痢站,即置換):
x = symbols('x')
expr = x + 1
expr.subs(x, 2)
輸出的結(jié)果為:
3
正是我們想要得到的預(yù)期結(jié)果磷箕。
也可以使用 subs
函數(shù)作為參數(shù)置換,比如:
在 9.1.2.3 中出現(xiàn)的符號(hào) Eq
是用來表示兩個(gè)數(shù)學(xué)表達(dá)式的相等關(guān)系阵难。比如岳枷, ,可以這樣:
Eq(x+1, 4)
輸出結(jié)果:
又比如呜叫,空繁,可以這樣:
Eq((x + 1)**2, x**2 + 2*x + 1)
輸出結(jié)果:
如果您想要判斷兩個(gè)數(shù)學(xué)表達(dá)式是否相等,您有兩種方式朱庆。下面我們來判斷
方式一:使用 equals
盛泡。
a = cos(x)**2 - sin(x)**2
b = cos(2*x)
a.equals(b)
輸出結(jié)果為 True
符合預(yù)期。
方式二:使用 simplify
化簡等式:
simplify(a - b)
輸出結(jié)果為 0
也符合預(yù)期椎工。
當(dāng)然饭于,也可以作圖:
9.1.4 使用 SymPy 做向量運(yùn)算
在 SymPy 中分別使用 Point
,Segment
來表示數(shù)學(xué)中的點(diǎn)與線段實(shí)體(entity)维蒙。
from sympy import Segment, Point
from sympy.abc import x
a = Point(1, 2, 3)
b = Point([2, 3])
c = Point(0, x)
a, b, c
輸出結(jié)果是:
(Point3D(1, 2, 3), Point2D(2, 3), Point2D(0, x))
即 Point
用來表示數(shù)學(xué)中的點(diǎn)(向量):掰吕,其中
。對于
Segment(x1,x2)
中的參數(shù) x1
颅痊,x2
可以是 Point
實(shí)例殖熟,也可以是元組或者列表,array 等斑响。但是 x1
與 x2
代表的點(diǎn)的維度必須一樣菱属。Segment(x1,x2)
表示一個(gè)有向的線段,即矩形的對角線方向?yàn)?舰罚。比如纽门,定義下圖9.4.1的實(shí)體:
x1 = Point(1,1)
x2 = Point(2,2)
s = Segment(x1,x2)
圖中的有向?qū)蔷€(向量)可以通過 s.direction
進(jìn)行計(jì)算:
s.direction
輸出結(jié)果為:
??????????2??(1,1)
即向量 。在許多領(lǐng)域中营罢,一般將水平向右作為
軸赏陵,水平向下作為
軸。左上角作為原點(diǎn)
饲漾。
有了對角線的方向蝙搔,便可以計(jì)算矩形框的寬與高:
w, h = s.direction
由于 Point
指代數(shù)學(xué)中的向量,故而其存在加法考传、減法吃型、數(shù)乘以及內(nèi)積運(yùn)算,它們均被重載為 Python 的運(yùn)算符僚楞。比如勤晚,我們定義向量 枉层,且
,可有:
a = Point(0,1)
b = Point(1,0)
c = Point(1,1)
下面我們對它們進(jìn)行加法運(yùn)算:
a + b == c
輸出結(jié)果為 True
符合預(yù)期赐写。同樣有:
a - b, c * 2, a.dot(c)
輸出結(jié)果為:
(Point2D(-1, 1), Point2D(2, 2), 1)
所有結(jié)果均符合預(yù)期返干。下面我們再來看看 Segment
:
ab = Segment(a, b)
ac = Segment(a, c)
ab.direction, ac.direction
輸出的結(jié)果是:
(Point2D(1, -1), Point2D(1, 0))
我們可以計(jì)算 ab
與 ac
的方向向量的夾角余弦:
ab.direction.dot(ac.direction) / (ab.length * ac.length)
輸出結(jié)果為 ,即
血淌。很容易求得
。更方便的是财剖,
Segment
提供了函數(shù)直接計(jì)算夾角:
ab.angle_between(ac)
輸出結(jié)果也是 悠夯。是不是很方便?如果您想要獲取
Segment
實(shí)例的全部點(diǎn)可以調(diào)用 ab.points
躺坟,分別調(diào)取各點(diǎn)則可以使用 ab.p1
與 ab.p2
沦补。
9.2 一個(gè)案例:實(shí)現(xiàn)圖像局部 resize 保持高寬比不變
下面我們利用 9.4 節(jié)學(xué)習(xí)的東西構(gòu)造一個(gè)實(shí)現(xiàn)圖像局部 resize 保持高寬比不變的模型。
- 題設(shè):假設(shè)有一張圖片
的尺寸為
咪橙,
存在一個(gè)目標(biāo)物
夕膀,
的尺寸為
。
- 目標(biāo):將
resize 為尺寸是
美侦,而約束條件是保證 resize 之后的邊界為
(注意:這里的
表示下邊界長度)产舞。需要盡可能保持 resize 之后的圖片塊寬高比不變,即滿足:
其中 指的是上邊界的長度菠剩。這里只有
的未知量易猫,我們可以直接求出:
為了保證 resize 的一致性,需要在原圖補(bǔ)邊 對應(yīng)于
具壮。下面給出它們的計(jì)算公式:
同時(shí)還需要保證 resize 前后的寬高與邊界的比例不變:
聯(lián)合上述公式准颓,便可以求出所有未知量。至此棺妓,模型建立完畢攘已。下面考慮使用 Python 實(shí)現(xiàn)。
from sympy import Point, Segment, symbols, Eq, solve
from dataclasses import dataclass
@dataclass
class Resize:
W: int
H: int
w: int
h: int
w_m: int
h_m: int
def model(self):
H_m, W_m, H_p, h_p = symbols("H_m, W_m, H_p, h_p")
eq1 = Eq(self.W/self.H, (self.w - 2 * self.w_m)/(self.h - self.h_m - h_p))
eq2 = Eq(self.w/self.h, (self.W + 2 * W_m)/(self.H + H_m + H_p))
eq3 = Eq(self.W/W_m, self.w/self.w_m)
eq4 = Eq(self.H/H_m, self.h/self.h_m)
R = solve([eq1, eq2, eq3, eq4], [H_m, W_m, H_p, h_p])
return R
# 傳入已知量
W, H = 100, 100
w, h = 32, 32
w_m, h_m = 5, 5
# 模型建立
r = Resize(W, H, w, h, w_m, h_m)
# 模型求解
R = r.model()
# 查看求解結(jié)果
R
輸出結(jié)果為:
{H_m: 15.6250000000000,
W_m: 15.6250000000000,
H_p: 15.6250000000000,
h_p: 5.00000000000000}
這樣我們完成了設(shè)定的目標(biāo)怜跑。為了更直觀样勃,從網(wǎng)上找一張貓的圖片作為例子。為了可視化的方便妆艘,我們需要定義一個(gè)畫邊界框的函數(shù):
def bbox_to_rect(bbox, color):
# 將邊界框(左上x, 左上y, 右下x, 右下y)格式轉(zhuǎn)換成matplotlib格式:
# ((左上x, 左上y), 寬, 高)
return plt.Rectangle(
xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
fill=False, edgecolor=color, linewidth=1)
該函數(shù)的參數(shù) bbox
取值為 彤灶,
color
指定框的顏色。
from matplotlib import pyplot as plt
%matplotlib inline
# 讀取圖片
img = plt.imread("cat.jpg")
bbox = [65, 25, 235, 175] # 框的坐標(biāo)
fig = plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(bbox, 'blue'))
plt.show()
輸出結(jié)果為:
數(shù)組 img
指代 批旺,而圖中的“貓”(即
)我們可以使用切片的方式獲取幌陕,即
img[bbox[1]:bbox[3]+1,bbox[0]:bbox[2]+1]
(這里需要注意,切片的第一維度為高)汽煮,即:
patch = img[bbox[1]:bbox[3]+1,bbox[0]:bbox[2]+1]
plt.imshow(patch)
plt.show()
輸出結(jié)果為:
我們可以直接求得貓片的寬和高:h, w = patch.shape[:2]
,但是心例,patch
是由 bbox 獲取的宵凌,故而,我們也可以直接求得:
def get_aspect(bbox):
'''
計(jì)算 bbox 的寬和高
'''
# 加 1 是因?yàn)橄袼攸c(diǎn)是的尺寸為 1x1
w = bbox[2] - bbox[0] + 1
h = bbox[3] - bbox[1] + 1
return w, h
# 獲取寬高
W, H = get_aspect(bbox)
我們想要將貓片 resize 為 止后,則可以設(shè)定:
w, h = 480, 480
w_m, h_m = 5, 5
接著瞎惫,計(jì)算邊界:
r = Resize(W, H, w, h, w_m, h_m)
R = r.model()
H_m, W_m, H_p, h_p = R.values()
由于這里獲取的值是浮點(diǎn)數(shù),我們需要將其轉(zhuǎn)換為整數(shù):
def float2int(*args):
return [int(arg)+1 for arg in args]
# 獲取整數(shù)型數(shù)據(jù)
H_m, W_m, H_p, h_p = float2int(*R.values())
最后译株,我們原圖與 resize 之后的圖片展示如下:
import cv2
patch = img[bbox[1]-H_p:bbox[3]+1+H_m,bbox[0]-W_m:bbox[2]+1+W_m]
resize = cv2.resize(patch, (w, h))
plt.imshow(patch)
plt.title("origin")
plt.show()
plt.imshow(resize)
plt.title("resize")
plt.show()
輸出結(jié)果為:
本章的代碼邏輯并不是很嚴(yán)謹(jǐn)瓜喇,主要是提供如何一個(gè)創(chuàng)建計(jì)算機(jī)視覺軟件的思路,您仔細(xì)研究本章的軟件設(shè)計(jì)思路將會(huì)收獲很多的歉糜。