sikuli onChange too sensitive

Asked by Ron Khera on 2019-07-26

I am trying to detect image changes using the onChange function.
I have a region over my laptop camera and the code appears to work.
When the camera is covered no changes are observed, which is correct.

However, when the camera is not covered the change event fires every 300ms even when no change appeared to occur.
I have tried various values for the pixel thresh hold, the observe scan rate and the minimum similarity,
Does anybody have suggestions on my approach or settings ?

Below is my complete code solution.

---
package com.images;

import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import org.sikuli.basics.Settings;
import org.sikuli.script.FindFailed;
import org.sikuli.script.ImagePath;
import org.sikuli.script.Key;
import org.sikuli.script.Match;
import org.sikuli.script.ObserveEvent;
import org.sikuli.script.ObserverCallBack;
import org.sikuli.script.Pattern;
import org.sikuli.script.Region;
import org.sikuli.script.Screen;

public class ChangeDemo {

 private static final double OBSERVATION_DURATION = 5;
 private static final String IMAGE_PATH = "images";
 private LocalDateTime lastDateTime;
 private List<String> messages = new ArrayList<>();

 // Define camera region
 private static int X = 1115;
 private static int Y = 340;
 private static int H = 355;
 private static int W = 640;

 public static void main(String[] args) {
  new ChangeDemo().trackChanges();
 }

 public ChangeDemo() {
  Settings.UserLogs = true;
  Settings.UserLogTime = true;
  ImagePath.setBundlePath(IMAGE_PATH);
 }

 private void trackChanges() {
  if (!openCamera()) {
   return;
  }

  process();

  closeCamera();
 }

 private boolean openCamera() {
  boolean result = false;
  String imageFileName = "magnifier.png";

  Screen screen = new Screen();
  Match match = findTarget(screen, imageFileName);
  if (null == match)
   return false;

  try {
   match.click(imageFileName);
   match.wait(1.0);
   match.type("camera" + Key.ENTER);
   match.wait(3.0);
   result = true;
  } catch (FindFailed e) {
   log("** FindFailed: " + e.getMessage());
  }

  return result;
 }

 private void closeCamera() {
  Screen screen = new Screen();
  Match match = findTarget(screen, "camera-window-icons.png");
  if (null == match)
   return;

  match = findTarget(match, "camera-window-close.png");
  if (null == match)
   return;
  match.click();
 }

 private void process() {
  // The number of times actual search operations are performed per second
  float observeScanRate = 3f;

  // The minimum area size in pixels that changes it’s content to trigger a change
  // event
  // when using Region.onChange() when no value is specified.
  // The default value is 50 (a rectangle of about 7x7 Pixels).
  int pixelChanged = 50;

  // Each Pattern object has its own min similarity value, which is 0.7, if no
  // similarity is given.
  float minSimilarity = 0.7f;

  observe(observeScanRate, pixelChanged, minSimilarity);
  writeMessages(observeScanRate, pixelChanged, minSimilarity);
 }

 private void observe(float observeScanRate, int threshold, float minSimilarity) {
  Settings.MinSimilarity = minSimilarity;
  Settings.ObserveScanRate = observeScanRate;
  Settings.ObserveMinChangedPixels = threshold;
  log("Settings.MinSimilarity: " + Settings.MinSimilarity);
  log("Settings.ObserveScanRate: " + Settings.ObserveScanRate);
  log("Settings.ObserveMinChangedPixels: " + Settings.ObserveMinChangedPixels);

  lastDateTime = null;
  LocalDateTime observeStartDateTime = LocalDateTime.now();

  Region cameraRegion = new Screen().setRect(X, Y, W, H);
  cameraRegion.setObserveScanRate(observeScanRate);

  ObserverCallBack eventCallBack = new ObserverCallBack() {
   @Override
   public void changed(ObserveEvent e) {
    LocalDateTime dateTime = LocalDateTime.now();
    Match m = e.getMatch();
    if (null!=m) {
     //m.highlight(0.3f, "red");
     m.saveScreenCapture("changes", "m_"+e.getName()+"_"+e.getCount());
    }
    Region r = e.getRegion();
    if (null!=r) {
     //r.highlight(0.3f, "red");
     r.saveScreenCapture("changes", "r_"+e.getName()+"_"+e.getCount());
    }

    if (null != e.getImage()) {
     try {
      RenderedImage image = (RenderedImage) e.getImage();
      ImageIO.write(image, "jpg", new File("changes/"+e.getName() + ".jpg"));
     } catch (IOException e1) {
      e1.printStackTrace();
     }
    }

    log("** " + dateTime + " " + e);
    if (null != lastDateTime) {
     long seconds = lastDateTime.until(dateTime, ChronoUnit.SECONDS);
     long ms = lastDateTime.until(dateTime, ChronoUnit.MILLIS);
     log("\t lastDateTime " + lastDateTime + " Seconds: " + seconds + " ms: " + ms);
    }
    //highlightChanges(e);
    lastDateTime = dateTime;
   }

   private void highlightChanges(ObserveEvent e) {
    int sequence = 0;
    for (Match change : e.getChanges()) {
     if (null != change) {
      sequence++;
      String id = e.getCount() + "_" + e.getName() + "_" + sequence;
      log(id + " " + " match: " + change.getScore() + " " + change);
      change.saveScreenCapture("changes", id);
      // change.highlight(0.3f, "red");
     }
    }
   }
  };

  cameraRegion.onChange(threshold, eventCallBack);
  cameraRegion.observe(OBSERVATION_DURATION);
  cameraRegion.stopObserver();

  LocalDateTime observeEndDateTime = LocalDateTime.now();
  long minutes = observeStartDateTime.until(observeEndDateTime, ChronoUnit.MINUTES);
  long seconds = observeStartDateTime.until(observeEndDateTime, ChronoUnit.SECONDS);
  long ms = observeStartDateTime.until(observeEndDateTime, ChronoUnit.MILLIS);
  log("Observation StartDateTime: " + observeStartDateTime + " EndDateTime: " + observeEndDateTime + " Minutes: "
    + minutes + " Seconds: " + seconds + " ms: " + ms);
 }

 private void log(String msg) {
  System.out.println(msg);
  messages.add(msg + System.lineSeparator());
 }

 private Match findTarget(Region region, String imageFileName) {
  return findTarget(region, imageFileName, 0.7f);
 }

 private Match findTarget(Region region, String imageFileName, float similar) {
  System.out.println("findTarget() ImageFileName: " + imageFileName + " similar: " + similar);
  Match matchedResult = null;
  try {
   Pattern pattern = new Pattern(imageFileName).similar(similar);
   matchedResult = region.find(pattern);
  } catch (FindFailed e) {
   System.out.println("FindFailed : " + e.getMessage());
  }
  return matchedResult;
 }

 private void writeMessages(float observeScanRate, int pixelChanged, float minSimilarity) {
  FileWriter fileWriter = null;
  try {
   fileWriter = new FileWriter(
     "logs\\log_" + LocalDateTime.now().getNano() + "_OSR" + String.format("%.2f", observeScanRate)
       + "_P" + pixelChanged + "_MS" + String.format("%.2f", minSimilarity) + ".txt");
   for (String message : messages) {
    fileWriter.write(message);
   }
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   messages.clear();
   try {
    if (fileWriter != null) {
     fileWriter.flush();
     fileWriter.close();
    }
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }

}

Question information

Language:
English Edit question
Status:
Answered
For:
Sikuli Edit question
Assignee:
No assignee Edit question
Last query:
2019-07-26
Last reply:
2019-07-28
masuo (masuo-ohara) said : #1

This is because the area displaying the camera image is used directly as Sikuli region.
I recommend the following way.
Periodically capture the contents of the display area from the camera and display it in a new area.
Use this new area as Sikuli region.

Can you help with this problem?

Provide an answer of your own, or ask Ron Khera for more information if necessary.

To post a message you must log in.