| Line 2... |
Line 2... |
| 2 |
|
2 |
|
| 3 |
import nu.pattern.OpenCV;
|
3 |
import nu.pattern.OpenCV;
|
| 4 |
import org.apache.commons.io.FileUtils;
|
4 |
import org.apache.commons.io.FileUtils;
|
| 5 |
import org.apache.logging.log4j.LogManager;
|
5 |
import org.apache.logging.log4j.LogManager;
|
| 6 |
import org.apache.logging.log4j.Logger;
|
6 |
import org.apache.logging.log4j.Logger;
|
| - |
|
7 |
import org.opencv.core.Core;
|
| 7 |
import org.opencv.core.Mat;
|
8 |
import org.opencv.core.Mat;
|
| - |
|
9 |
import org.opencv.core.Scalar;
|
| 8 |
import org.opencv.core.Size;
|
10 |
import org.opencv.core.Size;
|
| 9 |
import org.opencv.imgcodecs.Imgcodecs;
|
11 |
import org.opencv.imgcodecs.Imgcodecs;
|
| 10 |
import org.opencv.imgproc.Imgproc;
|
12 |
import org.opencv.imgproc.Imgproc;
|
| 11 |
import org.json.JSONObject;
|
13 |
import org.json.JSONObject;
|
| 12 |
import org.openqa.selenium.*;
|
14 |
import org.openqa.selenium.*;
|
| Line 163... |
Line 165... |
| 163 |
+ " - reloading. Cause: " + e.getMessage());
|
165 |
+ " - reloading. Cause: " + e.getMessage());
|
| 164 |
driver.get("https://support.oppo.com/in/warranty-check/");
|
166 |
driver.get("https://support.oppo.com/in/warranty-check/");
|
| 165 |
installResponseCapture(driver);
|
167 |
installResponseCapture(driver);
|
| 166 |
break;
|
168 |
break;
|
| 167 |
}
|
169 |
}
|
| - |
|
170 |
Double ringPageX = pieceCentreX(driver);
|
| - |
|
171 |
double[] circles = null;
|
| - |
|
172 |
if (ringPageX == null) {
|
| - |
|
173 |
LOGGER.warn("No ring element to anchor detection on");
|
| - |
|
174 |
} else {
|
| - |
|
175 |
int bgLeft = driver.findElement(
|
| - |
|
176 |
By.xpath("//*[starts-with(@id, 'dx_captcha_basic_bg_')]")).getLocation().getX();
|
| 168 |
double[] circles = getMatCircles2(driver, fileName);
|
177 |
circles = getMatCircles2(driver, fileName, ringPageX - bgLeft);
|
| - |
|
178 |
}
|
| 169 |
if (circles != null) {
|
179 |
if (circles != null) {
|
| 170 |
double firstCircleX = circles[0];
|
180 |
double firstCircleX = circles[0];
|
| 171 |
double secondCircleX = circles[1];
|
181 |
double secondCircleX = circles[1];
|
| 172 |
double distance = Math.abs(firstCircleX - secondCircleX);
|
182 |
double distance = Math.abs(firstCircleX - secondCircleX);
|
| 173 |
LOGGER.info("Distance is " + distance);
|
183 |
LOGGER.info("Distance is " + distance);
|
| Line 618... |
Line 628... |
| 618 |
Mat circles = new Mat();
|
628 |
Mat circles = new Mat();
|
| 619 |
Imgproc.HoughCircles(medianBlur, circles, Imgproc.HOUGH_GRADIENT, 1, 15, 50, 20, 17, 25);
|
629 |
Imgproc.HoughCircles(medianBlur, circles, Imgproc.HOUGH_GRADIENT, 1, 15, 50, 20, 17, 25);
|
| 620 |
return circles;
|
630 |
return circles;
|
| 621 |
}
|
631 |
}
|
| 622 |
|
632 |
|
| - |
|
633 |
/** Anything within this many px of the ring's known centre IS the ring, not the hole. */
|
| - |
|
634 |
private static final double RING_EXCLUSION_PX = 30;
|
| - |
|
635 |
/** A real hole sits at least this far below the frame's mean luminance. */
|
| - |
|
636 |
private static final double MIN_HOLE_DARKNESS = 15;
|
| - |
|
637 |
|
| - |
|
638 |
/** Mean luminance inside a detected circle, clamped to the image. */
|
| - |
|
639 |
private double meanInside(Mat gray, double[] circle) {
|
| - |
|
640 |
int r = (int) Math.max(3, circle[2] * 0.6); // inner core, avoids the rim
|
| - |
|
641 |
int x0 = (int) Math.max(0, circle[0] - r), y0 = (int) Math.max(0, circle[1] - r);
|
| - |
|
642 |
int x1 = (int) Math.min(gray.cols(), circle[0] + r), y1 = (int) Math.min(gray.rows(), circle[1] + r);
|
| - |
|
643 |
if (x1 - x0 < 2 || y1 - y0 < 2) return Double.MAX_VALUE;
|
| - |
|
644 |
return Core.mean(gray.submat(y0, y1, x0, x1)).val[0];
|
| - |
|
645 |
}
|
| - |
|
646 |
|
| - |
|
647 |
/**
|
| - |
|
648 |
* Locate the hole. The RING is not detected -- its exact position comes from the DOM
|
| - |
|
649 |
* (dx_captcha_basic_sub-slider_), so hough only has to find one thing instead of two.
|
| - |
|
650 |
*
|
| - |
|
651 |
* The old version demanded TWO circles whose radii matched within 3px and returned
|
| - |
|
652 |
* null otherwise, so a puzzle where the hole was found perfectly well still failed if
|
| - |
|
653 |
* the ring was missed or the radii differed by 4px. It was also re-detecting something
|
| - |
|
654 |
* already known exactly.
|
| - |
|
655 |
*
|
| - |
|
656 |
* param1 is the Canny HIGH threshold: edges weaker than it are discarded before circle
|
| - |
|
657 |
* finding begins. At 100 a low-contrast hole produces no edge at all. Measured on four
|
| - |
|
658 |
* live puzzles, hole-vs-background contrast was 98, 103, 28 and 117 -- the 28 (a pale
|
| - |
|
659 |
* lilac background) cannot survive 100, which is the mechanism behind "60% of attempts
|
| - |
|
660 |
* never find the circles". Dropped to 50.
|
| - |
|
661 |
*
|
| - |
|
662 |
* @param ringRelX ring centre in px relative to the bg element's left edge
|
| - |
|
663 |
* @return {ringRelX, holeRelX} -- shape kept so callers are unchanged -- or null
|
| - |
|
664 |
*/
|
| 623 |
private double[] getMatCircles2(WebDriver driver, String fileName) throws Exception {
|
665 |
private double[] getMatCircles2(WebDriver driver, String fileName, double ringRelX) throws Exception {
|
| 624 |
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
|
666 |
File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
|
| 625 |
BufferedImage fullImg = ImageIO.read(screenshot);
|
667 |
BufferedImage fullImg = ImageIO.read(screenshot);
|
| 626 |
|
668 |
|
| 627 |
// Get the location of element on the page
|
669 |
// Get the location of element on the page
|
| 628 |
WebElement imageElement = driver.findElement(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_bg_')]"));
|
670 |
WebElement imageElement = driver.findElement(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_bg_')]"));
|
| Line 653... |
Line 695... |
| 653 |
Imgproc.GaussianBlur(gray, gray, new Size(9, 9), 2, 2);
|
695 |
Imgproc.GaussianBlur(gray, gray, new Size(9, 9), 2, 2);
|
| 654 |
|
696 |
|
| 655 |
Mat circles = new Mat();
|
697 |
Mat circles = new Mat();
|
| 656 |
Imgproc.HoughCircles(gray, circles, Imgproc.HOUGH_GRADIENT, 1.0,
|
698 |
Imgproc.HoughCircles(gray, circles, Imgproc.HOUGH_GRADIENT, 1.0,
|
| 657 |
20, // min distance between centers
|
699 |
20, // min distance between centers
|
| 658 |
100, 20, // param1 (Canny high threshold), param2 (accumulator threshold)
|
700 |
50, 20, // param1 (Canny high threshold), param2 (accumulator threshold)
|
| 659 |
10, 50); // minRadius, maxRadius (adjust as needed)
|
701 |
10, 50); // minRadius, maxRadius
|
| 660 |
|
702 |
|
| 661 |
if (circles.cols() < 2) {
|
703 |
if (circles.cols() < 1) {
|
| 662 |
new File(fileName).delete();
|
704 |
new File(fileName).delete();
|
| 663 |
return null;
|
705 |
return null;
|
| 664 |
}
|
706 |
}
|
| 665 |
|
707 |
|
| - |
|
708 |
// Dropping param1 to 50 lets weak edges through, which also lets textured
|
| - |
|
709 |
// backgrounds (the sand images especially) produce spurious circles. Without the
|
| - |
|
710 |
// old two-circle pairing there is nothing else to reject them, so verify the
|
| - |
|
711 |
// candidate is actually a HOLE: it must be materially darker than the picture as
|
| - |
|
712 |
// a whole. Measured on live puzzles the hole runs 28-117 luminance below the
|
| - |
|
713 |
// median, so 15 rejects noise while keeping even the faintest real hole.
|
| - |
|
714 |
Scalar frameMean = Core.mean(gray);
|
| - |
|
715 |
// OpenCV returns circles strongest-accumulator first, so take the first candidate
|
| - |
|
716 |
// that is not the ring and does look like a hole.
|
| 666 |
for (int i = 0; i < circles.cols(); i++) {
|
717 |
for (int i = 0; i < circles.cols(); i++) {
|
| 667 |
double[] c1 = circles.get(0, i);
|
718 |
double[] c = circles.get(0, i);
|
| 668 |
for (int j = i + 1; j < circles.cols(); j++) {
|
719 |
if (Math.abs(c[0] - ringRelX) < RING_EXCLUSION_PX) continue; // that is the ring
|
| 669 |
double[] c2 = circles.get(0, j);
|
720 |
double darkness = frameMean.val[0] - meanInside(gray, c);
|
| 670 |
if (Math.abs(c1[2] - c2[2]) < 3) { // check similar radius
|
- |
|
| 671 |
new File(fileName).delete();
|
721 |
if (darkness < MIN_HOLE_DARKNESS) {
|
| 672 |
return new double[] {
|
722 |
LOGGER.info("Rejecting circle at {}px: only {} darker than the frame",
|
| 673 |
c1[0], c2[0] // x-coordinates
|
723 |
Math.round(c[0]), Math.round(darkness));
|
| 674 |
};
|
724 |
continue;
|
| 675 |
}
|
- |
|
| 676 |
}
|
725 |
}
|
| - |
|
726 |
new File(fileName).delete();
|
| - |
|
727 |
return new double[] { ringRelX, c[0] };
|
| 677 |
}
|
728 |
}
|
| 678 |
|
729 |
|
| - |
|
730 |
LOGGER.info("Detected {} circle(s) but all sat on the ring at {}px", circles.cols(), Math.round(ringRelX));
|
| 679 |
new File(fileName).delete();
|
731 |
new File(fileName).delete();
|
| 680 |
return null;
|
732 |
return null;
|
| 681 |
}
|
733 |
}
|
| 682 |
|
734 |
|
| 683 |
|
735 |
|