Automated removing of background

Anyone, especially newbies, asking for help with Photoshop Scripting and Photoshop Automation - as opposed to those contributing to discussion about an aspect of Photoshop Scripting

Moderators: Tom, Kukurykus

truslerp

Automated removing of background

Post by truslerp »

Hello all,
Great forum. I have to come up with a way to automate the removal of the background of an image.

The image has a white background(and always white), with some product image centered. The source image is flat, I have to seperate the product image from the white background so I can do other things to the selection, sans background.

I am kinda lost trying to figure out how to automate this part, any advice would be greatly appreciated.


Thanks!
Powell
Patrick

Automated removing of background

Post by Patrick »

I made a script for this a long time ago while trying to work with product shots over a chromakey backdrop. I took it about as far as I could, but it my opinion it is a very hard thing to automate. Sometimes it works great, sometimes it looks really bad.

I'll try to post it later tonight, I need to condense it down a bit because its currently setup using a bunch of my general include files.

Patrick
truslerp

Automated removing of background

Post by truslerp »

Patrick,
Thanks for the reply!

>I'll try to post it later tonight
Anything would be very much appreciated.

>but it my opinion it is a very hard thing to automate
Thats what I thought, my current script (centers, adds effects, outputs three sizes) assumes that the image has been seperated first manually. The guys using the script have to do hundreds of images, so if I can get something even if it works 50% of the time would be great, the boss would love it.

-Powell
xbytor

Automated removing of background

Post by xbytor »

Here's something I wrote up a couple of months ago. It was for someone doing some chromakey work.

It basically uses the Magic Wand at each of the four corners of the image with a tolerance that you can change in the top part of the script. The script could be simplified since it looks like this without the stdlib.js include file:

Code: Select all//
// TrimImage
//
// $Id: TrimImage.jsx,v 1.1 2008/09/19 17:17:43 anonymous Exp $
//
app;

TrimImageOptions = function(obj) {
  var self = this;

  // magic wand options
  self.x = 0;
  self.y = 0;
  self.tolerance = 50;

  Stdlib.copyFromTo(obj, self);
};

//
//@show include
//
//@includepath "/c/Program Files/Adobe/xtools;/Developer/xtools"
//
//@include "xlib/stdlib.js"
//

TrimImageOptions = function(obj) {
  var self = this;

  // magic wand options
  self.x = 0;
  self.y = 0;
  self.tolerance = 50;

  Stdlib.copyFromTo(obj, self);
};
TrimImageOptions.prototype.typename = "TrimImageOptions";

TrimImage = function() {
};

TrimImage.prototype.typename = "TrimImage";

TrimImage.prototype.processDocument = function(opts, doc) {
  var self = this;

  doc.flatten();
  doc.activeLayer.name = "Image";
//   var layer = doc.activeLayer.duplicate();
//   doc.activeLayer = layer;
//   doc.backgroundLayer.visible = false;

  opts.x = opts.y = 0;
  self.clearFrom(doc, opts);

  opts.y = doc.height.value - 1;
  self.clearFrom(doc, opts);

  opts.y = doc.height.value - 1;
  opts.x = doc.width.value - 1;;
  self.clearFrom(doc, opts);

  opts.y = 0;
  opts.x = doc.width.value - 1;;
  self.clearFrom(doc, opts);
};

TrimImage.prototype.clearFrom = function(doc, opts) {
  var self = this;

  try {
    // This is how we select with the Magic Wand...
    Stdlib.magicWand(doc, opts.x, opts.y, opts.tolerance, false, true);

  } catch (e) {
    alert(e);
    return;

  }

  doc.selection.clear();
  doc.selection.invert();
  Stdlib.crop(doc);
  doc.selection.deselect();
//   doc.backgroundLayer.visible = true;
//   layer.remove();
};


function main() {
  if (app.documents.length == 0) {
    return;
  }

  var ti = new TrimImage();
  var doc = app.activeDocument;

  var opts = new TrimImageOptions();

  ti.processDocument(opts, doc);
};

main();

"TrimImage.jsx";
// EOF

-X
Patrick

Automated removing of background

Post by Patrick »

Here is mine, it is conceptually doing pretty much the same thing as xbytors, but I use a dialog to let you toggle which corners to use in case the photo subject bleeds off one of the sides.

Patrick

Code: Select all#script "Erase BG";

///////////////////////////////////////////////////////////////////////////////
// function pulled from script listener to use the magic wand
///////////////////////////////////////////////////////////////////////////////
function wand(x,y,tol,option)
{
   // options:
   // setd = initial selection
   // AddT = add to selection

   var id9466 = charIDToTypeID( option );
       var desc1828 = new ActionDescriptor();
       var id9467 = charIDToTypeID( "null" );
           var ref1340 = new ActionReference();
           var id9468 = charIDToTypeID( "Chnl" );
           var id9469 = charIDToTypeID( "fsel" );
           ref1340.putProperty( id9468, id9469 );
       desc1828.putReference( id9467, ref1340 );
       var id9470 = charIDToTypeID( "T   " );
           var desc1829 = new ActionDescriptor();
           var id9471 = charIDToTypeID( "Hrzn" );
           var id9472 = charIDToTypeID( "#Pxl" );
           desc1829.putUnitDouble( id9471, id9472, x );
           var id9473 = charIDToTypeID( "Vrtc" );
           var id9474 = charIDToTypeID( "#Pxl" );
           desc1829.putUnitDouble( id9473, id9474, y );
       var id9475 = charIDToTypeID( "Pnt " );
       desc1828.putObject( id9470, id9475, desc1829 );
       var id9476 = charIDToTypeID( "Tlrn" );
       desc1828.putInteger( id9476, tol );
       var id9477 = charIDToTypeID( "AntA" );
       desc1828.putBoolean( id9477, true );
   executeAction( id9466, desc1828, DialogModes.NO );
};


function main()
{
   try
   {
   
      // basic error checking      
      if (app.documents.length < 1)   // make sure there is a image open
      {
         alert("You don't have an image opened.  Please open an image before running this script.");
         return;   // quit
      } else {
         docRef = app.activeDocument;   // reference to current file
      };

      // if on bg layer, convert it to normal
      if (docRef.activeLayer.isBackgroundLayer == true) {
         docRef.activeLayer.isBackgroundLayer = false;
         docRef.activeLayer.name  = 'Background'; // if you want...
      };

      function wandPopUp()
      {
         // establish document dimensions
         x = docRef.width.value;
         y = docRef.height.value;
      
         // sample area variables
         nw = true;
         ne = true;
         sw = true;
         se = true;

         // defaults
         tol = 25;
         fea = 2;
         exp = 3;
         i = 100;
   
         // sequence to figure out weather to start selection or add to selection
         function wandOption(){
            if (i == 0) {
               type = "setd";
               i++;
            } else {
               type = "AddT";
            };
         };
   
         
         var p = 0;
         var options = false;
   
         // build dialog window
         var win = new Window(
         "dialog{\
            text:'Chromakey Eraser',bounds:[100,100,270,250],\
            button0:Button{bounds:[20,125,90,145] , text:'Erase' },\
            button1:Button{bounds:[90,125,125,145] , text:'Quit' },\
            statictext0:StaticText{bounds:[20,20,140,37] , text:'Selection Tolerance' ,properties:{scrolling:undefined,multiline:undefined}},\
            edittext0:EditText{bounds:[125,20,150,37] , text:'" +  tol +"' ,properties:{multiline:false,noecho:false,readonly:false}},\
            slider0:Slider{bounds:[15,60,155,70] , minvalue:1,maxvalue:255,value:" + tol + "},\
            check1:Checkbox{bounds:[200,10,240,50] , text:'NW' , value:true },\
            check2:Checkbox{bounds:[200,65,240,80] , text:'SW' , value:true },\
            check3:Checkbox{bounds:[260,10,300,50] , text:'NE' , value:true },\
            check4:Checkbox{bounds:[260,65,300,80] , text:'SE' , value:true },\
            progress:Progressbar{bounds:[20,100,150,110] , minvalue:0,maxvalue:100,value:0},\
            statictext1:StaticText{bounds:[280,135,310,152] , text:'100%' ,properties:{scrolling:undefined,multiline:undefined}},\
            button2:Button{bounds:[125,125,150,145] , text:'->' },\
            featherTxt:StaticText{bounds:[200,90,250,110] , text:'Feather' ,properties:{scrolling:undefined,multiline:undefined}},\
            expandTxt:StaticText{bounds:[200,110,250,130] , text:'Expand' ,properties:{scrolling:undefined,multiline:undefined}},\
            featherBox:EditText{bounds:[275,90,300,110] , text:'" +  fea +"' ,properties:{multiline:false,noecho:false,readonly:false}},\
            expandBox:EditText{bounds:[275,110,300,130] , text:'" +  exp +"' ,properties:{multiline:false,noecho:false,readonly:false}},\
         };");

         // sequence to perform the 4-corner selection on the image
         function wandCorners(tolerance) {
            win.button0.enabled = false;
            win.button1.enabled = false;
            win.progress.value = 0;
            win.statictext1.text = "0%";
            i = 0;

            docRef.selection.deselect();
            
            if (nw == true) {
               wandOption();
               wand(50,50,tolerance,type);
            };
            
            while (i <= 25) {
               win.progress.value = i;
               win.statictext1.text = i + "%";
               i++;
               sleep(1);
            };
            
            if (ne == true) {
               wandOption();
               wand(x-50,50,tolerance,type);
            };

            while (i <= 50) {
               win.progress.value = i;
               win.statictext1.text = i + "%";
               i++;
               sleep(1);
            };
            
            if (sw == true) {
               wandOption();
               wand(50,y-50,tolerance,type);
            };

            while (i <= 75) {
               win.progress.value = i;
               win.statictext1.text = i + "%";
               i++;
               sleep(1);
            };
            
            if (se == true) {
               wandOption();
               wand(x-50,y-50,tolerance,type);
            };

            while (i <= 100) {
               win.progress.value = i;
               win.statictext1.text = i + "%";
               i++;
               sleep(1);
            };
            
            win.button0.enabled = true;
            win.button1.enabled = true;
            // debug
            // alert(nw + " | " + ne + "\n" + sw + " | " + se);
         };
   
         // perform initial selection
         wandCorners(tol);

         // initiate stop variable to make quit/corner "x" work properly
         stop = true;   // global
   
         // NW checkbox functionality
         win.check1.onClick = function() {
            // update selection
            if (nw == true) {
               nw = false;
               wandCorners(tol);
            } else {
               nw = true;
               wandCorners(tol);
            };
         };

         // NE checkbox functionality
         win.check3.onClick = function() {
            // update selection
            if (ne == true) {
               ne = false;
               wandCorners(tol);
            } else {
               ne = true;
               wandCorners(tol);
            };
         };

         // SW checkbox functionality
         win.check2.onClick = function() {
            // update selection
            if (sw == true) {
               sw = false;
               wandCorners(tol);
            } else {
               sw = true;
               wandCorners(tol);
            };
         };
   
         //  checkbox functionality
         win.check4.onClick = function() {
            // update selection
            if (se == true) {
               se = false;
               wandCorners(tol);
            } else {
               se = true;
               wandCorners(tol);
            };
         };

         // quit button functionality
         win.button1.onClick = function() {
            win.close(4);
         };

         // run button functionality
         win.button0.onClick = function() {
            win.close(4);
            stop = false;   // global
         };

         // options button functionality
         win.button2.onClick = function() {
            if (options == false) {
               var cx = win.bounds[0];   // current x
               var cy = win.bounds[1];   // current y
               var cw = win.bounds[2];   // current width
               var ch = win.bounds[3];   // current height
               win.bounds = [cx,cy,cw+140,ch];
               win.button2.text = "<-";
               options = true;
            } else {
               var cx = win.bounds[0];   // current x
               var cy = win.bounds[1];   // current y
               var cw = win.bounds[2];   // current width
               var ch = win.bounds[3];   // current height
               win.bounds = [cx,cy,cw-140,ch];
               win.button2.text = "->";
               options = false;
            };
//            win.center();

         };
            
         // update text on slider change
         win.slider0.onChanging = function() {
            var v = Math.floor(this.value);
            this.parent.edittext0.text = v;
         };

         win.featherBox.onChanging = function() {
            var v = Math.floor(this.text);
            fea = v;
         };

         win.expandBox.onChanging = function() {
            var v = Math.floor(this.text);
            exp = v;
         };

         // update slider on text change
         win.edittext0.onChange = function() {
            var v = parseInt(this.text);
            if ( v >= 1 && v <= 255) {
               this.parent.slider0.value = v;
               // make selection on text change
               tol = v;
               docRef.selection.deselect();
               wandCorners(tol);
            };
         };

         // make selection after slider change
         win.slider0.onChange = function() {
            tol = Math.floor(this.value);
            wandCorners(tol);
         };
   
         // show the dialog window
         win.center();
         win.show();
   
         // clean dialog result for after the function
         usertol = win.slider0.value.toFixed(0);
      };

      // show tool to user
      wandPopUp();

      if (stop != true) {
         // massage the selection
         docRef.selection.expand(exp);
         docRef.selection.feather(fea);
         // clear the selection
         docRef.selection.clear();
         docRef.selection.deselect();
      } else {
         docRef.selection.deselect();
//         alert(fea + " / " + exp);
      };

   }
    catch (e)
    {   // display error
      alert("Script failed! \r\r" + e);
    }
};

main();
xbytor

Automated removing of background

Post by xbytor »

Here is mine, it is conceptually doing pretty much the same thing as xbytors, but I use a dialog to let you toggle which corners to use in case the photo subject bleeds off one of the sides.

My client had 180,000 images of products on green screens, so there wasn't a chance of bleeds. What was problematic (and the reason that I did four corners) is that background was not as uniformly lit as one would like.

-X
Patrick

Automated removing of background

Post by Patrick »

We found even with a good contrast between the chromakey and the product, the wand is the weakest link in this process. Feathering helped a lot, but it was a tricky task to get enough feathering to make the wand look good while not leaving the final result with to soft of a edge.

We ended up ditching this and going back to manually using the extract tool in Photoshop. It is tough to ditch automation for such a time consuming process, but the results are pretty hard to beat,

Patrick
Patrick

Automated removing of background

Post by Patrick »

xbytor wrote:What was problematic (and the reason that I did four corners) is that background was not as uniformly lit as one would like.

We had that same problem. You apparently need a large area to be able to light the background completely separate from the product - I think I read at the time they should be 20 feet apart, and at the time we didn't have that much space to work with.
xbytor

Automated removing of background

Post by xbytor »

Patrick wrote:We ended up ditching this and going back to manually using the extract tool in Photoshop. It is tough to ditch automation for such a time consuming process, but the results are pretty hard to beat

As I noted, my client had a very large number of images to process. What they probably ended up doing was running my script over batches, inspecting the results and manually handling the troublesome images. I was hoping to get the number of manually edited images down into the 1-2% range. I'll have to check back with them and see how well that went.

-X