{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "using Images\n", "using ImageFiltering\n", "using PyPlot\n", "using ProgressMeter" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Use a weighted sum of rgb giving more weight to colors we \n", "# perceive as 'brighter'\n", "brightness(c::AbstractRGB) = 0.3 * c.r + 0.59 * c.g + 0.11 * c.b" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Use imfilter function from ImageFiltering.jl package\n", "# to calculate the convolution of an image and a kernel\n", "convolve!(imgconv, img, kernel) = imfilter!(imgconv, img, kernel)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "function edgeness!(e, img, gx, gy)\n", " b = e # alias for readability\n", " Sy, Sx = Kernel.sobel()\n", " b .= brightness.(img)\n", "\n", " # Gradients of brightness\n", " convolve!(gx, b, Sx)\n", " convolve!(gy, b, Sy)\n", " \n", " # the magnitude of the gradient as edgeness\n", " e .= sqrt.(gx.^2 + gy.^2)\n", " return nothing\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "function least_edgy(E)\n", " least_E = zeros(size(E))\n", " dirs = zeros(Int, size(E))\n", " least_E[end, :] .= E[end, :] # the minimum energy on the last row is the energy itself\n", "\n", " m, n = size(E)\n", " # Go from the last row up, finding the minimum energy\n", " for i = m-1:-1:1\n", " for j = 1:n\n", " j1, j2 = max(1, j-1), min(j+1, n)\n", " e, dir = findmin(least_E[i+1, j1:j2])\n", " least_E[i, j] += e\n", " least_E[i, j] += E[i, j]\n", " dirs[i, j] = (-1, 0, 1)[dir + (j==1)]\n", " end\n", " end\n", " least_E, dirs\n", "end\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "function get_seam!(seam, dirs, j)\n", " seam[1] = j\n", " for i = 2:length(seam)\n", " seam[i] = seam[i-1] + dirs[i-1, seam[i-1]]\n", " end\n", " return nothing\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "function mark_path(img, path)\n", " img2 = copy(img)\n", " m = size(img, 2)\n", " for i = 1:length(path)\n", " j = path[i]\n", " # To make it easier to see, we'll color not just\n", " # the pixels of the seam, but also those adjacent to it\n", " for k in j-1:j+1\n", " img2[i, clamp(k, 1, m)] = RGB(1,0,1)\n", " end\n", " end\n", " return img2\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "function rm_path(img, path)\n", " img2 = img[:, 1:end-1] # copy data, one less column\n", " for i = 1:length(path)\n", " j = path[i]\n", " img2[i, j:end] .= img[i, j+1:end]\n", " end\n", " return img2\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "function shrink_n(img, n, show_progress)\n", " marked_imgs = []\n", " \n", " m, k = size(img)\n", " seam = zeros(Int, m);\n", " e = Matrix{Float64}(undef, m, k);\n", " gx = similar(e);\n", " gy = similar(e);\n", " edgeness!(e, img, gx, gy)\n", " p = Progress(n; desc=\" Carving ... \", enabled=show_progress)\n", "\n", " for i = 1:n\n", " least_E, dirs = least_edgy(@view e[:, 1:k-i])\n", " min_j = argmin(@view least_E[1, :])\n", " get_seam!(seam, dirs, min_j)\n", " img = rm_path(img, seam)\n", "\n", " push!(marked_imgs, mark_path(img, seam))\n", "\n", " # Recompute the energy for the new image\n", " #\n", " # Note, this currently involves rerunning the convolution\n", " # on the whole image, but in principle the only values that\n", " # need recomputation are those adjacent to the seam, so there\n", " # is room for a meanintful speedup here.\n", " \n", " @views edgeness!(e[:, 1:k-i], img, gx[:, 1:k-i], gy[:, 1:k-i]) # working with a smaller image\n", " # e = rm_path(e, seam) # removal without re-evaluation\n", " \n", " ProgressMeter.next!(p)\n", " end\n", " return img, marked_imgs\n", "end\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", " hbox(imga, imgb, gap=16; sb=size(imgb), sa=size(imga))\n", "\n", "Join two images, imga and imgb, side by side horizontally, \n", "separated by a gap of width gap\n", "\"\"\"\n", "function hbox(imga, imgb, gap=16; sb=size(imgb), sa=size(imga))\n", " w = max(sa[1], sb[1])\n", " h = gap + sa[2] + sb[2]\n", " slate = fill(RGB(1,1,1), w, h)\n", " slate[1:size(imga, 1), 1:size(imga, 2)] .= RGB.(imga)\n", " slate[1:sb[1], sa[2] + gap .+ (1:size(imgb,2))] .= RGB.(imgb)\n", " return slate\n", "end" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image_urls = [\n", " \"https://wisetoast.com/wp-content/uploads/2015/10/The-Persistence-of-Memory-salvador-deli-painting.jpg\", # nseams=150 \n", " \"https://i.ytimg.com/vi/b06FwTP9TOU/maxresdefault.jpg\", # Easter island fellows, use img[1:720, 60:1220]\n", " \"https://www.libraryvisit.org/wp-content/uploads/2020/06/hot-air-balloons.jpeg\",\n", " \"https://www.hipcrave.com/wp-content/uploads/2019/05/kandinsky-1.png\" # Four figures, nseams = 150\n", "];" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image_url = image_urls[1]\n", "img = load(download(image_url));\n", "display(img)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m, k = size(img)\n", "e = Matrix{Float64}(undef, m, k);\n", "gx = similar(e);\n", "gy = similar(e);\n", "edgeness!(e, img, gx, gy);\n", "imshow(e);" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "least_e, dirs = least_edgy(e);\n", "imshow(least_e);" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "imshow(dirs);" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "nseams = 150\n", "show_progress = true\n", "carved, marked_carved = shrink_n(img, nseams, show_progress);" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "display(hbox(img, marked_carved[1]));\n", "sa1 = size(img)\n", "sb1 = size(marked_carved[1])\n", "for n = 2:length(marked_carved)\n", " display(hbox(img, marked_carved[n], 16; sb=sb1, sa=sa1))\n", " sleep(.3)\n", " IJulia.clear_output(true) \n", "end\n", "display(hbox(img, carved, 16; sb=sb1, sa=sa1));" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "@webio": { "lastCommId": null, "lastKernelId": null }, "kernelspec": { "display_name": "Julia 1.6.2", "language": "julia", "name": "julia-1.6" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.6.2" } }, "nbformat": 4, "nbformat_minor": 4 }