import numpy as np cimport numpy as cnp cdef cnp.double_t DBL_MAX = np.finfo(np.double).max cdef _preprocess_image(cnp.double_t[:, :, ::1] energy_img, cnp.double_t[:, ::1] cumulative_img, cnp.int8_t[:, ::1] track_img, Py_ssize_t cols): """ For each row, compute the lowest seam value for all its columns. This function updates `cumulative_img` such that `cumulative_img[r, c]` is the total energy of the lowest energy seam ending at `(r, c)`. Parameters ---------- energy_img : (M, N, 1) ndarray The array of cost of removal of each pixel. Seam carving tries to avoid pixels with high costs. cumulative_img : (M, N) ndarray The array to be updated with the total cost of lowest energy seams. track_img : (M, N) ndarray For each pixel, `track_img` stores the relative column offset in the previous row which has the lowest value in `cumulative_img`. This helps in in re-tracing the minimum cost seam. cols : int Number of columns to process. """ cdef Py_ssize_t r, c, offset, c_idx cdef Py_ssize_t rows = energy_img.shape[0] cdef cnp.double_t min_cost = DBL_MAX for c in range(cols): cumulative_img[0, c] = energy_img[0, c, 0] for r in range(1, rows): for c in range(cols): min_cost = DBL_MAX for offset in range(-1, 2): c_idx = c + offset if (c_idx > cols - 1) or (c_idx < 0) : continue if cumulative_img[r-1, c_idx] < min_cost: min_cost = cumulative_img[r-1, c_idx] track_img[r, c] = offset cumulative_img[r,c] = min_cost + energy_img[r, c, 0] cdef cnp.uint8_t _mark_seam(cnp.int8_t[:, ::1] track_img, Py_ssize_t start_index, cnp.uint8_t[:, ::1] seam_map): """ Re-trace the optimal seam from a given column in the last row. This function tries to re-track an optimal seam from `start_index` and tries to mark it in `seam_map`. If this seam intersects with any existing seam in `seam_map` the function returns `0` without marking anything. Else it marks the seam in `seam_map` and returns `1`. track_img : (M, N) ndarray The array of relative column indices as updated by `_preprocess_image`. start_index : int The column number of the bottom most row from where to start re-tracing the seam. seam_map : (M, N) ndarray The array used to mark seams. If a pixel is marked as as seam it is set to `1`, else `0`. Returns ------- out : int `1` if seam was marked, `0` is seam inersects and was not marked. """ cdef Py_ssize_t rows = track_img.shape[0] cdef Py_ssize_t[::1] current_seam_indices = np.zeros(rows, dtype=np.int) cdef Py_ssize_t row, col cdef cnp.int8_t offset cdef Py_ssize_t seams current_seam_indices[rows - 1] = start_index for row in range(rows - 2, -1, -1): col = current_seam_indices[row+1] offset = track_img[row, col] col = col + offset current_seam_indices[row] = col if seam_map[row, col]: return 0 for row in range(rows): col = current_seam_indices[row] seam_map[row, col] = 1 return 1 cdef _remove_seam(cnp.double_t[:, :, ::1] img, cnp.uint8_t[:, ::1] seam_map, Py_ssize_t cols): """ Removes marked seams from an image. Parameters ---------- img : (M, N, P) ndarray Input image whose vertical seams are to be removed. seam_map : (M, N) ndarray Array with seams to be removed marked by non-zero entries. cols : int The number of colums to process. """ cdef Py_ssize_t rows = img.shape[0] cdef Py_ssize_t channels = img.shape[2] cdef Py_ssize_t r, c, ch, shift for r in range(rows): shift = 0 for c in range(cols): shift += seam_map[r, c] for ch in range(channels): img[r, c, ch] = img[r, c + shift, ch] def _seam_carve_v(img, energy_map, iters, border): """ Carve vertical seams off an image. Carves out vertical seams off an image while using the given energy map to decide the importance of each pixel.[1] Parameters ---------- img : (M, N) or (M, N, 3) ndarray Input image whose vertical seams are to be removed. iters : int Number of vertical seams are to be removed. energy_map : (M, N) ndarray The array to decide the importance of each pixel. The higher the value corresponding to a pixel, the more the algorithm will try to keep it in the image. num : int Number of seams are to be removed. border : int, optional The number of pixels in the right, left and bottom end of the image to be excluded from being considered for a seam. This is important as certain filters just ignore image boundaries and set them to `0`. By default border is set to `1`. Returns ------- image : (M, N - iters, 3) ndarray of float The cropped image with the vertical seams removed. References ---------- .. [1] Shai Avidan and Ariel Shamir "Seam Carving for Content-Aware Image Resizing" http://www.cs.jhu.edu/~misha/ReadingSeminar/Papers/Avidan07.pdf """ last_row_obj = np.zeros(img.shape[1], dtype=np.float) cdef cnp.double_t[::1] last_row = last_row_obj cdef Py_ssize_t[::1] sorted_indices cdef cnp.uint8_t[:, ::1] seam_map = np.zeros(img.shape[0:2], dtype=np.uint8) cdef Py_ssize_t cols = img.shape[1] cdef Py_ssize_t rows = img.shape[0] cdef Py_ssize_t seams_left = iters cdef Py_ssize_t seams_removed cdef Py_ssize_t seam_idx cdef cnp.double_t[:, :, ::1] image = img cdef cnp.int8_t[:, ::1] track_img = np.zeros(img.shape[0:2], dtype=np.int8) cdef cnp.double_t[:, ::1] cumulative_img = np.zeros(img.shape[0:2], dtype=np.float) cdef cnp.double_t[:, :, ::1] energy_img energy_map[:, 0:border] = DBL_MAX energy_map[:, cols-border:cols] = DBL_MAX energy_map[rows-border:rows, :] = energy_map[rows-2*border:rows-border, :] energy_map = np.ascontiguousarray(energy_map[:, :, np.newaxis]) energy_img = energy_map _preprocess_image(energy_img, cumulative_img, track_img, cols) last_row[...] = cumulative_img[-1, :] sorted_indices = np.argsort(last_row_obj) seam_idx = 0 while seams_left > 0: if _mark_seam(track_img, sorted_indices[seam_idx], seam_map): seams_left -= 1 cols -= 1 seam_idx += 1 continue else: seam_idx = 0 _remove_seam(image, seam_map, cols) _remove_seam(energy_img, seam_map, cols) seam_map[...] = 0 _preprocess_image(energy_img, cumulative_img, track_img, cols) last_row[:cols] = cumulative_img[-1, :cols] sorted_indices = np.argsort(last_row_obj) return img[:, 0:cols]