2016-10-16 8 views
6

TL; DR: Каков наиболее эффективный способ обрезать прямоугольное изображение на круг?Самый эффективный способ обрезать изображение до круга (в R)?

Объяснение/Общие сведения:

Я работаю на некоторый код в R, который будет отображать Spotify художника образы как круги вместо rectanges по умолчанию/квадратов. Я не мог найти никаких пакетов или команд, которые обрезают изображения в R, особенно в круге, поэтому я написал свою собственную функцию, circ, которая читает 3-мерные (или 4-мерные) RGB (A) массивы и выдает их на circle, используя the parametric equation of a circle, чтобы определить значения x для каждого уникального y. Вот мой psuedocode:

Given an RGB(A) array: 
    Find the center of the image, radius = min(x coord, y coord) 
    Pre-crop the image to a square of dimensions 2r x 2r 
    For every unique y value: 
     Determine the x coordinates on the circle 
     Make pixels outside of the circle transparent 
    Return the cropped image as an RGBA array 

Эта функция является огромным шагом вперед по сравнению моей предыдущей, который проверял положение каждого пикселя, чтобы увидеть, если он был внутри или снаружи круга, но я все еще чувствую, что это может быть ускорено далее.

Есть ли способ проверить, может быть, половину значений y, а не всех, и зеркало по кругу? Есть ли фактическая функция обрезки, которую я мог бы использовать вместо этого? Любая помощь очень ценится!

Edited добавить код копипаст перспективе (благодаря @lukeA):

Мой оригинальный метод обрезки:

circ = function(a){ 
    # First part of the function finds the radius of the circle and crops the image accordingly 
    xc = floor(dim(a[,,1])[2]/2) # X coordinate of the center 
    yc = floor(dim(a[,,1])[1]/2) # Y coordinate of the center 
    r = min(xc, yc) - 1 # Radius is the smaller of the two -1 to avoid reading nonexistent data 
    ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)], # Read in the cropped image 
         a[,,2][(yc-r):(yc+r),(xc-r):(xc+r)], # Of dimensions 2r x 2r, centered 
         a[,,3][(yc-r):(yc+r),(xc-r):(xc+r)], # Around (xc, yc) 
         rep(1,length(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)]))), # Add fourth alpha layer 
      dim = c(length((yc-r):(yc+r)),length((xc-r):(xc+r)),4)) 

    if(yc > xc) yc = xc else if(xc > yc) xc = yc # Re-evaluate your center for the cropped image 
    xmax = dim(ma[,,1])[2]; ymax = dim(ma[,,1])[1] # Find maximum x and y values 

    # Second part of the function traces circle by the parametric eqn. and makes outside pixels transparent 
    for(y in 1:ymax){ # For every y in the cropped image 
    theta = asin((y - yc)/r) # y = yc + r * sin(theta) by parametric equation for a circle 
    x = xc + r * cos(theta) # Then we can find the exact x coordinate using the same formula 
    x = which.min(abs(1:xmax - x)) # Find which x in array is closest to exact coordinate 
    if(!x - xc == 0 && !xmax - x == 0){ # If you're not at the "corners" of the circle 
     ma[,,4][y,c(1:(xmax-x), (x+1):xmax)] = 0 # Make pixels on either side of the circle trans. 
    } else if(!xmax - x == 0) ma[,,4][y,] = 0 # This line makes tops/bottoms transparent 
    } 
    return(ma) 
} 

library(jpeg) 
a = readJPEG("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg") 
par(bg = "grey"); plot(1:2, type="n") # Color background to check transparency 
rasterImage(circ(a),1,1,2,2) 

Модифицированная версия (спасибо @dww):

dwwcirc = function(a){ 
    # First part of the function finds the radius of the circle and crops the image accordingly 
    xc = floor(dim(a[,,1])[2]/2) # X coordinate of the center 
    yc = floor(dim(a[,,1])[1]/2) # Y coordinate of the center 
    r = min(xc, yc) - 1 # Radius is the smaller of the two -1 to avoid reading nonexistent data 
    ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)], # Read in the cropped image 
         a[,,2][(yc-r):(yc+r),(xc-r):(xc+r)], # Of dimensions 2r x 2r, centered 
         a[,,3][(yc-r):(yc+r),(xc-r):(xc+r)], # Around (xc, yc) 
         rep(1,length(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)]))), # Add fourth alpha layer 
      dim = c(length((yc-r):(yc+r)),length((xc-r):(xc+r)),4)) 

    if(yc > xc) yc = xc else if(xc > yc) xc = yc # Re-evaluate your center for the cropped image 
    xmax = dim(ma[,,1])[2]; ymax = dim(ma[,,1])[1] # Find maximum x and y values 

    x = rep(1:xmax, ymax) # Vector containing all x values 
    y = rep(1:ymax, each=xmax) # Value containing all y values 
    r2 = r^2 
    ma[,,4][which(((x-xc)^2 + (y-yc)^2) > r2)] = 0 
    return(ma) 
} 

library(jpeg) 
a = readJPEG("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg") 
par(bg = "grey"); plot(1:2, type="n") # Color background to check transparency 
rasterImage(dwwcirc(a),1,1,2,2) 

Версия с использованием magick и plotrix (спасибо @lukeA и @hrbrmstr):

library(plotrix) 
jpeg(tf <- tempfile(fileext = "jpeg"), 1000, 1000) 
par(mar = rep(0,4), yaxs="i", xaxs = "i") 
plot(0, type = "n", ylim = c(0, 1), xlim = c(0,1), axes=F, xlab=NA, ylab=NA) 
draw.circle(.5,.5,.5,col="black") 
dev.off() 

library(magick) 
img = image_read("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg") 
mask = image_read(tf) 
radius = min(c(image_info(img)$width, image_info(img)$height)) 
mask = image_scale(mask, as.character(radius)) 

par(bg = "grey"); plot(1:2, type="n") 
rasterImage(as.raster(image_composite(image = mask, composite_image = img, operator = "plus")),1,1,2,2) 
+0

Вы можете обрезать с помощью Imager :: IMSUB, но он принимает только прямоугольные аргументы. Вам придется придерживаться того, что вы делаете, но он вырезает один шаг вашей эвристики. – shayaa

+0

Не следует ли использовать реальный инструмент обработки изображений, а не пытаться взломать R, как это? –

+0

[i only read tl; dr] есть примеры с графикой сетки, чтобы сделать растровую маску – baptiste

ответ

2

Вы можете улучшить производительность вашего circ функции, если вы делаете vectorised подмножество-присвойте операции в массиве (вместо петли), используя тот факт, что (x-xc)^2 +(y-yc)^2 > r^2 для точек вне круга.

Чтобы сделать это, заменить 2-ую часть вашей функции с

# Second part of the function traces circle by... 
    x = rep(1:xmax, ymax) 
    y = rep(1:ymax, each=xmax) 
    r2 = r^2 
    ma[,,4][which(((x-xc)^2 + (y-yc)^2) > r2)] <- 0 
    return(ma) 
+1

Ничего себе! Я попробовал это и проверил время выполнения на [подходящем jpeg] (http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing -HD-изображение-в-faacebook-sharing.jpg). Это занимает менее одной десятой времени (до 1.232 секунды с 16.887, согласно 'proc.time()'), что является безумным. Я буду реализовывать это немедленно, спасибо! –

+0

Отлично, рад, что это помогло. Только один хед-ап. определения x & y * могут * нуждаться в обмене, в зависимости, если ваш массив определен в порядке столбцов или строк. Если они неправильны, это легко исправить - (вы узнаете, нужно ли это настраивать, если окружность смещается от центра для неквадратных изображений). Дайте мне знать, если это произойдет, и я исправлю ответ соответственно. – dww

+0

Я не заметил ничего более нецентрального, чем было изначально (спасибо -1 при определении радиуса), но кажется, что вы вызываете определенные ячейки в массиве a, размерность d 'a [,, d] [y, x]', поэтому я думаю, что вы золотые. –

4

Я не знаю об «эффективности», но я бы не стал изобретать колесо здесь. Как предложено в комментариях @hrbrmstr, вы можете хотите попробовать magick, которая дает вам всю гибкость, что вам может понадобиться:

png(tf <- tempfile(fileext = ".png"), 1000, 1000) 
par(mar = rep(0,4), yaxs="i", xaxs="i") 
plot(0, type = "n", ylim = c(0,1), xlim=c(0,1), axes=F, xlab=NA, ylab=NA) 
plotrix::draw.circle(.5,0.5,.5, col="black") 
dev.off() 

library(magick) 
fn <- "https://www.gravatar.com/avatar/f57aba01c52e5c67696817eb87df84f2?s=328&d=identicon&r=PG&f=1" 
img <- image_read(fn) 
mask <- image_read(tf) 
mask <- image_scale(mask, as.character(image_info(img)$width)) 

Теперь

img 

enter image description here

mask 

enter image description here

image_composite(mask, img, "plus") 

enter image description here

image_composite(mask, img, "minus") 

enter image description here

Некоторые другие composite operators:

# https://www.imagemagick.org/Magick++/Enumerations.html#CompositeOperator 
ops <- c("over", "in", "out", "atop", "xor", "plus", "minus", "add", "difference", "multiply") 
for (op in ops) { 
    print(image_composite(img, mask, op)) 
    print(op) 
    readline() 
} 
+0

Сейчас я попробую. У меня есть пара вопросов для вас, пока я пытаюсь выяснить для себя. –

+0

К сожалению, я новичок в комментировании - я не понимал, что попадание в нее будет опубликовано. Первое заключается в том, что spotify держит их изображения художника как jpegs, а не PNG, но это должно работать примерно так же, я думаю. Другой вопрос: «минус» удаляет (или делает невидимым) область, которую вы маскируете? Или он сохраняет его, как и наоборот? Спасибо –

+0

# 1 работает так же, да. # 2 «минус» - это «составное изображение - изображение с переполнением, обрезанным до нуля». Матовая клавиша игнорируется (устанавливается на 255, полное покрытие). »_. Таким образом, черная маска означает, что она полностью видима. (Серый темнеет, белый - черный.) – lukeA