Cutting cards in an image

I did some debugging and found the problem.

The thing is that your white is not completely white and even with the threshold sometimes spots are left aside.

That’s I added the smoothWhiteBackground() that was getting rid of small gaps by checking if it was surrounded by close pixels. The biggest problem was with the black edge on the top between the 3 upper cards on the right.

To solve your problem I changed the distance to 5 pixels (instead of 2 pixels) and then called it 2 times at the end of the removeWhiteBackground() function instead of 1 time. It works but that’s not enough to ensure that everything will go correctly.

It will crash if you have more than 8 zones identified. What you should do is, after you remove the white background (just the main part actually), use another flood algorithm to get the size of each “air pocket” that were created. Then if the size is less than x pixels, you get rid of them, they are part of the background.

Anyway, there is the “improved” version:

PImage originalPic;              // The pic with your 8 pictures
int w, h, wh;                    // Width and height of originalPic and product of the 2 variables
boolean[] pixelsUsed;            // Store the pixels that we have already used for the background or a cart
ArrayList<Integer> queuedPixels; // Store the pixels that will need to be analyzed
boolean[] pixelsAlreadySelected; // Store the pixels that have been analyzed and the one that are waiting to be analyzed in the queudPixels array
PImage[] cards;                  // Store the cards
int imgToShow;
int xPos, yPos;


void setup() {
  //fullScreen();
  background(20);

  // Init
  originalPic = loadImage("ImgTest2.jpg");
  w = originalPic.width;
  h = originalPic.height;
  wh = w * h;
  cards = new PImage[8];
  imgToShow = 0;
  queuedPixels = new ArrayList<Integer>();  
  pixelsUsed = new boolean[wh];
  pixelsAlreadySelected = new boolean[wh];
  xPos = 0;
  yPos = 0;


  // Initializing the pixelAnalyzed array with false values
  for (int i = 0; i < pixelsUsed.length; i++) {
    pixelsUsed[i] = false;
  }


  // Initializing the pixelsAlreadySelected array with false values
  for (int i = 0; i < pixelsAlreadySelected.length; i++) {
    pixelsAlreadySelected[i] = false;
  }


  // Do the magic
  removeWhiteBackground();

  //originalPic.loadPixels();
  //for (int i = 0; i < wh; i++) {
  //  if (pixelsUsed[i]) {
  //    originalPic.pixels[i] = color(255, 0, 0);
  //  } else {
  //    originalPic.pixels[i] = color(255, 255, 255);
  //  }
  //}
  //originalPic.updatePixels();

  for (int i = 0; i < 8; i++) {
    getCard(i);
    cards[i].save("card_" + i + ".jpg");
  }

  //image(cards[imgToShow], 0, 0);
  
  println("Done");
}




void removeWhiteBackground() {
  // Getting rid of the white background (assuming the top right pixels is white)

  // Init
  queuedPixels.add(0);
  pixelsAlreadySelected[0] = true;


  while (queuedPixels.size() > 0) { // While there is some pixels to analyzed
    int idx = queuedPixels.get(0); // Get the index of the next pixel to analyzed

    if (colorDistance2(originalPic.pixels[idx], color(255, 255, 255)) <= 25) { // If that pixel is white
      pixelsUsed[idx] = true; // We now have used that pixel for the background
      //if (idx == 3547934 || idx == 3551439 || idx == 3554942 || idx == 3551437) {
      //    println(idx);
      //  }

      if (idx - w >= 0 && pixelsAlreadySelected[idx - w] == false) { // Spread to the top
        pixelsAlreadySelected[idx - w] = true;
        queuedPixels.add(idx - w);
      }

      if ((idx % w) != (w - 1) && idx + 1 < wh && pixelsAlreadySelected[idx + 1] == false) { // Spread to the right
        pixelsAlreadySelected[idx + 1] = true;
        queuedPixels.add(idx + 1);
      }

      if (idx + w < wh && pixelsAlreadySelected[idx + w] == false) { // Spread to the bottom
        pixelsAlreadySelected[idx + w] = true;
        queuedPixels.add(idx + w);
      }

      if ((idx % w) != 0 && idx - 1 >= 0 && pixelsAlreadySelected[idx - 1] == false) { // Spread to the left
        pixelsAlreadySelected[idx - 1] = true;
        queuedPixels.add(idx -1);
      }
    }

    queuedPixels.remove(0); // Don't forget to remove the pixel from the queue
  }

  SmoothWhiteBackground();
  SmoothWhiteBackground();
}




// Used to avoid some issues with edge pixels that are some times black
// Consider a pixel as a background if it has pixels above and under or to the left and to the right (the dist variable set how many pixels to the top rigth bottom left we are checking)
void SmoothWhiteBackground() {
  boolean[] pixelsUsedCopy; 
  int dist = 5;
  boolean up, right, bottom, left;

  pixelsUsedCopy = new boolean[wh];
  arrayCopy(pixelsUsed, pixelsUsedCopy);

  for (int i = 0; i < wh; i++) {
    if (pixelsUsed[i] == false) {
      int x = i % w; 
      int y = i / w;

      up = false;
      right= false;
      bottom = false;
      left = false;

      // Up
      if (y < dist) {
        up = true;
      } else {
        for (int j = 0; j < dist; j++) {
          if (pixelsUsedCopy[i - (j + 1) * w] == true) {
            up = true;
          }
        }
      }

      // Right
      if (x > w - dist - 1) {
        right = true;
      } else {
        for (int j = 0; j < dist; j++) {
          if (pixelsUsedCopy[i + j + 1] == true) {
            right = true;
          }
        }
      }

      // Bottom
      if (y > h - dist - 1) {
        bottom = true;
      } else {
        for (int j = 0; j < dist; j++) {
          if (pixelsUsedCopy[i + (j + 1) * w] == true) {
            bottom = true;
          }
        }
      }

      // Up
      if (x < dist) {
        left = true;
      } else {
        for (int j = 0; j < dist; j++) {
          if (pixelsUsedCopy[i - (j + 1)] == true) {
            left = true;
          }
        }
      }

      // Update the array
      if ((up && bottom) || (left && right)) {
        pixelsUsed[i] = true;
        //if (i == 3547934 || i == 3551439 || i == 3554942 || i == 3551437) {
        //  println(i);
        //}
      }
    }
  }
}




void getCard(int imgNb) {
  //originalPic.loadPixels();
  //color col = color(random(255), random(255), random(255));



  int xMax, xMin, yMax, yMin; // The boundary of the card

  // Init
  xMax = 0;
  xMin = w;
  yMax = 0;
  yMin = h;

  // Find the first pixel that is not part of the background or that wasn't used for another card
  int i = 0;
  while (pixelsUsed[i] == true) {
    i++;
  }

  // Reset the queue
  queuedPixels.clear();
  queuedPixels.add(i);

  // Reset pixelsAlreadySelected 
  for (int j = 0; j < pixelsAlreadySelected.length; j++) {
    pixelsAlreadySelected[j] = false;
  }
  pixelsAlreadySelected[i] = true;

  // Get the card
  while (queuedPixels.size() > 0) {
    int idx = queuedPixels.get(0);

    if (pixelsUsed[idx] == false) { // It is part of the card
      pixelsUsed[idx] = true;

      //originalPic.pixels[idx] = col;


      // transform idx in x, y coordinate
      int x = idx % w; 
      int y = idx / w;

      // Getting the lower and upper value
      if (x > xMax) {
        xMax = x;
      }

      if (x < xMin) {
        xMin = x;
      }

      if (y > yMax) {
        yMax = y;
      }

      if (y < yMin) {
        yMin = y;
      }

      //Spreading
      if (idx - w >= 0 && pixelsAlreadySelected[idx - w] == false) { // Spread to the top
        pixelsAlreadySelected[idx - w] = true;
        queuedPixels.add(idx - w);
      }

      if ((idx % w) != (w - 1) && idx + 1 < wh && pixelsAlreadySelected[idx + 1] == false) { // Spread to the right
        pixelsAlreadySelected[idx + 1] = true;
        queuedPixels.add(idx + 1);
      }

      if (idx + w < wh && pixelsAlreadySelected[idx + w] == false) { // Spread to the bottom
        pixelsAlreadySelected[idx + w] = true;
        queuedPixels.add(idx + w);
      }

      if ((idx % w) != 0 && idx - 1 >= 0 && pixelsAlreadySelected[idx - 1] == false) { // Spread to the left
        pixelsAlreadySelected[idx - 1] = true;
        queuedPixels.add(idx -1);
      }
    }

    queuedPixels.remove(0);
  }

  cards[imgNb] = originalPic.get(xMin, yMin, xMax - xMin, yMax - yMin);



  //originalPic.updatePixels();
}




int colorDistance(color col1, color col2) {
  int deltaR = (col1 >> 16 & 0xFF) - (col2 >> 16 & 0xFF);
  int deltaG = (col1 >> 8 & 0xFF)  - (col2 >> 8 & 0xFF);
  int deltaB = (col1 & 0xFF)       - (col2 & 0xFF);

  return deltaR * deltaR + deltaG * deltaG + deltaB * deltaB;
}





int colorDistance2(color col1, color col2) {
  int deltaR = (col1 >> 16 & 0xFF) - (col2 >> 16 & 0xFF);
  int deltaG = (col1 >> 8 & 0xFF)  - (col2 >> 8 & 0xFF);
  int deltaB = (col1 & 0xFF)       - (col2 & 0xFF);

  return max(abs(deltaR), abs(deltaG), abs(deltaB));
}




void draw() {
  //background(20);
  //image(originalPic, xPos, yPos, 7008, 4954);
  
  
  
  //fill(0);
  //noStroke();
  
  //int x = 3551438 % w; 
  //int y = 3551438 / w;
  //ellipse(2 * x + xPos, 2 * y + yPos, 5, 5);
}




void mouseClicked() {
  //imgToShow++;
  //if (imgToShow > 7) {
  //  imgToShow = 0;
  //}

  //background(20);
  //image(cards[imgToShow], 0, 0);
}



void keyPressed() {
  if (keyCode == 38) {
    yPos += 100;
  }
  if (keyCode == 39) {
    xPos -= 100;
  }
  if (keyCode == 40) {
    yPos -= 100;
  }
  if (keyCode == 37) {
    xPos += 100;
  }
}
2 Likes