Я реализовал интерполяцию bicubi c в стиле OpenCV в Python с нумбой для ускорения. Это прекрасно работает, но все же в 5-10 раз медленнее, чем соответствующий вызов OpenCV. Мои вопросы:
- Почему нумба медленнее? Использует ли OpenCV разные методы? Или это так же хорошо, как и с numba?
- Могу ли я предпринять какие-либо другие шаги, чтобы ускорить процесс?
Я повторно реализовал, потому что хочу для некоторых случаев добавьте нестандартную интерполяцию.
Я уже преобразовал код в двухпроходный алгоритм, который экономит около 25% времени. При передаче входного изображения как типа double все выглядит быстрее, вероятно, потому что требуется меньше преобразований типов. Я пытался играть с типами данных для разных переменных. Я использую полные назначения, такие как first_pass[row, col, 0] =
вместо first_pass[row, col, :] =
, что, кажется, дает небольшое преимущество.
Время на моей машине:
scale complete: 0.6035717999911867
opencv scale complete: 0.0807620002888143
Вот код:
from numba import uint8, double, jit
import cv2
import numpy as np
import timeit
@jit(
uint8[:,:,:](double[:,:,:], uint8),
nopython=True
)
def scale_image_cubic_opencv_style_so(image, up_scale_factor):
# Initialisations and helping numba with type inference
scaled_shape = (image.shape[0]*up_scale_factor, image.shape[1]*up_scale_factor, image.shape[2])
scaled_image = np.empty(scaled_shape, dtype=np.uint8)
# need more than uint8 to prevent overflows
first_pass = np.empty(scaled_shape, dtype=np.int32)
bicubic_interpolation = np.asarray([0,0,0],dtype=np.float64)
row_scale = float(image.shape[0]) / float(scaled_image.shape[0])
col_scale = float(image.shape[1]) / float(scaled_image.shape[1])
# First pass
for col in range(scaled_image.shape[1] - up_scale_factor-1):
xf = max((col+0.5) * col_scale - 0.5, 0.0)
x1 = int(xf)
x0 = max(x1-1, 0)
x2 = min(x1+1, image.shape[1]-1)
x3 = min(x1+2, image.shape[1]-1)
# according to opencv, see method interpolateCubic in
# https://github.com/opencv/opencv/blob/master/modules/imgproc/src/resize.cpp
A = -0.75
x = xf-x1
coeffs_x0 = ((A*(x + 1) - 5*A)*(x + 1) + 8*A)*(x + 1) - 4*A
coeffs_x1 = ((A + 2)*x - (A + 3))*x*x + 1
coeffs_x2 = ((A + 2)*(1 - x) - (A + 3))*(1 - x)*(1 - x) + 1
coeffs_x3 = 1.0 - coeffs_x0 - coeffs_x1 - coeffs_x2
for row in range(scaled_image.shape[0] - up_scale_factor-1):
yf = max((row+0.5) * row_scale - 0.5, 0.0)
y1 = int(yf)
# Calculate cubic interpolation for the y1 row
# The calculations and even more likely the assignement
# below take a lot of the time in this mehtod
first_pass[row, col, 0] = coeffs_x0*image[y1, x0, 0] + coeffs_x1*image[y1, x1, 0] + coeffs_x2*image[y1, x2, 0] + coeffs_x3*image[y1, x3, 0]
first_pass[row, col, 1] = coeffs_x0*image[y1, x0, 1] + coeffs_x1*image[y1, x1, 1] + coeffs_x2*image[y1, x2, 1] + coeffs_x3*image[y1, x3, 1]
first_pass[row, col, 2] = coeffs_x0*image[y1, x0, 2] + coeffs_x1*image[y1, x1, 2] + coeffs_x2*image[y1, x2, 2] + coeffs_x3*image[y1, x3, 2]
# Second pass
for row in range(scaled_image.shape[0] - up_scale_factor-1):
yf = max((row+0.5) * row_scale - 0.5, 0.0)
y1 = int(yf)
y0 = max(y1-1, 0)
y2 = min(y1+1, image.shape[0]-1)
y3 = min(y1+2, image.shape[0]-1)
# according to opencv, see method interpolateCubic in
# https://github.com/opencv/opencv/blob/master/modules/imgproc/src/resize.cpp
A = -0.75
y = yf-y1
coeffs_y0 = ((A*(y + 1) - 5*A)*(y + 1) + 8*A)*(y + 1) - 4*A
coeffs_y1 = ((A + 2)*y - (A + 3))*y*y + 1
coeffs_y2 = ((A + 2)*(1 - y) - (A + 3))*(1 - y)*(1 - y) + 1
coeffs_y3 = 1.0 - coeffs_y0 - coeffs_y1 - coeffs_y2
for col in range(scaled_image.shape[1] - up_scale_factor-1):
# Calculate bicubic interpolation
# The calculations and even more likely the assignement
# below take a lot of the time in this mehtod
# rint = round to nearest integer, needed so we get the same values as opencv
bicubic_interpolation[0] = np.rint(coeffs_y0*first_pass[y0*up_scale_factor, col, 0] + coeffs_y1*first_pass[y1*up_scale_factor, col, 0] + coeffs_y2*first_pass[y2*up_scale_factor, col, 0] + coeffs_y3*first_pass[y3*up_scale_factor, col, 0])
bicubic_interpolation[1] = np.rint(coeffs_y0*first_pass[y0*up_scale_factor, col, 1] + coeffs_y1*first_pass[y1*up_scale_factor, col, 1] + coeffs_y2*first_pass[y2*up_scale_factor, col, 1] + coeffs_y3*first_pass[y3*up_scale_factor, col, 1])
bicubic_interpolation[2] = np.rint(coeffs_y0*first_pass[y0*up_scale_factor, col, 2] + coeffs_y1*first_pass[y1*up_scale_factor, col, 2] + coeffs_y2*first_pass[y2*up_scale_factor, col, 2] + coeffs_y3*first_pass[y3*up_scale_factor, col, 2])
# bicubic values can be negative, bring them back into the allowed range
# np.clip not supported by numba so using min/max which adds 0.1-0.15s
scaled_image[row, col, 0] = max(min(bicubic_interpolation[0],255),0)
scaled_image[row, col, 1] = max(min(bicubic_interpolation[1],255),0)
scaled_image[row, col, 2] = max(min(bicubic_interpolation[2],255),0)
return scaled_image
up_scale_factor = 16
image = np.random.rand(384,256,3) * 255
start = timeit.default_timer()
scaled_image = scale_image_cubic_opencv_style_so(
image.astype(np.float64),
up_scale_factor
)
print('scale complete:', timeit.default_timer()-start)
# cv2.imwrite('./scale.png', scaled_image)
# pure opencv for comparision timings
start = timeit.default_timer()
scaled_image = cv2.resize(
image,
(image.shape[1]*up_scale_factor, image.shape[0]*up_scale_factor),
interpolation=cv2.INTER_CUBIC
)
print('opencv scale complete:', timeit.default_timer()-start)
# cv2.imwrite('./scale-opencv.png', scaled_image)