1. 程式人生 > >雙線性插值法,最鄰近法 處理圖片的旋轉,放大

雙線性插值法,最鄰近法 處理圖片的旋轉,放大

對於一張圖片旋轉某個角度,其實就是把每個畫素計算好它的位置,再對對應的位置設定畫素值即可,以順時針為例,如下圖,由P點旋轉到P',

x=rcos(a)
y=rsin(a)
x'=rcos(a+b)=rcos(a)cos(b)-rsin(a)sin(b)
y'=rsin(a+b)=rsin(a)cos(b)+rcos(a)sin(b)

可得x'=xcos(b)-ysin(b)    y'=xsin(b)+ycos(b)

寫成矩陣形式是

\begin{bmatrix} x'\\ y'\\ 1 \end{bmatrix} = \begin{bmatrix} cos (b) &-sin(b) &0 \\ sin(b))& cos(b)) &0 \\ 0&0 & 1 \end{bmatrix}\begin{bmatrix} x\\ y\\ 1 \end{bmatrix}                    1)

然而需要考慮的是由於以圖片中心點為旋轉點,而影象是基於矩形影象的,所以必須將旋轉後的影象放在一個能包含這個影象的矩形框中處理,稱為包圍盒,若只考慮最大情況,則是一個 \sqrt{w^2 + h^2}

的正方形,而要是想剛好可以包圍住整個圖形,則寬度為w*cos(b) + h*sin(b),高度為w*sin(b) + h*cos(b).

而由於在計算機中圖形原點是在左上角的,所以需要變換一下座標,即把左上角(0,0)點當做(-w/2 , -h/2)來處理變換完成後在設定處理後圖片的畫素顏色值的時候把計算後的位置再加上(-w/2 , -h/2)即可

矩陣形式可寫成

\begin{bmatrix} cos (b) &-sin(b) &0 \\ sin(b))& cos(b)) &0 \\ 0&0 & 1 \end{bmatrix}\begin{bmatrix} 1 &0 &-\frac{w}{2}\\ 0&1 &-\frac{h}{2} \\ 0& 0 &1 \end{bmatrix}*\begin{bmatrix} x\\ y\\ 1 \end{bmatrix}                                   2)

此時,只要給出x,y的值經過矩陣運算再分別加上w/2,h/2即可得到對應點的位置,處理結果如下

發現有很多有規律的噪聲,因為在處理過程中很多點計算後是浮點數,而圖片中畫素位置都是整數,所以有些點就沒有被正確設定到。既然這樣,可以反其道而行之,可以根據目標影象的大小,依次進行剛剛的逆運算,看看這個畫素是不是應該由影象旋轉而來的,即判斷點是否在原圖的範圍之內。根據 2)的逆運算可以得到在原圖上的位置。但是經過逆運算後往往得到的在原圖位置的座標也是一個浮點數,這時候就要用到雙線性插值法來得到近似的畫素值,雙線性插值介紹如下:

上圖中給出了公式 f(i+u ,j+v) = (1-u)(1-v)f(i,j) + u(1-v)f(i+1,j) + (1-u)vf(i,j+1) + uvf(i+1,j+1)

處理後結果如下

可以發現平滑了很多,但是此方法計算量較大,速度較慢一點。有一種簡單的叫最鄰近插值法,即在原圖臨近的四個點中取最近的一個點。

python程式碼如下:

from PIL import Image
import numpy as np 



def rotate(img,angle):
	#maxEdge = int(np.sqrt(img.width ** 2 + img.height ** 2))
	beta = angle/180 * np.pi
	newWidth = int(img.width * np.cos(beta) + img.height * np.sin(beta))
	newHeight = int(img.width * np.sin(beta) + img.height * np.cos(beta))
	desImg = Image.new(img.mode,(newWidth,newHeight))
	#print(desImg.size)
	convertMatrix = [[np.cos(beta),-np.sin(beta),0],[np.sin(beta),np.cos(beta),0],[0,0,1]]
	#m其實是為了乘上pos使得以圖片中心點為原點的,但本身前面還要乘 convertMatrix,所以在前面先乘好
	m = [[1,0,-img.width/2], [0,1,-img.height/2], [0,0,1]]
	convertMatrix = np.dot(convertMatrix,m)
	for x in range(img.width):
		for y in range(img.height):
			color = img.getpixel((x,y))
			pos = [x ,y ,1]
			pos_convert = np.dot(convertMatrix,pos)
			#print(pos_convert)
			#print(pos_convert + [maxEdge / 2,maxEdge / 2,maxEdge / 2])
			desImg.putpixel((int(pos_convert[0] + newWidth / 2),int(pos_convert[1] + newHeight / 2)),color)
	return desImg

def backRotate(img,angle):
	beta = angle/180 * np.pi
	newWidth = int(img.width * np.cos(beta) + img.height * np.sin(beta))
	newHeight = int(img.width * np.sin(beta) + img.height * np.cos(beta))
	desImg = Image.new(img.mode,(newWidth,newHeight))

	convertMatrix = [[np.cos(beta),-np.sin(beta),0],[np.sin(beta),np.cos(beta),0],[0,0,1]]
	m = [[1,0,-img.width/2], [0,1,-img.height/2], [0,0,1]]
	convertMatrix = np.dot(convertMatrix,m)
	convertMatrix_inv = np.linalg.inv(convertMatrix) #逆矩陣

	for x in range(newWidth):
		for y in range(newHeight):
            #由於之前是先轉換後再加上寬高的一半得到的座標,所以此處做逆運算時就要先減去寬高的一半
			pos = [int(x-newWidth/2) , int(y-newHeight/2) ,1]
			originPos = np.dot(convertMatrix_inv,pos)
			if (img.width >  originPos[0] + 1 and img.height >  originPos[1] + 1) and (originPos>=0).all():
				
				x_low = np.floor(originPos[0])
				x_up = np.ceil(originPos[0])
				y_low = np.floor(originPos[1])
				y_up = np.ceil(originPos[1])

				s = originPos[0] - x_low
				t = originPos[1] - y_low
				
				try:
					p1 = np.array(img.getpixel((x_low,y_low)))
					p2 = np.array(img.getpixel((x_up,y_low)))
					p3 = np.array(img.getpixel((x_low,y_up)))
					p4 = np.array(img.getpixel((x_up,y_up)))

					colorReal = np.array((1-s)*(1-t)*p1+(1-s)*t*p3+(1-t)*s*p2+s*t*p4,dtype="int")
					desImg.putpixel((x,y),tuple(colorReal))
				except:
					print(x_low," , ",x_up, " , ",y_low," , ",y_up," ",originPos)
				
				
				

	
	return desImg

img = Image.open("D:\\lena.jpg")
#rotate(img,30).save("D:\\1.jpg")
backRotate(img,45).save("D:\\2.jpg")

下面是放大,放大同樣是使用下面的公式

\begin{bmatrix} sx &0 &0 \\ 0& sy & 0\\ 0& 0& 1 \end{bmatrix}*\begin{bmatrix} x\\ y\\ 1 \end{bmatrix}=\begin{bmatrix} x'\\ y'\\ 1 \end{bmatrix}

但跟上面的旋轉有同樣的問題當縮放倍數大於1時,則會出現有規律的噪點,下圖為常規方式放大效果

同樣這個問題可以使用雙線性插值法來解決,解決後的效果如下

可以看出效果好多了。

推導方式與上面旋轉類似,同樣是乘一下轉換的逆矩陣即可得到在原圖中的位置,python程式碼如下:

def Scale(img,sx,sy):
	desImg = Image.new(img.mode,(int(img.width * sx),int(img.height *sy)))
	convertMatrix = [[sx,0,0],[0,sy,0],[0,0,1]]
	for x in range(img.width):
		for y in range(img.height):
			color = img.getpixel((x,y))
			pos = [x,y,1]
			pos_convert = np.dot(convertMatrix,pos)
			pos_convert = np.dot(convertMatrix,pos)
			#print(pos_convert)
			desImg.putpixel((int(pos_convert[0]),int(pos_convert[1])),color)
	print("origin size: ",img.size)
	print("new size: ",desImg.size)
	return desImg
def backScale(img,sx,sy):
	desImg = Image.new(img.mode,(int(img.width * sx),int(img.height *sy)))
	convertMatrix = [[sx,0,0],[0,sy,0],[0,0,1]]
	convertMatrix_inv = np.linalg.inv(convertMatrix)
	for x in range(int(img.width * sx)):
		for y in range(int(img.height *sy)):
			 #color = img.getpixel((x,y))
			pos  = [x,y,1]
			originPos = np.dot(convertMatrix_inv , pos)
			if (img.width >=  originPos[0] + 1 and img.height >=  originPos[1] + 1) and (originPos>=0).all():
				x_low = np.floor(originPos[0])
				x_up = np.ceil(originPos[0])
				y_low = np.floor(originPos[1])
				y_up = np.ceil(originPos[1])

				s = originPos[0] - x_low
				t = originPos[1] - y_low
				
				try:
					p1 = np.array(img.getpixel((x_low,y_low)))
					p2 = np.array(img.getpixel((x_up,y_low)))
					p3 = np.array(img.getpixel((x_low,y_up)))
					p4 = np.array(img.getpixel((x_up,y_up)))

					colorReal = np.array((1-s)*(1-t)*p1+(1-s)*t*p3+(1-t)*s*p2+s*t*p4,dtype="int")
					desImg.putpixel((x,y),tuple(colorReal))
				except:
					print(x_low," , ",x_up, " , ",y_low," , ",y_up," ",originPos)
	return desImg

最鄰近插值只需要在判斷語句下面更改成找尋最鄰近的點即可