{"id":1246,"date":"2019-05-02T14:13:37","date_gmt":"2019-05-02T12:13:37","guid":{"rendered":"https:\/\/esimov.com\/blog\/?p=1246"},"modified":"2019-07-26T09:54:57","modified_gmt":"2019-07-26T07:54:57","slug":"caire-a-content-aware-image-resizing-library","status":"publish","type":"post","link":"https:\/\/esimov.com\/blog\/2019\/05\/02\/caire-a-content-aware-image-resizing-library\/","title":{"rendered":"Caire &#8211; a content aware image resize library"},"content":{"rendered":"<p>Let&#8217;s assume you want to resize an image without content distortion but also you wish to preserve the relevant image parts. The normal image resize, but also the content cropping technique is not really suitable for this kind of task, since the first one will simply resize the image by preserving the aspect ratio and the last one will crop the image on the defined coordinate section, which might results in content loss, especially on photos with the relevant information scattered trough the image. Not even the smart cropping technique will help too much in this case.<\/p>\n<p>This is what <a href=\"https:\/\/github.com\/esimov\/caire\">Caire<\/a>, my content aware image resizing library developed in Go is trying to remedy. I&#8217;is pretty much based on the article <em><a href=\"https:\/\/inst.eecs.berkeley.edu\/~cs194-26\/fa16\/hw\/proj4-seamcarving\/imret.pdf\">Seam Carving for Content-Aware Image Resizing<\/a><\/em> by Shai Avidan and Ariel Shamir.<\/p>\n<h2>The background<\/h2>\n<p>Let\u2019s consider the image below (Fig.1). It\u2019s a nice and clean picture with a wide open background. Now suppose that we want to make it smaller. We have two options: either to crop it, or to scale it. Cropping is limited since it can only remove pixels from the image periphery. Also advanced cropping features like smart cropping cannot resolve our issue, since it will remove the person from the left margin or will crop a small part from the castle. Certainly we do not want this to happen. Scaling also is not sufficient since it is not aware of the image content and typically can be applied only uniformly.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Broadway_tower_edit-1024x694.jpg\" alt=\"\" width=\"650\" height=\"441\" class=\"aligncenter size-large wp-image-1248\" srcset=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Broadway_tower_edit-1024x694.jpg 1024w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Broadway_tower_edit-300x203.jpg 300w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Broadway_tower_edit-768x521.jpg 768w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Broadway_tower_edit-650x441.jpg 650w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Broadway_tower_edit.jpg 1280w\" sizes=\"auto, (max-width: 650px) 100vw, 650px\" \/><\/p>\n<p style=\"text-align: center;\">Fig.1: Sample image<\/p>\n<p>Seam carving was developed typically for this kind of use cases. It works by establishing a number of seams (a connected path of low energy pixels) crossing the image from top to down or from left to right defining the importance of pixels. By successively removing or inserting seams we can reduce or enlarge the size of the image in both directions. Fig.2. Illustrates the process.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/carving-steps.png\" alt=\"\" width=\"899\" height=\"253\" class=\"aligncenter size-full wp-image-1251\" srcset=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/carving-steps.png 899w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/carving-steps-300x84.png 300w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/carving-steps-768x216.png 768w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/carving-steps-650x183.png 650w\" sizes=\"auto, (max-width: 899px) 100vw, 899px\" \/><\/p>\n<p style=\"text-align: center;\">Fig.2: The seam carving method illustrated<\/p>\n<p>First let\u2019s skim through the details and summarize the important steps.<\/p>\n<ul>\n<li>An energy map (edge detection) is generated from the provided image.<\/li>\n<li>The algorithm tries to find the least important parts of the image taking into account the lowest energy values.<\/li>\n<li>Using a dynamic programming approach the algorithm will generate individual seams crossing the image from top to down, or from left to right (depending on the horizontal or vertical resizing) and will allocate for each seam a custom value, the least important pixels having the lowest energy cost and the most important ones having the highest cost.<\/li>\n<li>Traverse the image from the second row to the last row and compute the cumulative minimum energy for all possible connected seams for each entry.<\/li>\n<li>The minimum energy level is calculated by summing up the current pixel value with the lowest value of the neighboring pixels from the previous row.<\/li>\n<li>Traverse the image from top to bottom (or from left to right in case of vertical resizing) and compute the minimum energy level. For each pixel in a row we compute the energy of the current pixel plus the energy of one of the three possible pixels above it.<\/li>\n<li>Find the lowest cost seam from the energy matrix starting from the last row and remove it.<\/li>\n<li>Repeat the process.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Implementation<\/h2>\n<p>Seam carving can support several types of energy functions such as gradient magnitude, entropy, sobel filter, each of them having something in common: it values a pixel by measuring its contrast with its neighboring pixels. We will use the Sobel filter operator in our case. Typically in image processing the Sobel operator is used to detect image edges. It\u2019s working with an energy distribution matrix to differentiate the sensitive image information from the less sensitive ones. (Fig. 3)<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/carving-sobel-threshold.png\" alt=\"\" width=\"576\" height=\"390\" class=\"aligncenter size-full wp-image-1253\" srcset=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/carving-sobel-threshold.png 576w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/carving-sobel-threshold-300x203.png 300w\" sizes=\"auto, (max-width: 576px) 100vw, 576px\" \/><\/p>\n<p style=\"text-align: center;\">Fig.3: Sobel threshold applied to the original image<\/p>\n<p>Once we obtained the energy distribution matrix we can advance to the next step to generate individual seams of one pixel wide. We\u2019ll use a dynamic programming approach to store the results of sub-calculations in order to simplify calculating the more complex ones. For this purpose we define some setter and getter methods whose role are to set and get the pixel energy value.<\/p>\n<p><iframe\n  src=\"https:\/\/carbon.now.sh\/embed?bg=rgba(255%2C255%2C255%2C1)&#038;t=seti&#038;wt=none&#038;l=text%2Fx-go&#038;ds=false&#038;dsyoff=20px&#038;dsblur=68px&#038;wc=true&#038;wa=true&#038;pv=56px&#038;ph=56px&#038;ln=false&#038;fm=Hack&#038;fs=14px&#038;lh=133%25&#038;si=false&#038;es=2x&#038;wm=false&#038;code=%252F%252F%2520Seam%2520struct%2520contains%2520the%2520seam%2520pixel%2520coordinates.%250Atype%2520Seam%2520struct%2520%257B%250A%2520%2520%2520%2520%2520%2520X%2520int%250A%2520%2520%2520%2520%2520%2520Y%2520int%250A%257D%250A%250A%252F%252F%2520Carver%2520is%2520the%2520main%2520entry%2520struct%2520having%2520as%2520parameters%2520the%2520newly%2520generated%2520image%2520width%252C%2520height%2520and%2520seam%2520points.%250Atype%2520Carver%2520struct%2520%257B%250A%2520%2520%2520%2520%2520%2520Width%2520%2520int%250A%2520%2520%2520%2520%2520%2520Height%2520int%250A%2520%2520%2520%2520%2520%2520Points%2520%255B%255Dfloat64%250A%257D%250A%250A%252F%252F%2520NewCarver%2520returns%2520an%2520initialized%2520Carver%2520structure.%250Afunc%2520NewCarver(width%252C%2520height%2520int)%2520*Carver%2520%257B%250A%2520%2520%2520%2520%2520%2520return%2520%2526Carver%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520width%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520height%252C%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520make(%255B%255Dfloat64%252C%2520width*height)%252C%250A%2520%2520%2520%2520%2520%2520%257D%250A%257D%250A%250A%252F%252F%2520Get%2520energy%2520pixel%2520value.%250Afunc%2520(c%2520*Carver)%2520get(x%252C%2520y%2520int)%2520float64%2520%257B%250A%2520%2520%2520%2520%2520%2520px%2520%253A%253D%2520x%2520%252B%2520y*c.Width%250A%2520%2520%2520%2520%2520%2520return%2520c.Points%255Bpx%255D%250A%257D%250A%250A%252F%252F%2520Set%2520energy%2520pixel%2520value.%250Afunc%2520(c%2520*Carver)%2520set(x%252C%2520y%2520int%252C%2520px%2520float64)%2520%257B%250A%2520%2520%2520%2520%2520%2520idx%2520%253A%253D%2520x%2520%252B%2520y*c.Width%250A%2520%2520%2520%2520%2520%2520c.Points%255Bidx%255D%2520%253D%2520px%250A%257D\"\n  style=\"width:100%; height:473px; border:0; overflow:hidden;\"\n  sandbox=\"allow-scripts allow-same-origin\"><br \/>\n<\/iframe><\/p>\n<p>After generating the energy map there are two important steps we need to follow: <\/p>\n<p>Traverse the image from the second row to the last one and compute the cumulative minimum energy <em><strong>M<\/strong><\/em> for all possible connected seams for each entry <em><strong>(i, j)<\/strong><\/em>.  <em><strong>M<\/strong><\/em> is the two dimensional array of cumulative energies we are building up. The minimum energy level is calculated by summing up the current pixel value with the minimum pixel value of the neighboring pixels obtained from the previous row. This can be done via <a href=\"https:\/\/en.wikipedia.org\/wiki\/Dijkstra%27s_algorithm\">Dijkstra&#8217;s algorithm<\/a>. Suppose that we have a matrix with the following values:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/original-matrix.png\" alt=\"\" width=\"486\" height=\"196\" class=\"aligncenter size-full wp-image-1257\" srcset=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/original-matrix.png 486w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/original-matrix-300x121.png 300w\" sizes=\"auto, (max-width: 486px) 100vw, 486px\" \/><\/p>\n<p style=\"text-align: center;\">Original matrix<\/p>\n<p>To compute the minimum cumulative energies of the second row we start with the columns from the first row and sum up with the minimum value of the neighboring cells from the second row. After the above operation is carried out for every pixel in the second row, we go to the third row and so on.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Dijkstra-matrix.png\" alt=\"\" width=\"490\" height=\"248\" class=\"aligncenter size-full wp-image-1256\" srcset=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Dijkstra-matrix.png 490w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/Dijkstra-matrix-300x152.png 300w\" sizes=\"auto, (max-width: 490px) 100vw, 490px\" \/><\/p>\n<p style=\"text-align: center;\">Using Dijkstra\u2019s algorithm to calculate the minimum energy values.<\/p>\n<p><iframe\n  src=\"https:\/\/carbon.now.sh\/embed?bg=rgba(255%2C255%2C255%2C1)&#038;t=seti&#038;wt=none&#038;l=text%2Fx-go&#038;ds=false&#038;dsyoff=20px&#038;dsblur=68px&#038;wc=true&#038;wa=true&#038;pv=56px&#038;ph=56px&#038;ln=false&#038;fm=Hack&#038;fs=14px&#038;lh=133%25&#038;si=false&#038;es=2x&#038;wm=false&#038;code=%252F%252F%2520ComputeSeams%2520compute%2520the%2520minimum%2520energy%2520level.%250Afunc%2520(c%2520*Carver)%2520ComputeSeams(img%2520*image.NRGBA%252C%2520p%2520*Processor)%2520%255B%255Dfloat64%2520%257B%250A%2520%2520%2520%2520%2520%2520var%2520src%2520*image.NRGBA%250A%2520%2520%2520%2520%2520%2520newImg%2520%253A%253D%2520image.NewNRGBA(image.Rect(0%252C%25200%252C%2520img.Bounds().Dx()%252C%2520img.Bounds().Dy()))%250A%2520%2520%2520%2520%2520%2520draw.Draw(newImg%252C%2520newImg.Bounds()%252C%2520img%252C%2520image.ZP%252C%2520draw.Src)%250A%2520%2520%2520%2520%2520%2520%250A%2520%2520%2520%2520%2520%2520src%2520%253A%253D%2520SobelFilter(Grayscale(newImg)%252C%2520float64(p.SobelThreshold))%2520%2520%2520%2520%2520%2520%250A%250A%2520%2520%2520%2520%2520%2520for%2520x%2520%253A%253D%25200%253B%2520x%2520%253C%2520c.Width%253B%2520x%252B%252B%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520for%2520y%2520%253A%253D%25200%253B%2520y%2520%253C%2520c.Height%253B%2520y%252B%252B%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520r%252C%2520_%252C%2520_%252C%2520a%2520%253A%253D%2520src.At(x%252C%2520y).RGBA()%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520c.set(x%252C%2520y%252C%2520float64(r)%252Ffloat64(a))%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%257D%250A%250A%2520%2520%2520%2520%2520%2520var%2520left%252C%2520middle%252C%2520right%2520float64%250A%250A%2520%2520%2520%2520%2520%2520%252F%252F%2520Traverse%2520the%2520image%2520from%2520top%2520to%2520bottom%2520and%2520compute%2520the%2520minimum%2520energy%2520level.%250A%2520%2520%2520%2520%2520%2520%252F%252F%2520For%2520each%2520pixel%2520in%2520a%2520row%2520we%2520compute%2520the%2520energy%2520of%2520the%2520current%2520pixel%250A%2520%2520%2520%2520%2520%2520%252F%252F%2520plus%2520the%2520energy%2520of%2520one%2520of%2520the%2520three%2520possible%2520pixels%2520above%2520it.%250A%2520%2520%2520%2520%2520%2520for%2520y%2520%253A%253D%25201%253B%2520y%2520%253C%2520c.Height%253B%2520y%252B%252B%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520for%2520x%2520%253A%253D%25201%253B%2520x%2520%253C%2520c.Width-1%253B%2520x%252B%252B%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520left%2520%253D%2520c.get(x-1%252C%2520y-1)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520middle%2520%253D%2520c.get(x%252C%2520y-1)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520right%2520%253D%2520c.get(x%252B1%252C%2520y-1)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520min%2520%253A%253D%2520math.Min(math.Min(left%252C%2520middle)%252C%2520right)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%252F%252F%2520Set%2520the%2520minimum%2520energy%2520level.%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520c.set(x%252C%2520y%252C%2520c.get(x%252C%2520y)%252Bmin)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%252F%252F%2520Special%2520cases%253A%2520pixels%2520are%2520far%2520left%2520or%2520far%2520right%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520left%2520%253A%253D%2520c.get(0%252C%2520y)%2520%252B%2520math.Min(c.get(0%252C%2520y-1)%252C%2520c.get(1%252C%2520y-1))%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520c.set(0%252C%2520y%252C%2520left)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520right%2520%253A%253D%2520c.get(0%252C%2520y)%2520%252B%2520math.Min(c.get(c.Width-1%252C%2520y-1)%252C%2520c.get(c.Width-2%252C%2520y-1))%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520c.set(c.Width-1%252C%2520y%252C%2520right)%250A%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520return%2520c.Points%250A%257D\"\n  style=\"width:100%; height:473px; border:0; overflow:hidden;\"\n  sandbox=\"allow-scripts allow-same-origin\"><br \/>\n<\/iframe><\/p>\n<p>Once we calculated the minimum energy values for each row, we select the last row as starting position and search for the pixel with the smallest cumulative energy value. Then we traverse up on the matrix table one row at once and again search for the minimum cumulative energy value up until the first row. The obtained values (pixels) make up the seam which should be removed.<\/p>\n<p><iframe\n  src=\"https:\/\/carbon.now.sh\/embed?bg=rgba(255%2C255%2C255%2C1)&#038;t=seti&#038;wt=none&#038;l=text%2Fx-go&#038;ds=false&#038;dsyoff=20px&#038;dsblur=68px&#038;wc=true&#038;wa=true&#038;pv=56px&#038;ph=56px&#038;ln=false&#038;fm=Hack&#038;fs=14px&#038;lh=133%25&#038;si=false&#038;es=2x&#038;wm=false&#038;code=%252F%252F%2520FindLowestEnergySeams%2520find%2520the%2520lowest%2520vertical%2520energy%2520seam.%250Afunc%2520(c%2520*Carver)%2520FindLowestEnergySeams()%2520%255B%255DSeam%2520%257B%250A%2520%2520%2520%2520%2520%2520%252F%252F%2520Find%2520the%2520lowest%2520cost%2520seam%2520from%2520the%2520energy%2520matrix%2520starting%2520from%2520the%2520last%2520row.%250A%2520%2520%2520%2520%2520%2520var%2520min%2520%253D%2520math.MaxFloat64%250A%2520%2520%2520%2520%2520%2520var%2520px%2520int%250A%2520%2520%2520%2520%2520%2520seams%2520%253A%253D%2520make(%255B%255DSeam%252C%25200)%250A%250A%2520%2520%2520%2520%2520%2520%252F%252F%2520Find%2520the%2520pixel%2520on%2520the%2520last%2520row%2520with%2520the%2520minimum%2520cumulative%2520energy%2520and%2520use%2520this%2520as%2520the%2520starting%2520pixel%250A%2520%2520%2520%2520%2520%2520for%2520x%2520%253A%253D%25200%253B%2520x%2520%253C%2520c.Width%253B%2520x%252B%252B%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520seam%2520%253A%253D%2520c.get(x%252C%2520c.Height-1)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520if%2520seam%2520%253C%2520min%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520min%2520%253D%2520seam%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520px%2520%253D%2520x%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%257D%250A%250A%2520%2520%2520%2520%2520%2520seams%2520%253D%2520append(seams%252C%2520Seam%257BX%253A%2520px%252C%2520Y%253A%2520c.Height%2520-%25201%257D)%250A%2520%2520%2520%2520%2520%2520var%2520left%252C%2520middle%252C%2520right%2520float64%250A%250A%2520%2520%2520%2520%2520%2520%252F%252F%2520Walk%2520up%2520in%2520the%2520matrix%2520table%252C%2520check%2520the%2520immediate%2520three%2520top%2520pixel%2520seam%2520level%2520and%250A%2520%2520%2520%2520%2520%2520%252F%252F%2520add%2520the%2520one%2520which%2520has%2520the%2520lowest%2520cumulative%2520energy.%250A%2520%2520%2520%2520%2520%2520for%2520y%2520%253A%253D%2520c.Height%2520-%25202%253B%2520y%2520%253E%253D%25200%253B%2520y--%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520middle%2520%253D%2520c.get(px%252C%2520y)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%252F%252F%2520Leftmost%2520seam%252C%2520no%2520child%2520to%2520the%2520left%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520if%2520px%2520%253D%253D%25200%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520right%2520%253D%2520c.get(px%252B1%252C%2520y)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520if%2520right%2520%253C%2520middle%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520px%252B%252B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%252F%252F%2520Rightmost%2520seam%252C%2520no%2520child%2520to%2520the%2520right%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%2520else%2520if%2520px%2520%253D%253D%2520c.Width-1%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520left%2520%253D%2520c.get(px-1%252C%2520y)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520if%2520left%2520%253C%2520middle%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520px--%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%2520else%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520left%2520%253D%2520c.get(px-1%252C%2520y)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520right%2520%253D%2520c.get(px%252B1%252C%2520y)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520min%2520%253A%253D%2520math.Min(math.Min(left%252C%2520middle)%252C%2520right)%250A%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520if%2520min%2520%253D%253D%2520left%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520px--%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%2520else%2520if%2520min%2520%253D%253D%2520right%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520px%252B%252B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520seams%2520%253D%2520append(seams%252C%2520Seam%257BX%253A%2520px%252C%2520Y%253A%2520y%257D)%250A%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520return%2520seams%250A%257D\"\n  style=\"width:100%; height:473px; border:0; overflow:hidden;\"\n  sandbox=\"allow-scripts allow-same-origin\"><br \/>\n<\/iframe><\/p>\n<p>To remove the seam is only a matter of obtaining the pixel coordinates of the seam values and  checking on each iteration if the processed pixel coordinates corresponds with the seams pixel values position. We can check the accuracy of our logic by drawing the removable seams on top of the image. (Fig.4)<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/red-seams.png\" alt=\"\" width=\"550\" height=\"390\" class=\"aligncenter size-full wp-image-1258\" srcset=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/red-seams.png 550w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/red-seams-260x213.png 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/p>\n<p style=\"text-align: center;\">Fig.4: Low energy seams crossing the image<\/p>\n<p><iframe\n  src=\"https:\/\/carbon.now.sh\/embed?bg=rgba(255%2C255%2C255%2C1)&#038;t=seti&#038;wt=none&#038;l=text%2Fx-go&#038;ds=false&#038;dsyoff=20px&#038;dsblur=68px&#038;wc=true&#038;wa=true&#038;pv=56px&#038;ph=56px&#038;ln=false&#038;fm=Hack&#038;fs=14px&#038;lh=133%25&#038;si=false&#038;es=2x&#038;wm=false&#038;code=%252F%252F%2520RemoveSeam%2520remove%2520the%2520least%2520important%2520columns%2520based%2520on%2520the%2520stored%2520energy%2520(seams)%2520level.%250Afunc%2520(c%2520*Carver)%2520RemoveSeam(img%2520*image.NRGBA%252C%2520seams%2520%255B%255DSeam%252C%2520debug%2520bool)%2520*image.NRGBA%2520%257B%250A%2520%2520%2520%2520%2520%2520bounds%2520%253A%253D%2520img.Bounds()%250A%2520%2520%2520%2520%2520%2520%252F%252F%2520Reduce%2520the%2520image%2520width%2520with%2520one%2520pixel%2520on%2520each%2520iteration.%250A%2520%2520%2520%2520%2520%2520dst%2520%253A%253D%2520image.NewNRGBA(image.Rect(0%252C%25200%252C%2520bounds.Dx()-1%252C%2520bounds.Dy()))%250A%250A%2520%2520%2520%2520%2520%2520for%2520_%252C%2520seam%2520%253A%253D%2520range%2520seams%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520y%2520%253A%253D%2520seam.Y%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520for%2520x%2520%253A%253D%25200%253B%2520x%2520%253C%2520bounds.Max.X%253B%2520x%252B%252B%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520if%2520seam.X%2520%253D%253D%2520x%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520if%2520debug%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520dst.Set(x-1%252C%2520y%252C%2520color.RGBA%257B255%252C%25200%252C%25200%252C%2520255%257D)%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520continue%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%2520else%2520if%2520seam.X%2520%253C%2520x%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520dst.Set(x-1%252C%2520y%252C%2520img.At(x%252C%2520y))%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%2520else%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520dst.Set(x%252C%2520y%252C%2520img.At(x%252C%2520y))%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520%257D%250A%2520%2520%2520%2520%2520%2520return%2520dst%250A%257D\"\n  style=\"width:100%; height:473px; border:0; overflow:hidden;\"\n  sandbox=\"allow-scripts allow-same-origin\"><br \/>\n<\/iframe><\/p>\n<p>The same logic can be applied to enlarge images, only this time we compute the optimal vertical or horizontal seam (<em><strong>s<\/strong><\/em>) and duplicate the pixels of <em><strong>s<\/strong><\/em> by averaging them with their left and right neighbors (top and bottom in the horizontal case).<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/final-image-resized.png\" alt=\"\" width=\"650\" class=\"aligncenter size-full wp-image-1260\" srcset=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/final-image-resized.png 896w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/final-image-resized-300x291.png 300w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/final-image-resized-768x744.png 768w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/final-image-resized-650x630.png 650w\" sizes=\"(max-width: 896px) 100vw, 896px\" \/><\/p>\n<p style=\"text-align: center;\">Fig.5: The final resized image<\/p>\n<h2>Prevent face deformation with face detection<\/h2>\n<p>For certain content such as faces when relations between features are important, automatic face detection can be used to identify the areas that needs protection. This is an important requirement since in certain situations when sensible image regions like faces (detected by the sobel filter operator) are compressed in a small area it might happen to get distorted (see Fig.6). To prevent this, once these regions are detected we can increase the pixel intensity to a very high level before running the edge detector. This way we can assure that the sobel detector will consider these regions as important ones, which also means that they will receive high energy values.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/face-detection.jpg\" alt=\"\" width=\"894\" height=\"283\" class=\"aligncenter size-full wp-image-1262\" srcset=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/face-detection.jpg 894w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/face-detection-300x95.jpg 300w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/face-detection-768x243.jpg 768w, https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/face-detection-650x206.jpg 650w\" sizes=\"auto, (max-width: 894px) 100vw, 894px\" \/><\/p>\n<p style=\"text-align: center;\">Fig.6: Resizing image with and without face detection<\/p>\n<p>Below you can see the seam carving algorithm in action, first checking for human faces prior resizing the image. It&#8217;s visible from the image that the seams are trying to avoid the face zone normally marked with a rectangle. And this is done with a simple trick: if face zone is detected that rectangle is marked with a white background which makes the detector to believe that it&#8217;s an area with high energy map. For face detection it was used <a href=\"https:\/\/github.com\/esimov\/pigo\">Pigo<\/a>, another Go library developed by me.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/esimov.com\/blog\/wp-content\/uploads\/2019\/05\/seam-carving-in-action.gif\" alt=\"\" width=\"669\" height=\"447\" class=\"aligncenter size-full wp-image-1269\" \/><\/p>\n<h2>Conclusion<\/h2>\n<p>Seam carving is a technique which can be used on a variety of image manipulations including aspect ratio change, image retargeting, object removal or even content amplification. Also it can be seamlessly integrated with a Convolutional Neural Network which can be trained for specific object recognition, making the perfect toolset for every content delivery solution. There are other numerous possible domains this technique could be applied or even extended like video resizing or the ability for continuous resizing in real time.<\/p>\n<p>Of course as every technology has its limitations, like the case when the processed image is very condensed, in the sense that it does not contain \u201cless\u201d important areas, ugly artifacts might appear. Also the algorithm does not perform very well when the image albeit being not very condensed, the content is laid out in a manner that it does not permit the seams from bypassing some important parts.  In certain situations by tweaking the parameters, like using a higher sobel threshold or applying a blur filter these kind of limitations could be surpassed.<\/p>\n<h2>Source code<\/h2>\n<p>The code is open sourced and can be found on my Github page:<\/p>\n<p><a href=\"https:\/\/github.com\/esimov\/caire\">https:\/\/github.com\/esimov\/caire<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/esimov\/pigo\">https:\/\/github.com\/esimov\/pigo<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Caire is a content aware image resize library developed in Go which is capable of resizing the images without content distortion. It&#8217;s fully customizable and open source.<\/p>\n","protected":false},"author":1,"featured_media":1278,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[47,85,86,58,41,21],"tags":[88,80,81,59,60,77,79,87,78],"class_list":["post-1246","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","category-computer-vision","category-face-detection","category-go","category-programming","category-tutorials","tag-computer-vision","tag-edge-detection","tag-face-detection","tag-go-2","tag-golang","tag-image-processing","tag-image-resize","tag-machine-learning","tag-seam-carving"],"_links":{"self":[{"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/posts\/1246","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/comments?post=1246"}],"version-history":[{"count":24,"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/posts\/1246\/revisions"}],"predecessor-version":[{"id":1265,"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/posts\/1246\/revisions\/1265"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/media\/1278"}],"wp:attachment":[{"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/media?parent=1246"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/categories?post=1246"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/esimov.com\/blog\/wp-json\/wp\/v2\/tags?post=1246"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}