[英]How to Rotate a Bitmap by Small Amount in R
問題:
這與圖像數據的MNIST表示有關。 我想對圖像進行精細旋轉。 所有圖像的集合表示為data.frame,其中每一行(每個圖像)是785個字符向量; 第一個字符是數字的值; 其余784個像素是扁平的28x28像素矩陣。
我想有效地旋轉784個字符,以生成許多附加的旋轉圖像; 即我想設計一些新數據!
我已經使用以下代碼完成了此操作,但是現在我想做的是使其更高效。 在2.7GHz i7機器上使用6個線程,目前需要花費數小時才能運行42,000張圖像。
####################################################################################################
# rotateImage() -- generates new images rotated from -angleDegree tp +angleDegree
# images will be returned as a data.frame
####################################################################################################
xOffset = 14
yOffset = 14
rotateImage <- function(imgData,angleDegree) {
for (t in -angleDegree:angleDegree) {
newImgData <- t(as.data.frame(rep(0,785)))
newImgData[1,1]=imgData[1,1] # set the actual digit value to imgData value
for (j in 1:28) {
for (i in 1:28) {
newI = round(xOffset + (((i-xOffset)*cos(t*pi/180)) + ((j-yOffset)*sin(t*pi/180))))
newJ = round(yOffset + (((xOffset-i)*sin(t*pi/180)) + ((j-yOffset)*cos(t*pi/180))))
if ((newI %in% 1:28) && (newJ %in% 1:28)) {
newImgData[1,1 + newI + ((newJ-1)*28)] = imgData[1,1+i+((j-1)*28)]
}
}
}
if (exists("retImages")) {
retImages <- rbind(retImages,newImgData)
} else {
retImages = newImgData
}
}
retImages
}
####################################################################################################
# various globals
degreesToRotate = 5
# one sample image
sampleData = t(as.data.frame(c(8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,34,0,0,0,57,136,162,245,203,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,147,249,253,224,232,232,6,81,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,91,254,253,242,128,17,97,240,149,254,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,57,253,254,151,38,0,0,47,253,253,228,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,221,254,160,0,0,0,0,164,254,228,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,253,253,9,0,0,17,130,251,223,73,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,253,253,17,0,19,199,254,223,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,186,253,235,101,199,253,195,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,210,255,254,254,228,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,254,253,253,211,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,149,254,215,232,253,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,125,253,228,15,107,253,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,254,228,0,0,17,235,229,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130,243,164,15,0,0,13,222,241,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,119,254,206,9,0,0,0,141,253,142,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,234,228,40,0,0,0,102,240,219,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,141,254,102,0,0,0,128,245,188,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,207,168,0,9,89,172,254,160,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,207,169,83,174,242,230,80,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,140,253,228,143,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)))
# image rotated from -5 to 5 degrees:
rotations <- rotateImage(sampleData,degreesToRotate)
# plot the images to verify correctness
library(raster)
bw=grey.colors(2,start=0,end=1)
for (i in 1:nrow(rotations)) {
pixData <- rotations[i,2:785]
dim(pixData) <- c(28,28)
image(pixData, col=bw)
}
基本上,這是從關閉所有像素開始的(這樣就不用擔心搖晃了-我做的是細微旋轉,而不是45度的東西),並且在兩個循環中,所有“開”像素都將移動到新位置進行旋轉圖片。 (順便說一下,這是在原始圖像的同一“幀”內在R中進行細角度圖像旋轉的一種簡單方法。)
我想通過運行更快的某種奇特的單線轉換來消除i和j循環。 遵循這些思路的任何技巧或其他新穎的想法,將不勝感激。
一個解法:
一個被接受的答案(見下面的@ 42-)利用了TsparseMatrix。 並已在此處稍微修改為一個函數:
####################################################################################################
xOffsetM = 13
yOffsetM = 13
rotateImageM <- function(imgData,angleDegree) {
for (t in -angleDegree:angleDegree) {
rotMat <- matrix( c( cos(t*pi/180), sin(t*pi/180), -sin(t*pi/180), cos(t*pi/180)), 2, 2)
M <- as(matrix(imgData[,2:785], 28, 28), "TsparseMatrix")
idxs <- round(cbind(M@i - xOffsetM, M@j - yOffsetM) %*% rotMat)
M@i <- as.integer(idxs[,1]+xOffsetM) # back shift to right and ..
M@j <- as.integer(idxs[,2]+yOffsetM) # back "up"
newImgData <- imgData
newImgData[,2:785] <- t(as.data.frame(as.vector(M)))
if (exists("retImages")) {
retImages <- rbind(retImages,newImgData)
} else {
retImages <- newImgData
}
}
retImages
}
對於其他對此類問題感到好奇的人,您可能會發現圖像尺寸(28x28)是任意的-特定於我正在研究的示例。 這些也決定了“偏移”的選擇,在解決方案中是14還是13。 因此,應輕松將其擴展到任何尺寸的圖像。
在一天結束時...
提出的解決方案都可以使用,但是對於大量圖像仍然有點慢或內存效率低下。 我最終在VB.Net中編寫了一個簡單的應用程序(我很清楚,沒有其他理由與其他原因相比),它處理了42,000張rotateImage
圖像,使用原始的rotateImage
根據10次旋轉生成了11倍的rotateImage
方法...並在6分鍾內完成,包括所有對非SSD磁盤的讀取/寫入。 我仍然不確定為什么我不能讓R以大致相同的效率翻閱所有內容。
這演示了處理sparseMatrix表示形式的策略:
M <- as( matrix( sampleData[-1], 28), "TsparseMatrix")
idxs <- floor( cbind( M@i-13, M@j-13) %*%
# This implicitly shifts the rows down and columns left by 14 since sparse i-j's are 0-based
matrix( c(cos(5*2*pi/360), sin(5*2*pi/360),
-sin(15*2*pi/360), cos(5*2*pi/360)), 2,2) ) # 5 degrees
M@i <- as.integer( idxs[,1]+14) # back shift to right and ..
M@j=as.integer( idxs[,2]+14) # back "up"
image( matrix(sampleData[-1], 28) )
image( as.matrix(M) )
另一種方法是使用Bioconductor軟件包EBImage提供的功能, EBImage是R的圖像處理和分析工具箱。 要安裝軟件包,請使用:
source("http://bioconductor.org/biocLite.R")
biocLite("EBImage")
然后,您可以將圖像數據表示為Image
對象,並使用rotate
函數對其進行轉換。 下面的代碼對此進行了說明。
如果原始像素數據存儲在數據框中,則首先需要將其轉換為EBImage使用的基於數組的格式。
library("EBImage")
# sample data.frame containg 3 copies of sampleData
df = rbind(sampleData, sampleData, sampleData)
# convert rows to 28x28 matrices and combine them into a single image stack
img = combine(lapply(1:nrow(df), function(i) Image(df[i, -1], c(28, 28))))
# set frame names to digit values
dimnames(img) = list(NULL, NULL, df[, 1])
# img contains an array of 3 grayscale 28x28 frames
img
## Image
## colorMode : Grayscale
## storage.mode : double
## dim : 28 28 3
## frames.total : 3
## frames.render: 3
##
## imageData(object)[1:5,1:6,1]
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 0 0 0 0 0 0
## [2,] 0 0 0 0 0 0
## [3,] 0 0 0 0 0 0
## [4,] 0 0 0 0 0 0
## [5,] 0 0 0 0 0 0
# display the first frame
display(getFrame(img, 1)/255, method = "raster", interpolate = FALSE)
注意縮放比例為1/255,這是因為EBImage使用[0:1]范圍內的強度來繪制圖像數據。
一旦您的數據采用正確的格式,就可以使用函數rotate
一次旋轉所有幀。
# rotate of the whole image stack
imgr = rotate(img, angle = 5, output.dim = dim(img)[1:2])
display(getFrame(imgr, 1)/255, method = "raster", interpolate = FALSE)
要保留原始圖像尺寸,我們需要設置output.dim
,否則將擴展生成的圖像,以使旋轉后的圖像適合其中。
可以在函數getFrames
的幫助下將像素數組展平為data.frame
,該函數返回圖像幀列表。 這些可以擴展為向量,並逐行組合。 原始數字名稱將作為結果數據幀的第一列。
dfr = as.data.frame(cbind(as.numeric(dimnames(imgr)[[3]]),
do.call(rbind, lapply(getFrames(imgr), as.vector))))
使用通用仿射變換執行旋轉,該仿射變換在C中有效實現。它使用雙線性濾波,可以通過將filter = "none"
設置為關閉。 根據我的微基准測量, rotate
功能比快18倍左右rotateImageM
功能(和200X速度相比原來的rotateImage
功能)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.