Base
This commit is contained in:
3
seed-counter-desktop/README.txt
Normal file
3
seed-counter-desktop/README.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
TODO:
|
||||
Закончить рефакторинг с введением PlanarObject, WritableData и ImageArea интерфейсов.
|
||||
Удалить или пересмотреть различные Main-классы запуска SeedCounter-а.
|
||||
50
seed-counter-desktop/build.gradle
Normal file
50
seed-counter-desktop/build.gradle
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*
|
||||
* This generated file contains a sample Java application project to get you started.
|
||||
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
|
||||
* User Manual available at https://docs.gradle.org/7.5/userguide/building_java_projects.html
|
||||
*/
|
||||
|
||||
plugins {
|
||||
// Apply the application plugin to add support for building a CLI application in Java.
|
||||
id 'application'
|
||||
id 'org.springframework.boot' version '2.7.2'
|
||||
id 'io.spring.dependency-management' version '1.0.12.RELEASE'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
compileJava.options.encoding = 'UTF-8'
|
||||
compileTestJava.options.encoding = 'UTF-8'
|
||||
|
||||
|
||||
repositories {
|
||||
// Use Maven Central for resolving dependencies.
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
|
||||
|
||||
implementation 'commons-io:commons-io:2.6'
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'com.googlecode.json-simple:json-simple:1.1.1'
|
||||
|
||||
implementation("org.openpnp:opencv:3.4.2-0")
|
||||
|
||||
implementation project(":mask-color-descriptors")
|
||||
implementation project(":utils")
|
||||
}
|
||||
|
||||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass = 'org.wheatdb.seedcounter.desktop.DesktopMainWithColorDescriptorsOneThread'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
// Use JUnit Platform for unit tests.
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package org.wheatdb.imgproc.colorchecker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.fileutils.FileUtilities;
|
||||
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
|
||||
import ru.delkom07.workspaces.SimpleWorkspace.OpUnit;
|
||||
import ru.delkom07.workspaces.StatusWorkspace;
|
||||
|
||||
public class ColorCheckerCDCalculation {
|
||||
private static final String SEPARATOR = ";";
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
File inputDir = new File(args[0]);
|
||||
File outputDir = new File(args[1]);
|
||||
|
||||
StatusWorkspace workspace = new StatusWorkspace(inputDir, outputDir, true, FileUtilities.jpgImageFilter, null);
|
||||
List<OpUnit> inputAndOutputs = workspace.getRawOpUnits();
|
||||
|
||||
File ccDataFile = new File(outputDir, "colorcheckersign.csv");
|
||||
File wsDataFile = new File(outputDir, "whitesquaresign.csv");
|
||||
|
||||
|
||||
try (
|
||||
PrintWriter ccWriter = new PrintWriter(ccDataFile);
|
||||
PrintWriter wsWriter = new PrintWriter(wsDataFile);
|
||||
) {
|
||||
ccWriter.print("File name" + SEPARATOR);
|
||||
wsWriter.println("File name" + SEPARATOR);
|
||||
|
||||
printHeader(ccWriter, SEPARATOR);
|
||||
|
||||
System.out.println("Total imgs amount: " + inputAndOutputs.size() + "\n");
|
||||
|
||||
int count = 1;
|
||||
for(OpUnit inputAndOutput : inputAndOutputs) {
|
||||
File inputFile = inputAndOutput.getIn();
|
||||
File outputFile = inputAndOutput.getOut();
|
||||
|
||||
Mat inputImg = null;
|
||||
try {
|
||||
inputImg = Imgcodecs.imread(inputFile.getCanonicalPath());
|
||||
|
||||
System.out.println(count++ + " " + inputFile.getCanonicalPath() + "... ");
|
||||
|
||||
MyColorChecker myColorChecker = new MyColorChecker(inputImg);
|
||||
|
||||
ColorCheckerSign sign = myColorChecker.getSignFrom(inputImg);
|
||||
sign.printColorDescriptors(outputFile, inputFile.getName(), ccWriter, SEPARATOR);
|
||||
|
||||
Quad ccQuad = myColorChecker.getColorChecherQuad();
|
||||
|
||||
Rect whiteSquareRect = new Rect((int)ccQuad.tr().x+100, (int)ccQuad.tr().y-100, 300, 300);
|
||||
printAddWhiteBackgroundSquare(inputFile.getName(), inputImg, whiteSquareRect, wsWriter, SEPARATOR);
|
||||
|
||||
Mat extractedCCImg = sign.getColorCheckerImg();
|
||||
|
||||
sign.drawRects(extractedCCImg);
|
||||
Mat whiteSquare = inputImg.submat(whiteSquareRect);
|
||||
|
||||
|
||||
Mat submat = extractedCCImg.submat(new Rect(0, 0, whiteSquareRect.width, whiteSquareRect.height));
|
||||
whiteSquare.copyTo(submat);
|
||||
|
||||
Imgcodecs.imwrite(outputFile.getCanonicalPath(), inputImg);
|
||||
Imgcodecs.imwrite(outputFile.getCanonicalPath().replace(".jpg", "_cc.png"), extractedCCImg);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("error. File was skipped.");
|
||||
} finally {
|
||||
inputImg.release();
|
||||
}
|
||||
|
||||
ccWriter.flush();
|
||||
wsWriter.flush();
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
fnfe.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static void printAddWhiteBackgroundSquare(String fileName, Mat img, Rect rect, PrintWriter writer, String separator) {
|
||||
ColorCheckerSample addWhiteSquareSample = new ColorCheckerSample(img, rect);
|
||||
|
||||
for(int i=0; i<ColorCheckerSample.PARTS_AMOUNT; i++) {
|
||||
Mat samplePartBGR = addWhiteSquareSample.getSampleParts()[i];
|
||||
|
||||
Mat subMatRGB = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatRGB, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Mat subMatHSV = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatHSV, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat subMatLab = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatLab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
Mat subMatYCrCb = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatYCrCb, Imgproc.COLOR_BGR2YCrCb);
|
||||
|
||||
|
||||
// Mean color descriptor:
|
||||
int rejectionSigma = 3;
|
||||
MeanColorDescriptor meanColorRGB = new MeanColorDescriptor(rejectionSigma).calculate(subMatRGB);
|
||||
MeanColorDescriptor meanColorHSV = new MeanColorDescriptor(rejectionSigma).calculate(subMatHSV);
|
||||
MeanColorDescriptor meanColorLab = new MeanColorDescriptor(rejectionSigma).calculate(subMatLab);
|
||||
MeanColorDescriptor meanColorYCrCb = new MeanColorDescriptor(rejectionSigma).calculate(subMatYCrCb);
|
||||
|
||||
writer.print(fileName + separator);
|
||||
writer.print(meanColorRGB.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorHSV.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorLab.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorYCrCb.toTsvCsvString(separator) + separator);
|
||||
writer.println();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void printHeader(PrintWriter writer, String separator) {
|
||||
|
||||
for(int i=0; i<ColorCheckerSign.PALITRA_HEIGHT*ColorCheckerSign.PALITRA_WIDTH; i++) {
|
||||
int number = i+1;
|
||||
writer.print("RGB_mR_" + number + separator);
|
||||
writer.print("RGB_mG_" + number + separator);
|
||||
writer.print("RGB_mB_" + number + separator);
|
||||
writer.print("RGB_v_" + number + separator);
|
||||
}
|
||||
|
||||
for(int i=0; i<ColorCheckerSign.PALITRA_HEIGHT*ColorCheckerSign.PALITRA_WIDTH; i++) {
|
||||
int number = i+1;
|
||||
writer.print("HSV_mH_" + number + separator);
|
||||
writer.print("HSV_mS_" + number + separator);
|
||||
writer.print("HSV_mV_" + number + separator);
|
||||
writer.print("HSV_v_" + number + separator);
|
||||
}
|
||||
|
||||
for(int i=0; i<ColorCheckerSign.PALITRA_HEIGHT*ColorCheckerSign.PALITRA_WIDTH; i++) {
|
||||
int number = i+1;
|
||||
writer.print("Lab_mL_" + number + separator);
|
||||
writer.print("Lab_ma_" + number + separator);
|
||||
writer.print("Lab_mb_" + number + separator);
|
||||
writer.print("Lab_v_" + number + separator);
|
||||
}
|
||||
|
||||
for(int i=0; i<ColorCheckerSign.PALITRA_HEIGHT*ColorCheckerSign.PALITRA_WIDTH; i++) {
|
||||
int number = i+1;
|
||||
writer.print("YCrCb_mY_" + number + separator);
|
||||
writer.print("YCrCb_mCr_" + number + separator);
|
||||
writer.print("YCrCb_mCb_" + number + separator);
|
||||
writer.print("YCrCb_v_" + number + separator);
|
||||
}
|
||||
|
||||
writer.println();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.wheatdb.imgproc.colorchecker;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Size;
|
||||
|
||||
class ColorCheckerSample {
|
||||
private static final int SPLIT = 4;
|
||||
public static final int PARTS_AMOUNT = SPLIT * SPLIT;
|
||||
|
||||
private Rect[] samplePartsRects = new Rect[PARTS_AMOUNT];
|
||||
private Mat[] sampleParts = new Mat[PARTS_AMOUNT];
|
||||
|
||||
ColorCheckerSample(Mat img, Rect rect) {
|
||||
for(int i=0; i<SPLIT; i++) {
|
||||
for(int j=0; j<SPLIT; j++) {
|
||||
int xStep = rect.width / SPLIT;
|
||||
int yStep = rect.height / SPLIT;
|
||||
|
||||
int num = i*SPLIT + j;
|
||||
samplePartsRects[num] = new Rect(new Point(rect.tl().x + xStep*j, rect.tl().y + yStep*i), new Size(xStep, yStep));
|
||||
|
||||
Rect roi = samplePartsRects[num];
|
||||
if(roi.x + roi.width > img.cols() || roi.y + roi.height > img.rows()) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
|
||||
sampleParts[num] = img.submat(samplePartsRects[num]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect[] getRects() {
|
||||
return samplePartsRects;
|
||||
}
|
||||
|
||||
Mat[] getSampleParts() {
|
||||
return sampleParts;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
//colors[(i*SPLIT) + j] = getMiddleColor(img, new Point(tl.x + xStep*j, tl.y + yStep*i),
|
||||
// new Point(tl.x + xStep*(j+1), tl.y + yStep*(i+1)));
|
||||
|
||||
private double[] getMiddleColor(Mat img, Point tl, Point br) {
|
||||
double[] middle = new double[] {0, 0, 0};
|
||||
|
||||
int count = 0;
|
||||
for(int x = (int)tl.x; x<br.x; x++) {
|
||||
for(int y = (int)tl.y; y<br.y; y++) {
|
||||
double[] pix = img.get(x, y);
|
||||
middle[0] += pix[0];
|
||||
middle[1] += pix[1];
|
||||
middle[2] += pix[2];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
middle[0] = middle[0] / (double) count;
|
||||
middle[1] = middle[1] / (double) count;
|
||||
middle[2] = middle[2] / (double) count;
|
||||
|
||||
return middle;
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.wheatdb.imgproc.colorchecker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
|
||||
|
||||
/**
|
||||
* ColorCheckerSign -
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class ColorCheckerSign {
|
||||
public static final int PALITRA_WIDTH = 4;
|
||||
public static final int PALITRA_HEIGHT = 6;
|
||||
|
||||
private static final int SAMPLE_WIDTH = 220;
|
||||
private static final int SAMPLE_HEIGHT = 220;
|
||||
|
||||
ColorCheckerSample[] samples = new ColorCheckerSample[PALITRA_WIDTH*PALITRA_HEIGHT]; // 24
|
||||
|
||||
private static Point baseTl = new Point(120, 295);
|
||||
|
||||
private static final Point[][] tlSamplePoints = new Point[][]{
|
||||
new Point[]{
|
||||
new Point(0, 0), new Point(393, 0), new Point(783, 0), new Point(1171, 0)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 396), new Point(393, 396), new Point(783, 396), new Point(1171, 396)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 782), new Point(393, 782), new Point(783, 782), new Point(1171, 782)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 1172), new Point(393, 1172), new Point(783, 1172), new Point(1171, 1172)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 1558), new Point(393, 1558), new Point(783, 1558), new Point(1171, 1558)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 1946), new Point(393, 1946), new Point(783, 1946), new Point(1171, 1946)
|
||||
}
|
||||
};
|
||||
|
||||
// point position correction by baseTl
|
||||
static {
|
||||
for(int i=0; i<PALITRA_HEIGHT; i++) {
|
||||
for(int j=0; j<PALITRA_WIDTH; j++) {
|
||||
tlSamplePoints[i][j] = new Point(tlSamplePoints[i][j].x + baseTl.x, tlSamplePoints[i][j].y + baseTl.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mat colorCheckerImg = null;
|
||||
|
||||
|
||||
public ColorCheckerSign(Mat colorCheckerImg) {
|
||||
this.colorCheckerImg = colorCheckerImg;
|
||||
|
||||
for(int i=0; i<PALITRA_HEIGHT; i++) {
|
||||
for(int j=0; j<PALITRA_WIDTH; j++) {
|
||||
Point tl = tlSamplePoints[i][j];
|
||||
samples[i*PALITRA_WIDTH + j] = new ColorCheckerSample(colorCheckerImg, new Rect(tl, new Point(tl.x+SAMPLE_WIDTH, tl.y+SAMPLE_HEIGHT)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawRects(Mat img) {
|
||||
for(int i=0; i<samples.length; i++) {
|
||||
ColorCheckerSample sample = samples[i];
|
||||
|
||||
Rect[] rects = sample.getRects();
|
||||
for(Rect rect : rects) {
|
||||
Imgproc.rectangle(img,
|
||||
rect.tl(), rect.br(),
|
||||
//new Point(from.x + rect.tl().x, from.y + rect.tl().y),
|
||||
//new Point(from.x + rect.br().x, from.y + rect.br().y),
|
||||
new Scalar(255, 0, 0), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void printColorDescriptors(File file, String fileName, PrintWriter writer, String separator) {
|
||||
Mat debugRGB = new Mat(new Size(3000, 3500), CvType.CV_8UC3);
|
||||
|
||||
for(int i=0; i<ColorCheckerSample.PARTS_AMOUNT; i++) {
|
||||
|
||||
writer.print(fileName + separator);
|
||||
|
||||
for(ColorCheckerSample sample : samples) {
|
||||
Mat samplePartBGR = sample.getSampleParts()[i];
|
||||
|
||||
Mat subMatRGB = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatRGB, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Mat subMatHSV = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatHSV, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat subMatLab = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatLab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
Mat subMatYCrCb = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatYCrCb, Imgproc.COLOR_BGR2YCrCb);
|
||||
|
||||
|
||||
// Mean color descriptor:
|
||||
int rejectionSigma = 3;
|
||||
MeanColorDescriptor meanColorRGB = new MeanColorDescriptor(rejectionSigma).calculate(subMatRGB);
|
||||
MeanColorDescriptor meanColorHSV = new MeanColorDescriptor(rejectionSigma).calculate(subMatHSV);
|
||||
MeanColorDescriptor meanColorLab = new MeanColorDescriptor(rejectionSigma).calculate(subMatLab);
|
||||
MeanColorDescriptor meanColorYCrCb = new MeanColorDescriptor(rejectionSigma).calculate(subMatYCrCb);
|
||||
|
||||
writer.print(meanColorRGB.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorHSV.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorLab.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorYCrCb.toTsvCsvString(separator) + separator);
|
||||
|
||||
double[] color = meanColorRGB.getMeanColorWithoutSigma();
|
||||
Rect rect = sample.getRects()[i];
|
||||
Imgproc.rectangle(debugRGB, rect.tl(), rect.br(), new Scalar(color[0], color[1], color[2]), -1);
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
|
||||
Mat debugBGR = new Mat();
|
||||
Imgproc.cvtColor(debugRGB, debugBGR, Imgproc.COLOR_RGB2BGR);
|
||||
try {
|
||||
Imgcodecs.imwrite(file.getCanonicalPath().replace(".jpg", "_mc.png"), debugBGR);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Mat getColorCheckerImg() {
|
||||
return colorCheckerImg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
private final Point[][] tlSamplePoints = new Point[][]{
|
||||
new Point[]{
|
||||
new Point(82, 276), new Point(474, 276), new Point(864, 276), new Point(1253, 276)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 672), new Point(474, 672), new Point(864, 672), new Point(1253, 672)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 1058), new Point(474, 1058), new Point(864, 1058), new Point(1253, 1058)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 1448), new Point(474, 1448), new Point(864, 1448), new Point(1253, 1448)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 1834), new Point(474, 1834), new Point(864, 1834), new Point(1253, 1834)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 2222), new Point(474, 2222), new Point(864, 2222), new Point(1253, 2222)
|
||||
}
|
||||
};
|
||||
*/
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.wheatdb.imgproc.colorchecker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.features2d.AKAZE;
|
||||
import org.opencv.features2d.DescriptorMatcher;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
import smirnov.colorchecker.ColorChecker;
|
||||
import smirnov.colorchecker.FindColorChecker;
|
||||
import smirnov.colorchecker.MatchingModel;
|
||||
import smirnov.regression.ColorSpace;
|
||||
import smirnov.regression.RegressionFactory;
|
||||
import smirnov.regression.RegressionFactory.Order;
|
||||
import smirnov.regression.RegressionModel;
|
||||
|
||||
public class MyColorChecker {
|
||||
//private static final String REFERENCE_FILE = "src/main/resources/templates/color_checker/reference.png";
|
||||
private static final URL REFERENCE_FILE = ClassLoader.getSystemClassLoader().getResource("templates/color_checker/reference.png");
|
||||
|
||||
private static MatchingModel matchingModel = new MatchingModel(
|
||||
AKAZE.create(), AKAZE.create(),
|
||||
DescriptorMatcher.BRUTEFORCE_HAMMING, 0.80f
|
||||
);
|
||||
|
||||
private static RegressionModel model = RegressionFactory.createModel(Order.THIRD);
|
||||
|
||||
private Mat img = null;
|
||||
private Quad ccQuad = null;
|
||||
private ColorChecker colorChecker = null;
|
||||
private Mat extractedColorChecker = null;
|
||||
|
||||
public MyColorChecker(Mat img) throws IOException {
|
||||
this.img = img;
|
||||
FindColorChecker findColorChecker = new FindColorChecker(REFERENCE_FILE, matchingModel);
|
||||
ccQuad = findColorChecker.findBestFitColorChecker(img);
|
||||
|
||||
extractedColorChecker = ccQuad.getTransformedField(img);
|
||||
colorChecker = new ColorChecker(extractedColorChecker, ccQuad, false, true);
|
||||
}
|
||||
|
||||
|
||||
public Quad getColorChecherQuad() {
|
||||
return ccQuad;
|
||||
}
|
||||
|
||||
|
||||
public void fillColorChecker() {
|
||||
int borderSize = (int) (ccQuad.getBigSideSize()*0.1);
|
||||
MatOfPoint matOfPoint = new MatOfPoint(
|
||||
new Point(ccQuad.tl().x+borderSize, ccQuad.tl().y+borderSize),
|
||||
new Point(ccQuad.tr().x-borderSize, ccQuad.tr().y+borderSize),
|
||||
new Point(ccQuad.br().x-borderSize, ccQuad.br().y-borderSize),
|
||||
new Point(ccQuad.bl().x+borderSize, ccQuad.bl().y-borderSize)
|
||||
);
|
||||
Imgproc.fillConvexPoly(img, matOfPoint, new Scalar(255, 255, 255));
|
||||
}
|
||||
|
||||
public Pair<Mat, Double> calibrateImg() {
|
||||
return colorChecker.calibrate(img, model, ColorSpace.RGB, ColorSpace.RGB);
|
||||
}
|
||||
|
||||
public double getPixelArea() {
|
||||
return colorChecker.pixelArea();
|
||||
}
|
||||
|
||||
public double getPixPerMM() {
|
||||
return 1/Math.sqrt(colorChecker.pixelArea());
|
||||
}
|
||||
|
||||
public ColorCheckerSign getSignFrom(Mat img) {
|
||||
Mat extractedColorChecker = getTransformedField(img, ccQuad);
|
||||
|
||||
return new ColorCheckerSign(extractedColorChecker);
|
||||
}
|
||||
|
||||
|
||||
public Mat getTransformedField(Mat image, Quad ccQuad) {
|
||||
// Define the destination image
|
||||
Mat transformed = new Mat(2791, 1645, image.type());
|
||||
|
||||
// Corners of the destination image
|
||||
Point[] quad_pts = new Point[4];
|
||||
quad_pts[0] = new Point(0, 0);
|
||||
quad_pts[1] = new Point(transformed.cols(), 0);
|
||||
quad_pts[2] = new Point(transformed.cols(), transformed.rows());
|
||||
quad_pts[3] = new Point(0, transformed.rows());
|
||||
|
||||
// Get transformation matrix
|
||||
Mat transmtx = Imgproc.getPerspectiveTransform(new MatOfPoint2f(ccQuad.getPoints()),
|
||||
new MatOfPoint2f(quad_pts));
|
||||
|
||||
// Apply perspective transformation
|
||||
Imgproc.warpPerspective(image, transformed, transmtx, transformed.size());
|
||||
|
||||
return transformed;
|
||||
}
|
||||
|
||||
public Mat getExtractedColorChecker() {
|
||||
return extractedColorChecker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public interface ISaverLoader {
|
||||
void setData(Object obj);
|
||||
|
||||
void save(File toFile) throws IOException;
|
||||
|
||||
Object load(File fromFile) throws IOException, SAXException;
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import ru.delkom07.util.xml.JElement;
|
||||
import ru.delkom07.util.xml.JXMLRepresentable;
|
||||
|
||||
/**
|
||||
* Замер.
|
||||
* Класс представляющий единичный замер зерен с листа бумаги.
|
||||
* @author Komyshev.
|
||||
* v.0.3 (C)
|
||||
*/
|
||||
public class Measurement {
|
||||
private String name = ""; // Название серии измерения (опционально)
|
||||
private Date date; // Дата и время измерения
|
||||
private List<SeedDataContainer> listOfSeedData = new LinkedList<SeedDataContainer>(); // Список полученных данных
|
||||
private File fromSavedImageFile; // измерения получены из сохраненного изображения
|
||||
private SheetFormat sheetFormat; // формат использованного листа бумаги
|
||||
private boolean selected = false; // UI selection
|
||||
private Status status = Status.NONE;
|
||||
|
||||
private int numberOfTrash = -1;
|
||||
private int numberOfSeeds = -1;
|
||||
private int numberOfStuckTogether = -1;
|
||||
private int totalNumberOfStuckTogetherEst = -1;
|
||||
private int totalNumberOfSeedsEst = -1;
|
||||
|
||||
private boolean objsClassified = false;
|
||||
|
||||
|
||||
public enum Status {
|
||||
NONE, CALCULATED, DELAYED, FAILURE;
|
||||
|
||||
public static Status fromString(String str) {
|
||||
if(null == str)
|
||||
return null;
|
||||
|
||||
if(str.equals("NONE"))
|
||||
return NONE;
|
||||
|
||||
if(str.equals("CALCULATED"))
|
||||
return CALCULATED;
|
||||
|
||||
if(str.equals("DELAYED"))
|
||||
return DELAYED;
|
||||
|
||||
if(str.equals("FAILURE"))
|
||||
return FAILURE;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Measurement(){}
|
||||
|
||||
public Measurement(String name, Date date, File fromSavedImageFile, SheetFormat sheetFormat, Status status) {
|
||||
this.name = name;
|
||||
this.date = date;
|
||||
this.fromSavedImageFile = fromSavedImageFile;
|
||||
this.sheetFormat = sheetFormat;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Measurement(Date date, SeedDataContainer dataItem) {
|
||||
this.date = date;
|
||||
listOfSeedData.add(dataItem);
|
||||
}
|
||||
|
||||
public Measurement(String name, Date date, SeedDataContainer dataItem) {
|
||||
this(date, dataItem);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Measurement(Date date, List<SeedDataContainer> listOfData) {
|
||||
this.date = date;
|
||||
this.listOfSeedData = listOfData;
|
||||
}
|
||||
|
||||
public Measurement(String name, Date date, List<SeedDataContainer> listOfData) {
|
||||
this(date, listOfData);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получить название измерения.
|
||||
* @return название измерения.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Задать название измерения.
|
||||
* @param name - новое название измерения.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получить дату измерения.
|
||||
* @return - дата измерения.
|
||||
*/
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получить список данных о зернах.
|
||||
* @return - список данных о зернах.
|
||||
*/
|
||||
public List<SeedDataContainer> getListOfSeedData() {
|
||||
return listOfSeedData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получить список данных о зернах.
|
||||
* @return - список данных о зернах.
|
||||
*/
|
||||
public void setListOfSeedData(List<SeedDataContainer> listOfSeedData) {
|
||||
this.listOfSeedData = listOfSeedData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Добавить в список данных данные о зерне.
|
||||
* @param dataItem - данные о зерне.
|
||||
*/
|
||||
public void addSeedData(SeedDataContainer dataItem) {
|
||||
listOfSeedData.add(dataItem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set selection from UI.
|
||||
* @param selected
|
||||
*/
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this measurement was selected in UI.
|
||||
* @return
|
||||
*/
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set delayed flag to measurement. Such measurement will be calculated later.
|
||||
* @param
|
||||
*/
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns is delayed flag enabled.
|
||||
* @return - true if delayed is turn on or false otherwise.
|
||||
*/
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set saved local image file name which is used to obtain this measurement.
|
||||
* It is optional parameter which sets in case if measures was produced in background mode.
|
||||
* @param file - file object.
|
||||
*/
|
||||
public void setFromSavedImageFile(File file) {
|
||||
this.fromSavedImageFile = file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get name of saved local image file which is used to obtain this measurement (can be null).
|
||||
* @return - file object.
|
||||
*/
|
||||
public File getFromSavedImageFile() {
|
||||
return fromSavedImageFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get format of sheet used for measuring seeds.
|
||||
* @return instance of SheetFormat class.
|
||||
*/
|
||||
public SheetFormat getSheetFormat() {
|
||||
return sheetFormat;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set format of sheet used for measuring seeds.
|
||||
* @param sheetFormat - instance of SheetFormat class.
|
||||
*/
|
||||
public void setSheetFormat(SheetFormat sheetFormat) {
|
||||
this.sheetFormat = sheetFormat;
|
||||
}
|
||||
|
||||
|
||||
public int getNumberOfTrash() {
|
||||
return numberOfTrash;
|
||||
}
|
||||
|
||||
public int getNumberOfSeeds() {
|
||||
return numberOfSeeds;
|
||||
}
|
||||
|
||||
public int getNumberOfStuckTogether() {
|
||||
return numberOfStuckTogether;
|
||||
}
|
||||
|
||||
public int getTotalNumberOfStuckTogetherEst() {
|
||||
return totalNumberOfStuckTogetherEst;
|
||||
}
|
||||
|
||||
public int getTotalNumberOfSeedsEst() {
|
||||
return totalNumberOfSeedsEst;
|
||||
}
|
||||
|
||||
public boolean isObjsClassified() {
|
||||
return objsClassified;
|
||||
}
|
||||
|
||||
public void setNumberOfTrash(int numberOfTrash) {
|
||||
this.numberOfTrash = numberOfTrash;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNumberOfSeeds(int numberOfSeeds) {
|
||||
this.numberOfSeeds = numberOfSeeds;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNumberOfStuckTogether(int numberOfStuckTogether) {
|
||||
this.numberOfStuckTogether = numberOfStuckTogether;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTotalNumberOfStuckTogetherEst(int totalNumberOfStuckTogetherObjs) {
|
||||
this.totalNumberOfStuckTogetherEst = totalNumberOfStuckTogetherObjs;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTotalNumberOfSeedsEst(int totalNumberOfSeedsEst) {
|
||||
this.totalNumberOfSeedsEst = totalNumberOfSeedsEst;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Представитель JXML используется для экспорта измерений в XML файлы, а также для передачи в базу данных.
|
||||
* Поля fromSavedImageFile и SheetFormat не экспортируются, т.к. являются метаданными приложения.
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class Representablator implements JXMLRepresentable {
|
||||
@Override
|
||||
public JElement getJXMLRepresentation(Document doc) {
|
||||
JElement root = new JElement(doc, Names.JELEMENT_NAME);
|
||||
root.setAttribute(Names.NAME_NAME, name);
|
||||
root.setAttribute(Names.DATE_NAME, new SimpleDateFormat("MMMM d, yyyy HH:mm:ss", Locale.ENGLISH).format(date));
|
||||
|
||||
for(SeedData seedData : listOfSeedData) {
|
||||
SeedData.Representablator res = seedData.new Representablator();
|
||||
JElement seedDataJEl = res.getJXMLRepresentation(doc);
|
||||
root.appendChild(seedDataJEl);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public void recoveryFromJXML(JElement root) {
|
||||
listOfSeedData = new LinkedList<SeedDataContainer>();
|
||||
|
||||
name = root.getAttribute("name");
|
||||
try {
|
||||
date = new SimpleDateFormat("MMMM d, yyyy HH:mm:ss", Locale.ENGLISH).parse(root.getAttribute("date"));
|
||||
} catch (ParseException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
List<JElement> seedDataJEls = root.getAll(SeedData.Representablator.Names.JELEMENT_NAME);
|
||||
for(JElement seedDataEl : seedDataJEls) {
|
||||
SeedDataContainer seedData = new SeedDataContainer();
|
||||
SeedDataContainer.Representablator repr = seedData.new Representablator();
|
||||
repr.recoveryFromJXML(seedDataEl);
|
||||
|
||||
listOfSeedData.add(seedData);
|
||||
}
|
||||
}
|
||||
|
||||
public class Names {
|
||||
public static final String JELEMENT_NAME = "measurement";
|
||||
private static final String NAME_NAME = "name";
|
||||
private static final String DATE_NAME = "date";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Представитель в TSV используется для экспорта данных в TSV файлы.
|
||||
* Поля fromSavedImageFile и SheetFormat не экспортируются, т.к. являются метаданными приложения.
|
||||
* @author Komyshev.
|
||||
*/
|
||||
public class TVSRepresentablator {
|
||||
public String toTSV() {
|
||||
String caption = "Measurement\t" + name + "\t" + date + "\r\n";
|
||||
String result = caption;
|
||||
|
||||
boolean withHeader = true;
|
||||
for(SeedData seedData : listOfSeedData) {
|
||||
result = result.concat((seedData.new TVSRepresentablator()).toTSV(withHeader));
|
||||
withHeader = false;
|
||||
}
|
||||
|
||||
return result.concat("\r\n\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
|
||||
Measurement other = (Measurement) obj;
|
||||
if (!name.equals(other.name))
|
||||
return false;
|
||||
|
||||
if (!date.equals(other.date))
|
||||
return false;
|
||||
|
||||
if (!listOfSeedData.equals(other.listOfSeedData))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
public class SeedCounterException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SeedCounterException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SeedCounterException(String mes) {
|
||||
super(mes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import ru.delkom07.util.xml.JElement;
|
||||
import ru.delkom07.util.xml.JXMLRepresentable;
|
||||
|
||||
/**
|
||||
* Единица полученных данных об одном зерне в ходе измерения.
|
||||
* @author Komyshev.
|
||||
* v.0.6 (C)
|
||||
*/
|
||||
public class SeedData implements WritableData {
|
||||
private double length;
|
||||
private double width;
|
||||
private double height; // (optional)
|
||||
private double area;
|
||||
private double[] lengthWidthIntersectPoint = new double[2];
|
||||
private double[] massCenter = new double[2];
|
||||
private double DFMCTWLIP; //distance from mass center to width length intersect point
|
||||
|
||||
private double circularity;
|
||||
private double roundness;
|
||||
private double solidity;
|
||||
private double rugosity;
|
||||
private double MIMCCDR; // Maximum Inscribed Circle To Minimum Circumscribed Circle Ratio
|
||||
private double MICTEACR; // Maximum Inscribed Circle To Equivalent Area Circle Ratio
|
||||
|
||||
public enum ObjType {
|
||||
TRASH, SEED, STUCKTOGETHER
|
||||
}
|
||||
private ObjType objType;
|
||||
|
||||
public SeedData() {}
|
||||
|
||||
public SeedData(double length, double width) {
|
||||
this.length = length;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public SeedData(double length, double width, double height) {
|
||||
this.length = length;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public double getLenght() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public double getArea() {
|
||||
return area;
|
||||
}
|
||||
|
||||
public void setArea(double area) {
|
||||
this.area = area;
|
||||
}
|
||||
|
||||
public double[] getLengthWidthIntersectPoint() {
|
||||
return lengthWidthIntersectPoint;
|
||||
}
|
||||
|
||||
public void setLengthWidthIntersectPoint(double[] lengthWidthIntersectPoint) {
|
||||
this.lengthWidthIntersectPoint = lengthWidthIntersectPoint;
|
||||
}
|
||||
|
||||
public double[] getMassCenter() {
|
||||
return massCenter;
|
||||
}
|
||||
|
||||
public void setMassCenter(double[] massCenter) {
|
||||
this.massCenter = massCenter;
|
||||
}
|
||||
|
||||
public double getDistaceFromMassCenterToWidthLengthIntersectPoint() {
|
||||
return DFMCTWLIP;
|
||||
}
|
||||
|
||||
public void setDistaceFromMassCenterToWidthLengthIntersectPoint(double distaceFromMassCenterToWidthLengthIntersectPoint) {
|
||||
this.DFMCTWLIP = distaceFromMassCenterToWidthLengthIntersectPoint;
|
||||
}
|
||||
|
||||
|
||||
// Additional indexies
|
||||
// getters
|
||||
public double getCircularity() {
|
||||
return circularity;
|
||||
}
|
||||
|
||||
public double getRoundness() {
|
||||
return roundness;
|
||||
}
|
||||
|
||||
public double getSolidity() {
|
||||
return solidity;
|
||||
}
|
||||
|
||||
public double getRugosity() {
|
||||
return rugosity;
|
||||
}
|
||||
|
||||
public double getMaxInscribedMinCircumscribedCirclesDiametersRatio() {
|
||||
return MIMCCDR;
|
||||
}
|
||||
|
||||
public double getMaxInscribedCircleToEqAreaCircleRatio() {
|
||||
return MICTEACR;
|
||||
}
|
||||
|
||||
|
||||
// setters
|
||||
public void setCircularityIndex(double circularityIndex) {
|
||||
this.circularity = circularityIndex;
|
||||
}
|
||||
|
||||
public void setRoundness(double roundness) {
|
||||
this.roundness = roundness;
|
||||
}
|
||||
|
||||
public void setSolidity(double solidity) {
|
||||
this.solidity = solidity;
|
||||
}
|
||||
|
||||
public void setRugosity(double rugosity) {
|
||||
this.rugosity = rugosity;
|
||||
}
|
||||
|
||||
public void setMaxInscribedMinCircumscribedCirclesDiametersRatio(double maxInscribedMinCircumscribedCirclesDiametersRatio) {
|
||||
this.MIMCCDR = maxInscribedMinCircumscribedCirclesDiametersRatio;
|
||||
}
|
||||
|
||||
public void setMaxInscribedCircleToEqAreaCircleRatio(double MaxInscribedCircleToEqAreaCircleRatio) {
|
||||
this.MICTEACR = MaxInscribedCircleToEqAreaCircleRatio;
|
||||
}
|
||||
|
||||
public ObjType getObjType() {
|
||||
return objType;
|
||||
}
|
||||
|
||||
public void setObjType(ObjType objType) {
|
||||
this.objType = objType;
|
||||
}
|
||||
|
||||
// public static List<String> headers() {
|
||||
// var columnNames = new ArrayList<String>();
|
||||
//
|
||||
// columnNames.add("Length");
|
||||
// columnNames.add("Width");
|
||||
// columnNames.add("Area");
|
||||
// columnNames.add("Circularity");
|
||||
// columnNames.add("Roundness");
|
||||
// columnNames.add("Rugosity");
|
||||
// columnNames.add("Solidity");
|
||||
//
|
||||
// return columnNames;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Получить данные в списка в том же порядке, что и заголовки.
|
||||
* @return список значений в строковом представлении.
|
||||
*/
|
||||
@Override
|
||||
public List<String> data() {
|
||||
var data = new ArrayList<String>();
|
||||
|
||||
data.add(Double.toString(length));
|
||||
data.add(Double.toString(width));
|
||||
data.add(Double.toString(area));
|
||||
data.add(Double.toString(circularity));
|
||||
data.add(Double.toString(roundness));
|
||||
data.add(Double.toString(rugosity));
|
||||
data.add(Double.toString(solidity));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String separator) {
|
||||
return length + separator + width + separator + area + separator + circularity + separator + roundness + separator + rugosity + separator + solidity + separator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Представитель в JXML. Используется для экспорта данных в файлы формата XML, а также для передачи данных в базу данных.
|
||||
* @author Komyshev.
|
||||
*/
|
||||
class Representablator implements JXMLRepresentable {
|
||||
@Override
|
||||
public JElement getJXMLRepresentation(Document doc) {
|
||||
JElement root = new JElement(doc, Names.JELEMENT_NAME);
|
||||
root.setAttribute(Names.LENGTH_NAME, Double.toString(length));
|
||||
root.setAttribute(Names.WIDTH_NAME, Double.toString(width));
|
||||
//root.setAttribute(Names.HEIGHT_NAME, Double.toString(height));
|
||||
root.setAttribute(Names.AREA_NAME, Double.toString(area));
|
||||
root.setAttribute(Names.DFMCTWLIP_NAME, Double.toString(DFMCTWLIP));
|
||||
|
||||
root.setAttribute(Names.CIRCULARITY_INDEX_NAME, Double.toString(circularity));
|
||||
root.setAttribute(Names.ROUNDNESS_NAME, Double.toString(roundness));
|
||||
root.setAttribute(Names.SOLIDITY_NAME, Double.toString(solidity));
|
||||
root.setAttribute(Names.RUGOSITY_NAME, Double.toString(rugosity));
|
||||
root.setAttribute(Names.MIMCCDR_NAME, Double.toString(MIMCCDR));
|
||||
root.setAttribute(Names.MICTEACR_NAME, Double.toString(MICTEACR));
|
||||
|
||||
JElement lengthWidthIntersectPointJEl = new JElement(doc, Names.LENGTH_WIDTH_INTERSECT_POINT_NAME);
|
||||
root.appendChild(lengthWidthIntersectPointJEl);
|
||||
lengthWidthIntersectPointJEl.setAttribute(Names.X_COORDINATE_NAME, Double.toString(lengthWidthIntersectPoint[0]));
|
||||
lengthWidthIntersectPointJEl.setAttribute(Names.Y_COORDINATE_NAME, Double.toString(lengthWidthIntersectPoint[1]));
|
||||
|
||||
JElement massCenterPointJEl = new JElement(doc, Names.MASS_CENTER_NAME);
|
||||
root.appendChild(massCenterPointJEl);
|
||||
massCenterPointJEl.setAttribute(Names.X_COORDINATE_NAME, Double.toString(massCenter[0]));
|
||||
massCenterPointJEl.setAttribute(Names.Y_COORDINATE_NAME, Double.toString(massCenter[1]));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recoveryFromJXML(JElement root) {
|
||||
length = Double.parseDouble(root.getAttribute(Names.LENGTH_NAME));
|
||||
width = Double.parseDouble(root.getAttribute(Names.WIDTH_NAME));
|
||||
//height = Double.parseDouble(root.getAttribute(Names.HEIGHT_NAME));
|
||||
area = Double.parseDouble(root.getAttribute(Names.AREA_NAME));
|
||||
DFMCTWLIP = Double.parseDouble(root.getAttribute(Names.DFMCTWLIP_NAME));
|
||||
|
||||
circularity = Double.parseDouble(root.getAttribute(Names.CIRCULARITY_INDEX_NAME));
|
||||
roundness = Double.parseDouble(root.getAttribute(Names.ROUNDNESS_NAME));
|
||||
solidity = Double.parseDouble(root.getAttribute(Names.SOLIDITY_NAME));
|
||||
rugosity = Double.parseDouble(root.getAttribute(Names.RUGOSITY_NAME));
|
||||
MIMCCDR = Double.parseDouble(root.getAttribute(Names.MIMCCDR_NAME));
|
||||
MICTEACR = Double.parseDouble(root.getAttribute(Names.MICTEACR_NAME));
|
||||
|
||||
JElement lengthWidthIntersectPointJEl = root.get(Names.LENGTH_WIDTH_INTERSECT_POINT_NAME);
|
||||
lengthWidthIntersectPoint[0] = Double.parseDouble(lengthWidthIntersectPointJEl.getAttribute(Names.X_COORDINATE_NAME));
|
||||
lengthWidthIntersectPoint[1] = Double.parseDouble(lengthWidthIntersectPointJEl.getAttribute(Names.Y_COORDINATE_NAME));
|
||||
|
||||
JElement massCenterPointJEl = root.get(Names.MASS_CENTER_NAME);
|
||||
massCenter[0] = Double.parseDouble(massCenterPointJEl.getAttribute(Names.X_COORDINATE_NAME));
|
||||
massCenter[1] = Double.parseDouble(massCenterPointJEl.getAttribute(Names.Y_COORDINATE_NAME));
|
||||
}
|
||||
|
||||
class Names {
|
||||
public static final String JELEMENT_NAME = "seedData";
|
||||
private static final String LENGTH_NAME = "length";
|
||||
private static final String WIDTH_NAME = "width";
|
||||
//private static final String HEIGHT_NAME = "height";
|
||||
private static final String AREA_NAME = "area";
|
||||
private static final String LENGTH_WIDTH_INTERSECT_POINT_NAME = "lengthWidthIntersectPoint";
|
||||
private static final String MASS_CENTER_NAME = "massCenter";
|
||||
private static final String DFMCTWLIP_NAME = "dfmctwlip";
|
||||
private static final String X_COORDINATE_NAME = "x";
|
||||
private static final String Y_COORDINATE_NAME = "y";
|
||||
|
||||
private static final String CIRCULARITY_INDEX_NAME = "circularityIndex";
|
||||
private static final String ROUNDNESS_NAME = "roundness";
|
||||
private static final String SOLIDITY_NAME = "solidity";
|
||||
private static final String RUGOSITY_NAME = "rugosity";
|
||||
private static final String MIMCCDR_NAME = "mimccdr";
|
||||
private static final String MICTEACR_NAME = "micteacr";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Представитель в TSV. Используется для экспорта данных в файлы формата TSV.
|
||||
* @author Komyshev.
|
||||
*/
|
||||
class TVSRepresentablator {
|
||||
private String header = "Length\tWidth\tArea\tCircularity Index\tRoundness\tSolidity\tRugosity\t" +
|
||||
"Maximum inscribed circle to minimum circumscribed circle ratio\tMaximum inscribed circle to equivalent area circle ratio\t"+
|
||||
"Main axises intersection point X\tMain axises intersection point Y\tMass center X\tMass center Y\tDistance from mass center point to main axises intersection point\n";
|
||||
|
||||
public String toTSV(boolean first) {
|
||||
String output = length+"\t"+width+"\t"+area+"\t"+
|
||||
circularity+"\t"+roundness+"\t"+solidity+"\t"+rugosity+"\t"+MIMCCDR+"\t"+MICTEACR+"\t"+
|
||||
lengthWidthIntersectPoint[0]+"\t"+lengthWidthIntersectPoint[1]+"\t"+
|
||||
massCenter[0]+"\t"+massCenter[1]+"\t"+DFMCTWLIP+"\r\n";
|
||||
|
||||
if(first) {
|
||||
return header.concat(output);
|
||||
} else {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
|
||||
SeedData other = (SeedData) obj;
|
||||
if (length != other.length)
|
||||
return false;
|
||||
|
||||
if (width != other.width)
|
||||
return false;
|
||||
|
||||
//if (height != other.height)
|
||||
// return false;
|
||||
|
||||
if (area != other.area)
|
||||
return false;
|
||||
|
||||
if(circularity != other.circularity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(roundness != other.roundness) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(rugosity != other.rugosity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(solidity != other.solidity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(MIMCCDR != other.MIMCCDR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(MICTEACR != other.MICTEACR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lengthWidthIntersectPoint[0] != other.lengthWidthIntersectPoint[0]
|
||||
|| lengthWidthIntersectPoint[1] != other.lengthWidthIntersectPoint[1])
|
||||
return false;
|
||||
|
||||
if (massCenter[0] != other.massCenter[0]
|
||||
|| massCenter[1] != other.massCenter[1])
|
||||
return false;
|
||||
|
||||
if (DFMCTWLIP != other.DFMCTWLIP)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public class SeedDataContainer extends SeedData implements PlanarObject {
|
||||
private MatOfPoint contour = null;
|
||||
|
||||
public SeedDataContainer() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SeedDataContainer(MatOfPoint contour, double length, double width) {
|
||||
super(length, width);
|
||||
this.contour = contour;
|
||||
}
|
||||
|
||||
public SeedDataContainer(double length, double width) {
|
||||
super(length, width);
|
||||
}
|
||||
|
||||
public SeedDataContainer(double length, double width, double height) {
|
||||
super(length, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatOfPoint getContour() {
|
||||
return contour;
|
||||
}
|
||||
|
||||
public MatOfPoint2f getContourApprox() {
|
||||
Point[] contourArr = contour.toArray();
|
||||
MatOfPoint2f contour2f = new MatOfPoint2f(contourArr);
|
||||
double perimeter = Imgproc.arcLength(contour2f, true);
|
||||
|
||||
MatOfPoint2f approx2f = new MatOfPoint2f();
|
||||
Imgproc.approxPolyDP(contour2f, approx2f, perimeter*0.02, true);
|
||||
|
||||
return approx2f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.opencv.core.Size;
|
||||
|
||||
|
||||
/**
|
||||
* Formats of sheet for detecting seeds.
|
||||
* @author Komyshev
|
||||
* v.1.0
|
||||
*/
|
||||
public enum SheetFormat {
|
||||
A2, A3, A4, A5, // International standard
|
||||
Letter, Legal, // USA standard
|
||||
B4, B5, B6, // Japanese standard
|
||||
USER; // User settings
|
||||
|
||||
Size userSize = new Size(0, 0);
|
||||
|
||||
|
||||
/**
|
||||
* Returns size of this sheet format.
|
||||
* @return size of sheet format represented by this object.
|
||||
*/
|
||||
public Size getSize() {
|
||||
switch(this) {
|
||||
case A2: return new Size(420, 594);
|
||||
case A3: return new Size(297, 420);
|
||||
case A4: return new Size(210, 297);
|
||||
case A5: return new Size(148, 210);
|
||||
|
||||
case Letter: return new Size(216, 279);
|
||||
case Legal: return new Size(216, 356);
|
||||
|
||||
case B4: return new Size(257, 364);
|
||||
case B5: return new Size(182, 257);
|
||||
case B6: return new Size(128, 182);
|
||||
case USER: {
|
||||
if(null==userSize)
|
||||
throw new IllegalArgumentException("User sheet format must be with width and height arguments.");
|
||||
return userSize;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setUserSize(Size userSize) {
|
||||
if(!this.equals(USER))
|
||||
throw new IllegalArgumentException("Only user sheet may be setted with width and height.");
|
||||
this.userSize = userSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns big side size of this sheet format.
|
||||
* @return the big side size of sheet format represented by this object.
|
||||
*/
|
||||
public double getBigSideSize() {
|
||||
Size size = getSize();
|
||||
return size.height > size.width ? size.height: size.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns small side size of this sheet format.
|
||||
* @return the small side size of sheet format represented by this object.
|
||||
*/
|
||||
public double getSmallSideSize() {
|
||||
Size size = getSize();
|
||||
return size.height > size.width ? size.width : size.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parsed instance of SheedFormat from string.
|
||||
* @param string - source string.
|
||||
* @return parsed instance of SheedFormat from string.
|
||||
*/
|
||||
public static SheetFormat fromString(String string) {
|
||||
if(null == string)
|
||||
return null;
|
||||
|
||||
if("" == string)
|
||||
return null;
|
||||
|
||||
if(A2.toString().equals(string))
|
||||
return SheetFormat.A2;
|
||||
if(A3.toString().equals(string))
|
||||
return SheetFormat.A3;
|
||||
if(A4.toString().equals(string))
|
||||
return SheetFormat.A4;
|
||||
if(A5.toString().equals(string))
|
||||
return SheetFormat.A5;
|
||||
|
||||
if(Letter.toString().equals(string))
|
||||
return SheetFormat.Letter;
|
||||
if(Legal.toString().equals(string))
|
||||
return SheetFormat.Legal;
|
||||
|
||||
if(B4.toString().equals(string))
|
||||
return SheetFormat.B4;
|
||||
if(B5.toString().equals(string))
|
||||
return SheetFormat.B5;
|
||||
if(B6.toString().equals(string))
|
||||
return SheetFormat.B6;
|
||||
|
||||
StringTokenizer st = new StringTokenizer(string);
|
||||
if(st.hasMoreTokens())
|
||||
if(st.nextToken().equals("USER")) {
|
||||
double width = Double.parseDouble(st.nextToken());
|
||||
double height = Double.parseDouble(st.nextToken());
|
||||
|
||||
SheetFormat user = SheetFormat.USER;
|
||||
user.setUserSize(new Size(height, width));
|
||||
return user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if(this==USER) {
|
||||
String sizeStr = "";
|
||||
if(null!=userSize) sizeStr = userSize.height+" "+userSize.width;
|
||||
return "USER "+ sizeStr;
|
||||
}
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Данные, предназначенные для сохранения в файлы.
|
||||
*/
|
||||
public interface WritableData {
|
||||
|
||||
/**
|
||||
* Получить данные в виде списка.
|
||||
* @return список значений в строковом представлении.
|
||||
*/
|
||||
public List<String> data();
|
||||
|
||||
/**
|
||||
* Получить данные в виде строки с разделителями.
|
||||
* @param separator - раделители колонок в файлах (точка с запятой, знак табуляции и т.п.).
|
||||
* @return строковое представление данных.
|
||||
*/
|
||||
public String toString(String separator);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import ru.delkom07.util.xml.JElement;
|
||||
import ru.delkom07.util.xml.XMLDOMHandler;
|
||||
|
||||
public class XMLMapSaverLoader implements ISaverLoader {
|
||||
private Map<String, String> data;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void setData(Object data) {
|
||||
this.data = (Map<String, String>) data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(File toFile) throws IOException {
|
||||
Document document = XMLDOMHandler.newDocument();
|
||||
JElement root = new JElement(document, toFile.getName());
|
||||
document.appendChild(root.getElement());
|
||||
|
||||
for(Entry<String, String> entry : data.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
JElement element = new JElement(document, name);
|
||||
element.setTextContent(value);
|
||||
root.appendChild(element);
|
||||
}
|
||||
|
||||
XMLDOMHandler.writeTree(document, toFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> load(File fromFile) throws IOException, SAXException {
|
||||
Map<String, String> data = new HashMap<String, String>();
|
||||
Document document;
|
||||
|
||||
document = XMLDOMHandler.parse(fromFile);
|
||||
JElement root = new JElement(document.getDocumentElement());
|
||||
List<JElement> elements = root.getChildJElements();
|
||||
|
||||
for(JElement element : elements) {
|
||||
String name = element.getName();
|
||||
String value = element.getTextContent();
|
||||
|
||||
data.put(name, value);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import ru.delkom07.util.xml.JElement;
|
||||
import ru.delkom07.util.xml.XMLDOMHandler;
|
||||
|
||||
public class XMLSaverLoader implements ISaverLoader {
|
||||
JElement root;
|
||||
|
||||
@Override
|
||||
public void setData(Object obj) {
|
||||
root = (JElement) obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(File toFile) throws IOException {
|
||||
XMLDOMHandler.writeTree(root.getElement().getOwnerDocument(), toFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JElement load(File fromFile) {
|
||||
Document document = null;
|
||||
try {
|
||||
document = XMLDOMHandler.parse(fromFile);
|
||||
} catch (SAXException e) {
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Element root = document.getDocumentElement();
|
||||
if(null!=root)
|
||||
return new JElement(root);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AppPrint {
|
||||
|
||||
public static void print(String str) {
|
||||
System.out.println(str);
|
||||
}
|
||||
|
||||
public static void eprint(String str) {
|
||||
System.err.println(str);
|
||||
}
|
||||
|
||||
public static void printFilePath(File file) throws IOException {
|
||||
System.out.println(file.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.wheatdb.imgproc.colorchecker.MyColorChecker;
|
||||
|
||||
import ru.delkom07.fileutils.FileUtilities;
|
||||
import ru.delkom07.util.Pair;
|
||||
import ru.delkom07.workspaces.SimpleWorkspace.OpUnit;
|
||||
import ru.delkom07.workspaces.StatusWorkspace;
|
||||
|
||||
public class ColorCorrection {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
File inputDir = new File(args[0]);
|
||||
File outputDir = new File(args[1]);
|
||||
|
||||
StatusWorkspace workspace = new StatusWorkspace(inputDir, outputDir, true, FileUtilities.jpgImageFilter, null);
|
||||
List<OpUnit> inputAndOutputs = workspace.getRawOpUnits();
|
||||
|
||||
File dataFile = new File(outputDir, "data.csv");
|
||||
PrintWriter printWriter = new PrintWriter(dataFile);
|
||||
printWriter.println("File name;transformationDeviance");
|
||||
|
||||
System.out.println("Total imgs amount: " + inputAndOutputs.size() + "\n");
|
||||
|
||||
int count = 1;
|
||||
for(OpUnit inputAndOutput : inputAndOutputs) {
|
||||
File inputFile = inputAndOutput.getIn();
|
||||
File outputFile = inputAndOutput.getOut();
|
||||
|
||||
try {
|
||||
System.out.print(count++ + " " + inputFile.getCanonicalPath() + "... ");
|
||||
|
||||
Mat inputImg = Imgcodecs.imread(inputFile.getCanonicalPath());
|
||||
|
||||
MyColorChecker myColorChecker = new MyColorChecker(inputImg);
|
||||
|
||||
Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
|
||||
Mat calibrated = calibratedPair.getLeft();
|
||||
double transformationDeviance = calibratedPair.getRight();
|
||||
|
||||
System.out.println("D: " + transformationDeviance);
|
||||
printWriter.println(inputFile.getCanonicalPath() + ";" + transformationDeviance);
|
||||
printWriter.flush();
|
||||
|
||||
Imgcodecs.imwrite(outputFile.getCanonicalPath(), calibrated);
|
||||
} catch(Exception e) {
|
||||
System.err.println("error. File was skipped.");
|
||||
}
|
||||
}
|
||||
|
||||
printWriter.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
public enum ColorSpace {
|
||||
RGB, HSV, Lab, YCrCb;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.imgproc.colorchecker.MyColorChecker;
|
||||
import org.wheatdb.seedcounter.SheetFormat;
|
||||
import org.wheatdb.seedcounter.processor.DetectionProcessor;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
import org.wheatdb.seedcounter.server.DefaultFilesAndDirs;
|
||||
|
||||
import ru.delkom07.util.ArgOption;
|
||||
import ru.delkom07.util.CommandRepresentation;
|
||||
import ru.delkom07.util.IncorrectCmdException;
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
/**
|
||||
* It is obsolete?? Check it!
|
||||
* Main class of test application.
|
||||
*/
|
||||
public class DesktopMain {
|
||||
static class ImageFilter implements java.io.FileFilter {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
if(pathname.getName().matches(".+\\.(png|PNG|jpg|JPG|bmp|BMP)"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
try {
|
||||
// processing of command line
|
||||
List<ArgOption> optMap = new ArrayList<ArgOption>(2);
|
||||
optMap.add(new ArgOption("--out", "-o", false));
|
||||
optMap.add(new ArgOption("--help", "-h", true));
|
||||
|
||||
optMap.add(new ArgOption("--colorchecker", "-cc", true));
|
||||
optMap.add(new ArgOption("--calibrate", "-cl", true));
|
||||
optMap.add(new ArgOption("--withoutsheet", "-wh", true));
|
||||
|
||||
CommandRepresentation cmd =
|
||||
new CommandRepresentation(args, optMap, optMap.size(), 0, 2000, 0);
|
||||
|
||||
// command options
|
||||
if(cmd.containsOptionByName("--help"))
|
||||
usage("");
|
||||
|
||||
File outputDir = null;
|
||||
if(cmd.containsOptionByName("--out")) {
|
||||
|
||||
outputDir = new File(cmd.getOptionByName("--out").getValue());
|
||||
|
||||
if(!outputDir.isDirectory())
|
||||
throw new IncorrectCmdException("Option '--out' isn't a path to directory.");
|
||||
} else {
|
||||
outputDir = DefaultFilesAndDirs.outputDir;
|
||||
|
||||
if(outputDir.isFile()) {
|
||||
throw new IncorrectCmdException("Default output directory is existing file.");
|
||||
}
|
||||
|
||||
if(!outputDir.exists()) {
|
||||
outputDir.mkdir();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// preparing input files
|
||||
List<File> inputFiles = new LinkedList<File>();
|
||||
|
||||
String[] appArg = cmd.getAcceptedArgs();
|
||||
if(appArg.length != 0) {
|
||||
for(String inputPathStr : appArg) {
|
||||
File inputPath = new File(inputPathStr);
|
||||
if(!inputPath.exists())
|
||||
throw new IncorrectCmdException("Input file doesn't exists.");
|
||||
|
||||
if(inputPath.isDirectory()) {
|
||||
File[] files = inputPath.listFiles(new DesktopMain.ImageFilter());
|
||||
|
||||
for(File f : files) {
|
||||
inputFiles.add(f);
|
||||
}
|
||||
|
||||
} else {
|
||||
inputFiles.add(inputPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
File[] files = DefaultFilesAndDirs.inputDir.listFiles(new DesktopMain.ImageFilter());
|
||||
|
||||
for(File f : files) {
|
||||
inputFiles.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// processing input files
|
||||
DetectionProcessor detectProcessor = new DetectionProcessor(SheetFormat.A4);
|
||||
for(File inputImg : inputFiles) {
|
||||
Mat srcImg = Imgcodecs.imread(inputImg.getAbsolutePath());
|
||||
if(srcImg.empty())
|
||||
throw new IncorrectCmdException("Input file is not image.");
|
||||
|
||||
Mat region = srcImg;
|
||||
|
||||
if(!cmd.containsOptionByName("--withoutsheet")) {
|
||||
Quad quad = detectProcessor.detectQuad(srcImg);
|
||||
if(null==quad) {
|
||||
System.out.print("Input file:" + inputImg.getName()+". ");
|
||||
System.out.println("Sheet isn't found. Skipping image.");
|
||||
|
||||
continue;
|
||||
}
|
||||
region = detectProcessor.getTransformedField(srcImg, quad);
|
||||
}
|
||||
|
||||
List<PlanarObject> containers;
|
||||
|
||||
// if is colorchecker
|
||||
if(cmd.containsOptionByName("--colorchecker")) {
|
||||
MyColorChecker myColorChecker = new MyColorChecker(region);
|
||||
|
||||
// fill ColorChecker
|
||||
myColorChecker.fillColorChecker();
|
||||
|
||||
if(cmd.containsOptionByName("--calibrate")) {
|
||||
Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
region = calibratedPair.getLeft();
|
||||
Double transformationDeviance = calibratedPair.getRight();
|
||||
|
||||
System.out.println("# Transformation deviance: " + transformationDeviance.toString());
|
||||
}
|
||||
|
||||
containers = detectProcessor.detectSeedContours(region.clone(), myColorChecker.getPixPerMM());
|
||||
} else {
|
||||
containers = detectProcessor.detectSeedContours(region.clone());
|
||||
}
|
||||
|
||||
System.out.println("Seed count: " + containers.size());
|
||||
|
||||
String base = FilenameUtils.getBaseName(inputImg.getName());
|
||||
String extention = "tsv";
|
||||
File outputImgFile = new File(outputDir, base + "." + extention);
|
||||
|
||||
saveResults(outputImgFile, containers);
|
||||
|
||||
File outputFile = new File(outputDir, "output_" + inputImg.getName().replace(".jpg", ".png"));
|
||||
drawContoursWithRectAndNum(region, containers);
|
||||
Imgcodecs.imwrite(outputFile.getCanonicalPath(), region);
|
||||
}
|
||||
|
||||
} catch (IncorrectCmdException e) {
|
||||
System.err.println("Error: "+e.getMessage());
|
||||
usage(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error: "+e.getMessage());
|
||||
System.out.println("IO error. The program will be terminated");
|
||||
System.exit(0);
|
||||
} catch (SeedCounterProcessorException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static void drawContoursWithRectAndNum(Mat img, List<PlanarObject> containers) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
Rect rect = Imgproc.boundingRect(container.getContour());
|
||||
|
||||
Imgproc.rectangle(img, rect.tl(), rect.br(), new Scalar(255, 255, 0));
|
||||
Imgproc.drawContours(img, Arrays.asList(container.getContour()), -1, new Scalar(255, 255, 0), 1);
|
||||
Imgproc.putText(img, Integer.toString(i+1), rect.tl(), 1, 1, new Scalar(255, 255, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static void saveResults(File outputDataFile, List<PlanarObject> containers) throws IOException {
|
||||
PrintWriter writer = new PrintWriter(outputDataFile, "UTF-8");
|
||||
|
||||
int count = 1;
|
||||
writer.println("Number" + " " + "Length" + " " + "Width" + " " + "Area" + " " +
|
||||
"Circularity" + " " + "Roundness" + " " + "Rugosity" + " " + "Solidity");
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
writer.println(Integer.toString(count++) + " " + container.toString(" "));
|
||||
}
|
||||
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
private static void usage(String message) {
|
||||
System.out.println(message + "\n" + Messages.usage);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import static org.wheatdb.seedcounter.desktop.AppPrint.eprint;
|
||||
import static org.wheatdb.seedcounter.desktop.AppPrint.print;
|
||||
import static org.wheatdb.seedcounter.desktop.AppPrint.printFilePath;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.AddInfo;
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.ImageHandler;
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.ResultSaver;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.ex.QuadNotFoundException;
|
||||
import org.wheatdb.seedcounter.server.DefaultFilesAndDirs;
|
||||
|
||||
import nu.pattern.OpenCV;
|
||||
import ru.delkom07.util.ArgOption;
|
||||
import ru.delkom07.util.CommandRepresentation;
|
||||
import ru.delkom07.util.IncorrectCmdException;
|
||||
import ru.delkom07.workspaces.SimpleWorkspace.OpUnit;
|
||||
import ru.delkom07.workspaces.StatusWorkspace;
|
||||
|
||||
/**
|
||||
* Main class of SeedCounter desktop application.
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class DesktopMainWithColorDescriptorsOneThread {
|
||||
private static void handleOptions(CommandRepresentation cmd, RunConfiguration config) throws IncorrectCmdException {
|
||||
// command options
|
||||
if(cmd.containsOptionByName("--help"))
|
||||
usage("");
|
||||
|
||||
// input and output dirs
|
||||
config.inputDir = new File(cmd.getAcceptedArgs()[0]);
|
||||
|
||||
File outputDir = null;
|
||||
if(cmd.containsOptionByName("--out")) {
|
||||
outputDir = new File(cmd.getOptionByName("--out").getValue());
|
||||
|
||||
if(!outputDir.isDirectory())
|
||||
throw new IncorrectCmdException("Option '--out' isn't a path to directory.");
|
||||
} else {
|
||||
outputDir = DefaultFilesAndDirs.outputDir;
|
||||
|
||||
if(outputDir.isFile()) {
|
||||
throw new IncorrectCmdException("Default output directory is existing file.");
|
||||
}
|
||||
|
||||
if(!outputDir.exists()) {
|
||||
outputDir.mkdir();
|
||||
}
|
||||
}
|
||||
|
||||
config.outputDir = outputDir;
|
||||
|
||||
if(cmd.containsOptionByName("--colorchecker")) {
|
||||
config.colorchecker = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--calibrate")) {
|
||||
config.calibrate = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--withoutsheet")) {
|
||||
config.withoutsheet = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--colordescriptors")) {
|
||||
config.colordescriptors = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--texturedescriptors")) {
|
||||
config.texturedescriptors = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--draw")) {
|
||||
config.draw = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--markdescriptor")) {
|
||||
config.markdescriptor = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--debug")) {
|
||||
config.DEBUG_MODE = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--markdescriptor")) {
|
||||
try {
|
||||
String[] splited = cmd.getOptionByName("--markdescriptor").getValue().split("_");
|
||||
config.colorSpace = ColorSpace.valueOf(splited[0]);
|
||||
config.markGCHDimension = Integer.parseInt(splited[1].replace("GCH", ""));
|
||||
config.markGCHBinNumber = Integer.parseInt(splited[2]) - 1;
|
||||
} catch(Exception e) {
|
||||
throw new IncorrectCmdException("Incorrect MarkGCHdescriptor option error.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static CommandRepresentation init(String[] args) throws IncorrectCmdException {
|
||||
print("Start");
|
||||
print("Length: " + args.length);
|
||||
print(Arrays.toString(args));
|
||||
|
||||
// processing of command line
|
||||
List<ArgOption> optMap = new ArrayList<ArgOption>(2);
|
||||
optMap.add(new ArgOption("--help", "-h", true)); // print help
|
||||
optMap.add(new ArgOption("--out", "-o", false)); // set output directory
|
||||
|
||||
optMap.add(new ArgOption("--colorchecker", "-cc", true)); // image contains ColorChecker
|
||||
optMap.add(new ArgOption("--calibrate", "-cl", true)); // do color calibration with ColorChecker
|
||||
optMap.add(new ArgOption("--withoutsheet", "-ws", true)); // background is solid. standard sheet of paper is not used for scale definition.
|
||||
|
||||
optMap.add(new ArgOption("--colordescriptors", "-cd", true)); // calculate color descriptors
|
||||
optMap.add(new ArgOption("--texturedescriptors", "-td", true)); // calculate texture descriptors
|
||||
optMap.add(new ArgOption("--draw", "-dr", true)); // draw additional output images
|
||||
optMap.add(new ArgOption("--markdescriptor", "-md", false)); // mark HSV color descriptor on output image
|
||||
optMap.add(new ArgOption("--debug", "-dg", true)); // enable debug mode
|
||||
|
||||
return new CommandRepresentation(args, optMap, optMap.size(), 0, 2000, 0);
|
||||
}
|
||||
|
||||
|
||||
private static RunConfiguration initAppConfiguration(String[] args) throws IncorrectCmdException {
|
||||
SpringApplication.run(DesktopMainWithColorDescriptorsOneThread.class, args);
|
||||
@SuppressWarnings("resource")
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
|
||||
|
||||
RunConfiguration config = (RunConfiguration) context.getBean("config");
|
||||
CommandRepresentation cmd = init(args);
|
||||
handleOptions(cmd, config);
|
||||
|
||||
config.imageHandler = (ImageHandler) context.getBean("imageHandler");
|
||||
|
||||
if(config.colordescriptors && !config.texturedescriptors) {
|
||||
config.imageHandler.setResultSaver((ResultSaver) context.getBean("colorResultSaver"));
|
||||
} else
|
||||
if(!config.colordescriptors && config.texturedescriptors) {
|
||||
config.imageHandler.setResultSaver((ResultSaver) context.getBean("textureResultSaver"));
|
||||
} else
|
||||
if(config.colordescriptors && config.texturedescriptors) {
|
||||
config.imageHandler.setResultSaver((ResultSaver) context.getBean("colorTextureResultSaver"));
|
||||
}
|
||||
|
||||
// Additional info writing to output table
|
||||
config.info = (AddInfo) context.getBean("simpleAddInfo");
|
||||
config.imageHandler.getResultSaver().setInfo(config.info);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
OpenCV.loadLocally();
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
var config = initAppConfiguration(args);
|
||||
|
||||
String base = "output";
|
||||
String extention = "tsv";
|
||||
File outputDataFile = new File(config.outputDir, base + "." + extention);
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputDataFile, "UTF-8");
|
||||
|
||||
ResultSaver resultSaver = config.imageHandler.getResultSaver();
|
||||
resultSaver.writeFileHead(writer);
|
||||
writer.println();
|
||||
|
||||
StatusWorkspace workspace = new StatusWorkspace(config.inputDir, config.outputDir, true, null, null);
|
||||
List<OpUnit> opUnits = workspace.getRawOpUnits();
|
||||
|
||||
for(OpUnit unit : opUnits) {
|
||||
File inFile = unit.getIn();
|
||||
File outFile = unit.getOut();
|
||||
|
||||
printFilePath(inFile);
|
||||
|
||||
try {
|
||||
resultSaver.setInfo(config.info.from(inFile));
|
||||
|
||||
config.imageHandler.handleImage(inFile, outFile, writer);
|
||||
|
||||
print(inFile.getParent() + "/" + inFile.getName() + " done.");
|
||||
} catch(QuadNotFoundException e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("Sheet isn't found. Skipping image...");
|
||||
} catch (SeedCounterProcessorException e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("Inner error. Skipping image...");
|
||||
} catch (IOException e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("IO error. Skipping image...");
|
||||
} catch (SeedCounterException e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("Skipping image...");
|
||||
} catch (Exception e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Unknow exception: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
eprint("Skipping image...");
|
||||
} finally {
|
||||
workspace.saveProcessedOpUnit(unit);
|
||||
}
|
||||
}
|
||||
|
||||
writer.close();
|
||||
System.out.println("Computation time: " + Integer.toString((int)(System.currentTimeMillis() - time)/1000));
|
||||
System.exit(0); // STATUS - ok
|
||||
|
||||
} catch (IncorrectCmdException e) {
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("Command line error. The program will be terminated");
|
||||
usage(e.getMessage());
|
||||
System.exit(-1);
|
||||
} catch (IOException e) {
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("IO error. The program will be terminated");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void usage(String message) {
|
||||
System.out.println(message + "\n" + Messages.usage);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JFrame;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfByte;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
|
||||
public class Display extends JFrame {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static int screenHeight = 1030;
|
||||
private static int screenWidth = 1284;
|
||||
|
||||
private static int screenUpPadding = 100;
|
||||
private static int screenSidePaddint = 30;
|
||||
|
||||
private int windowHeight = screenHeight-screenUpPadding*2;
|
||||
private int windowWidth = screenWidth-screenSidePaddint*2;
|
||||
|
||||
private BufferedImage image = null;
|
||||
private static int imagePadding = 30;
|
||||
private int imageHeight;
|
||||
private int imageWidth;
|
||||
|
||||
|
||||
public Display() {
|
||||
super("Display...");
|
||||
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
|
||||
setSize(windowWidth, windowHeight);
|
||||
setLocation(screenSidePaddint, screenUpPadding);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
super.paint(g);
|
||||
//Graphics2D graphics = (Graphics2D) g;
|
||||
|
||||
if(null!=image) {
|
||||
double heightResize = ((getHeight()-imagePadding*2)/(double)image.getHeight());
|
||||
double widthResize = ((getWidth()-imagePadding*2)/(double)image.getWidth());
|
||||
double resize = heightResize < widthResize ? heightResize : widthResize;
|
||||
|
||||
imageHeight = (int) (image.getHeight()*resize);
|
||||
imageWidth = (int) (image.getWidth()*resize);
|
||||
|
||||
g.drawImage(image, imagePadding, imagePadding*2, imageWidth, imageHeight,
|
||||
0,0,image.getWidth(),image.getHeight(), new Color(10,255,0), null);
|
||||
}
|
||||
}
|
||||
|
||||
private static BufferedImage matToImage(Mat mat) {
|
||||
MatOfByte bytemat = new MatOfByte();
|
||||
Imgcodecs.imencode(".jpg", mat, bytemat);
|
||||
byte[] bytes = bytemat.toArray();
|
||||
InputStream in = new ByteArrayInputStream(bytes);
|
||||
BufferedImage img = null;
|
||||
try {
|
||||
img = ImageIO.read(in);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
public void setImage(Mat img) {
|
||||
image = matToImage(img);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage img) {
|
||||
image = img;
|
||||
repaint();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.wheatdb.seedcounter.SeedDataContainer;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public class JSONSaver {
|
||||
|
||||
public void saveInJSON(Mat region, List<PlanarObject> containers, File inFile, File outFile) throws IOException {
|
||||
PrintWriter jsonWriter = new PrintWriter(new File(outFile.getParent(), FilenameUtils.getBaseName(outFile.getName()) + ".json"), "UTF-8");
|
||||
|
||||
JSONObject jsonObj = new JSONObject();
|
||||
JSONArray shapes = new JSONArray();
|
||||
|
||||
jsonObj.put("version", "4.0.0");
|
||||
jsonObj.put("flags", new JSONObject());
|
||||
jsonObj.put("shapes", shapes);
|
||||
|
||||
jsonObj.put("imagePath", outFile.getName());
|
||||
jsonObj.put("imageData", null);
|
||||
jsonObj.put("imageHeight", region.height());
|
||||
jsonObj.put("imageWidth", region.width());
|
||||
|
||||
for(PlanarObject container : containers) {
|
||||
|
||||
JSONObject fly = new JSONObject();
|
||||
fly.put("label", "fly");
|
||||
fly.put("group_id", null);
|
||||
fly.put("shape_type", "polygon");
|
||||
fly.put("flags", new JSONObject());
|
||||
|
||||
JSONArray points = new JSONArray();
|
||||
Point[] contourPoints = container.getContourApprox().toArray();
|
||||
|
||||
for(Point point : contourPoints) {
|
||||
|
||||
|
||||
|
||||
JSONArray jsonPoint = new JSONArray();
|
||||
jsonPoint.add(point.x);
|
||||
jsonPoint.add(point.y);
|
||||
|
||||
points.add(jsonPoint);
|
||||
}
|
||||
|
||||
fly.put("points", points);
|
||||
|
||||
shapes.add(fly);
|
||||
}
|
||||
|
||||
try {
|
||||
jsonObj.writeJSONString(jsonWriter);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
jsonWriter.close();
|
||||
Imgcodecs.imwrite(outFile.getCanonicalPath(), region);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
public class Messages {
|
||||
|
||||
public static final String usage = "" +
|
||||
"Usage: java -jar seedCounter.jar [--out outputDir] inputImgsDir \n\n" +
|
||||
|
||||
"-o, --out set output directory\n" +
|
||||
"-cc, --colorchecker image contains ColorChecker\n" +
|
||||
"-cl, --calibrate do color calibration with ColorChecker\n" +
|
||||
"-ws, --withoutsheet background is solid. standard sheet of paper is not used for scale definition\n" +
|
||||
"-cd, --colordescriptors calculate color descriptors\n" +
|
||||
"-dr, --draw draw additional output images\n" +
|
||||
"-md, --markdescriptor mark HSV color descriptor on output image\n";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.AddInfo;
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.ImageHandler;
|
||||
|
||||
public class RunConfiguration {
|
||||
// options:
|
||||
public boolean colorchecker = false;
|
||||
public boolean calibrate = false;
|
||||
public boolean withoutsheet = false;
|
||||
public boolean colordescriptors = false;
|
||||
public boolean texturedescriptors = false;
|
||||
public boolean draw = false;
|
||||
public boolean markdescriptor = false;
|
||||
public boolean DEBUG_MODE = false;
|
||||
|
||||
// Mark GCH Descriptor
|
||||
public ColorSpace colorSpace = null;
|
||||
public int markGCHDimension = -1;
|
||||
public int markGCHBinNumber = -1;
|
||||
|
||||
public File inputDir;
|
||||
public File outputDir;
|
||||
|
||||
public ImageHandler imageHandler;
|
||||
public AddInfo info;
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.imgproc.colorchecker.MyColorChecker;
|
||||
import org.wheatdb.seedcounter.SheetFormat;
|
||||
import org.wheatdb.seedcounter.processor.DetectionProcessor;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.improc.color.descriptors.ColorLayoutDescriptor;
|
||||
import ru.delkom07.improc.color.descriptors.DominantColorDescriptor;
|
||||
import ru.delkom07.improc.color.descriptors.GlobalColorHistogram;
|
||||
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
|
||||
import ru.delkom07.util.CommandRepresentation;
|
||||
import ru.delkom07.util.IncorrectCmdException;
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
/**
|
||||
* It is obsolete?? Check it!
|
||||
* @author Komyshev
|
||||
*
|
||||
*/
|
||||
public class SeedCounterWCDThread implements Runnable {
|
||||
private static final String SEPARATOR = " ";
|
||||
|
||||
// processing input files
|
||||
private DetectionProcessor detectProcessor = new DetectionProcessor(SheetFormat.A4);
|
||||
private List<Pair<File, File>> inputAndOutputs;
|
||||
private PrintWriter writer;
|
||||
private CommandRepresentation cmd;
|
||||
|
||||
ExecutorService service;
|
||||
|
||||
public SeedCounterWCDThread(ExecutorService service, List<Pair<File, File>> inputAndOutputs, CommandRepresentation cmd, PrintWriter writer) {
|
||||
this.service = service;
|
||||
this.inputAndOutputs = inputAndOutputs;
|
||||
this.cmd = cmd;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for(Pair<File, File> pair : inputAndOutputs) {
|
||||
File inFile = pair.getLeft();
|
||||
File outFile = pair.getRight();
|
||||
|
||||
String group = inFile.getParentFile().getName();
|
||||
String[] splitedSampleDirName = inFile.getParentFile().getParentFile().getName().split(" ");
|
||||
String sampleName;
|
||||
String year;
|
||||
if(splitedSampleDirName.length == 3) {
|
||||
sampleName = splitedSampleDirName[0] + "_" + splitedSampleDirName[1];
|
||||
year = splitedSampleDirName[2];
|
||||
} else
|
||||
if(splitedSampleDirName.length == 2){
|
||||
sampleName = splitedSampleDirName[0] + "_" + splitedSampleDirName[1];
|
||||
year = "-";
|
||||
} else {
|
||||
sampleName = splitedSampleDirName[0];
|
||||
year = "-";
|
||||
}
|
||||
|
||||
String mixing = FilenameUtils.getBaseName(inFile.getName()).split("_")[0];
|
||||
String duplicate = FilenameUtils.getBaseName(inFile.getName()).split("_")[1];
|
||||
|
||||
System.out.println(inFile.getParent() + "/" + inFile.getName());
|
||||
|
||||
try {
|
||||
|
||||
Mat srcImg = Imgcodecs.imread(inFile.getAbsolutePath());
|
||||
if(srcImg.empty())
|
||||
throw new IncorrectCmdException("Input file is not image.");
|
||||
|
||||
Mat region = srcImg;
|
||||
|
||||
if(!cmd.containsOptionByName("--withoutsheet")) {
|
||||
Quad quad = detectProcessor.detectQuad(srcImg);
|
||||
if(null==quad) {
|
||||
System.out.print("Input file:" + inFile.getName()+". ");
|
||||
System.out.println("Sheet isn't found. Skipping image.");
|
||||
|
||||
continue;
|
||||
}
|
||||
region = detectProcessor.getTransformedField(srcImg, quad);
|
||||
}
|
||||
|
||||
List<PlanarObject> containers;
|
||||
|
||||
// if is colorchecker
|
||||
Double transformationDeviance = 0.0;
|
||||
if(cmd.containsOptionByName("--colorchecker")) {
|
||||
MyColorChecker myColorChecker = new MyColorChecker(region);
|
||||
|
||||
// fill ColorChecker
|
||||
myColorChecker.fillColorChecker();
|
||||
|
||||
containers = detectProcessor.detectSeedContours(region, myColorChecker.getPixPerMM());
|
||||
|
||||
if(cmd.containsOptionByName("--calibrate")) {
|
||||
Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
region = calibratedPair.getLeft();
|
||||
transformationDeviance = calibratedPair.getRight();
|
||||
|
||||
//System.out.println("# Transformation deviance: " + transformationDeviance.toString());
|
||||
}
|
||||
} else {
|
||||
containers = detectProcessor.detectSeedContours(region);
|
||||
}
|
||||
|
||||
//System.out.println("Seed count: " + containers.size());
|
||||
|
||||
saveResults(region, containers, writer, inFile.getName(), year, sampleName, group, mixing, duplicate, transformationDeviance);
|
||||
|
||||
|
||||
if(cmd.containsOptionByName("--draw")) {
|
||||
drawContoursWithRectAndNum(region, containers);
|
||||
Imgcodecs.imwrite(outFile.getCanonicalPath(), region);
|
||||
}
|
||||
|
||||
|
||||
// Releases
|
||||
for(PlanarObject container : containers) {
|
||||
container.getContour().release();
|
||||
}
|
||||
containers.clear();
|
||||
|
||||
if(!srcImg.empty()) {
|
||||
srcImg.release();
|
||||
}
|
||||
|
||||
if(!region.empty()) {
|
||||
region.release();
|
||||
}
|
||||
|
||||
System.out.println(inFile.getParent() + "/" + inFile.getName() + " done.");
|
||||
|
||||
} catch(IncorrectCmdException ex) {
|
||||
service.shutdown();
|
||||
} catch (SeedCounterProcessorException e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
service.shutdown();
|
||||
}
|
||||
|
||||
|
||||
private static void drawContoursWithRectAndNum(Mat img, List<PlanarObject> containers) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
Rect rect = Imgproc.boundingRect(container.getContour());
|
||||
|
||||
Imgproc.rectangle(img, rect.tl(), rect.br(), new Scalar(255, 255, 0));
|
||||
Imgproc.drawContours(img, Arrays.asList(container.getContour()), -1, new Scalar(255, 255, 0), 1);
|
||||
Imgproc.putText(img, Integer.toString(i+1), rect.tl(), 1, 1, new Scalar(255, 255, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void saveResults(Mat region, List<PlanarObject> containers, PrintWriter writer, String fileName, String year, String sampleName, String group, String mixing, String duplicate, double transformationDeviance) throws IOException {
|
||||
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
|
||||
// Color Descriptors:
|
||||
MatOfPoint contour = container.getContour();
|
||||
Rect currentSeedRoi = Imgproc.boundingRect(contour);
|
||||
Mat subMatBGR = region.submat(currentSeedRoi);
|
||||
|
||||
Mat subMatRGB = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatRGB, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Mat subMatHSV = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatHSV, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat subMatLab = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatLab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -currentSeedRoi.x, -currentSeedRoi.y);
|
||||
Mat seedMask = Mat.zeros(currentSeedRoi.height, currentSeedRoi.width, CvType.CV_8UC1);
|
||||
Imgproc.drawContours(seedMask, Arrays.asList(shiftedContour), -1, new Scalar(255), -1);
|
||||
|
||||
// Mean color descriptor:
|
||||
int rejectionSigma = 3;
|
||||
MeanColorDescriptor meanColorRGB = new MeanColorDescriptor(rejectionSigma).calculate(subMatRGB, seedMask);
|
||||
MeanColorDescriptor meanColorHSV = new MeanColorDescriptor(rejectionSigma).calculate(subMatHSV, seedMask);
|
||||
MeanColorDescriptor meanColorLab = new MeanColorDescriptor(rejectionSigma).calculate(subMatLab, seedMask);
|
||||
|
||||
// GCH4:
|
||||
int GCH4Dimension = 4;
|
||||
GlobalColorHistogram gch4RGB = new GlobalColorHistogram(GCH4Dimension).calculate(subMatRGB, seedMask);
|
||||
GlobalColorHistogram gch4HSV = new GlobalColorHistogram(GCH4Dimension).calculate(subMatHSV, seedMask);
|
||||
GlobalColorHistogram gch4Lab = new GlobalColorHistogram(GCH4Dimension).calculate(subMatLab, seedMask);
|
||||
|
||||
// GCH8:
|
||||
int GCH8Dimension = 8;
|
||||
GlobalColorHistogram gch8RGB = new GlobalColorHistogram(GCH8Dimension).calculate(subMatRGB, seedMask);
|
||||
GlobalColorHistogram gch8HSV = new GlobalColorHistogram(GCH8Dimension).calculate(subMatHSV, seedMask);
|
||||
GlobalColorHistogram gch8Lab = new GlobalColorHistogram(GCH8Dimension).calculate(subMatLab, seedMask);
|
||||
|
||||
// Color layout descriptor:
|
||||
ColorLayoutDescriptor cldRGB = new ColorLayoutDescriptor(12).calculate(subMatRGB, seedMask, null);
|
||||
ColorLayoutDescriptor cldHSV = new ColorLayoutDescriptor(12).calculate(subMatHSV, seedMask, null);
|
||||
ColorLayoutDescriptor cldLab = new ColorLayoutDescriptor(12).calculate(subMatLab, seedMask, null);
|
||||
|
||||
// Dominant color descriptor:
|
||||
int colorsAmount = 3;
|
||||
DominantColorDescriptor dcdRGB = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatRGB, seedMask, null);
|
||||
DominantColorDescriptor dcdHSV = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatHSV, seedMask, null);
|
||||
DominantColorDescriptor dcdLab = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatLab, seedMask, null);
|
||||
|
||||
subMatBGR.release();
|
||||
subMatRGB.release();
|
||||
subMatHSV.release();
|
||||
subMatLab.release();
|
||||
seedMask.release();
|
||||
shiftedContour.release();
|
||||
|
||||
synchronized (SeedCounterWCDThread.class) {
|
||||
writer.print(fileName + SEPARATOR);
|
||||
writer.print(year + SEPARATOR);
|
||||
writer.print(sampleName + SEPARATOR);
|
||||
writer.print(group + SEPARATOR);
|
||||
writer.print(mixing + SEPARATOR);
|
||||
writer.print(duplicate + SEPARATOR);
|
||||
|
||||
writer.print(Double.toString(transformationDeviance) + SEPARATOR);
|
||||
|
||||
writer.print(Integer.toString(i+1) + SEPARATOR + container.toString(SEPARATOR));
|
||||
|
||||
writer.print(meanColorRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch4RGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch8RGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cldRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(dcdRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(meanColorHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch4HSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch8HSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cldHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(dcdHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(meanColorLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch4Lab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch8Lab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cldLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(dcdLab.toTsvCsvString(SEPARATOR));
|
||||
|
||||
writer.println();
|
||||
}
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
private MatOfPoint shiftContour(MatOfPoint contour, int shiftX, int shiftY) {
|
||||
Point[] pointArr = new Point[contour.rows()*contour.cols()];
|
||||
|
||||
int i = 0;
|
||||
for(Point srcPoint : contour.toArray()) {
|
||||
pointArr[i++] = new Point(srcPoint.x+shiftX, srcPoint.y+shiftY);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = new MatOfPoint();
|
||||
shiftedContour.fromArray(pointArr);
|
||||
return shiftedContour;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.wheatdb.seedcounter.desktop.coloredseedsclassification;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.ml.clustering.CentroidCluster;
|
||||
import org.apache.commons.math3.ml.clustering.Clusterable;
|
||||
|
||||
public class ClusterFunctions {
|
||||
|
||||
public static double clusterRadius(CentroidCluster<ContourContainer> cluster) {
|
||||
Clusterable center = cluster.getCenter();
|
||||
List<ContourContainer> points = cluster.getPoints();
|
||||
|
||||
double maxDistance = 0;
|
||||
for(ContourContainer point : points) {
|
||||
double curDist = euclideanDistance(center.getPoint(), point.getPoint());
|
||||
|
||||
if(curDist > maxDistance) {
|
||||
maxDistance = curDist;
|
||||
}
|
||||
}
|
||||
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
|
||||
public static double euclideanDistance(double[] point1, double[] point2) {
|
||||
int dimensions = point1.length < point2.length ? point1.length : point2.length;
|
||||
|
||||
double quadSum = 0;
|
||||
for(int i=0; i<dimensions; i++) {
|
||||
quadSum += (point1[i]-point2[i]) * (point1[i]-point2[i]);
|
||||
}
|
||||
|
||||
return Math.sqrt(quadSum);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.wheatdb.seedcounter.desktop.coloredseedsclassification;
|
||||
|
||||
import org.apache.commons.math3.ml.clustering.Clusterable;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.wheatdb.seedcounter.processor.ElementsProcessor;
|
||||
|
||||
import ru.delkom07.geometry.ContoursFunctions;
|
||||
import ru.delkom07.geometry.SimpleGeometry;
|
||||
|
||||
|
||||
|
||||
public class ContourContainer implements Clusterable {
|
||||
MatOfPoint contour;
|
||||
|
||||
|
||||
|
||||
public ContourContainer(MatOfPoint contour) {
|
||||
this.contour = contour;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double[] getPoint() {
|
||||
return ContoursFunctions.massCenter(contour);
|
||||
}
|
||||
|
||||
|
||||
public MatOfPoint getContour() {
|
||||
return contour;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.wheatdb.seedcounter.desktop.coloredseedsclassification;
|
||||
|
||||
public class Messages {
|
||||
public static final String usage = "" +
|
||||
"Usage: seedCounter [[--out outputDir] || [-o outputDir]] inputImage [inputImage...]";
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public interface AddInfo {
|
||||
public List<String> headers();
|
||||
|
||||
public String toString(String separator);
|
||||
|
||||
public AddInfo from(File inFile);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public class BaseResultSaver implements ResultSaver {
|
||||
private AddInfo info;
|
||||
|
||||
@Override
|
||||
public void saveResults(PrintWriter writer, Mat region, List<PlanarObject> containers, String fileName, double transformationDeviance) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
|
||||
writeData(writer, region, container, i+1, transformationDeviance);
|
||||
|
||||
writer.println();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
public void writeData(PrintWriter writer, Mat region, PlanarObject container, int seedNumber, double transformationDeviance) {
|
||||
//if(null != info)
|
||||
writer.print(info.toString(SEPARATOR));
|
||||
|
||||
writer.print(Double.toString(transformationDeviance) + SEPARATOR);
|
||||
writer.print(Integer.toString(seedNumber) + SEPARATOR);
|
||||
writer.print(container.toString(SEPARATOR));
|
||||
}
|
||||
|
||||
|
||||
public void writeFileHead(PrintWriter writer) {
|
||||
List<String> columnNames = new LinkedList<String>();
|
||||
|
||||
columnNames.addAll(info.headers());
|
||||
|
||||
columnNames.add("D");
|
||||
columnNames.add("Seed_number");
|
||||
columnNames.add("Length");
|
||||
columnNames.add("Width");
|
||||
columnNames.add("Area");
|
||||
columnNames.add("Circularity");
|
||||
columnNames.add("Roundness");
|
||||
columnNames.add("Rugosity");
|
||||
columnNames.add("Solidity");
|
||||
//columnNames.addAll(SeedData.headers());
|
||||
|
||||
for(String columnName : columnNames) {
|
||||
writer.print(columnName + SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setInfo(AddInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import static org.wheatdb.seedcounter.desktop.AppPrint.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.imgproc.colorchecker.MyColorChecker;
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.desktop.JSONSaver;
|
||||
import org.wheatdb.seedcounter.desktop.RunConfiguration;
|
||||
import org.wheatdb.seedcounter.processor.DetectionProcessor;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
public class ClassicImageHandler implements ImageHandler {
|
||||
private DetectionProcessor detectProcessor; // = new DetectionProcessor(SheetFormat.A4); // from DI container
|
||||
private RunConfiguration config; // from DI container
|
||||
private ResultSaver resultSaver; // from DI container
|
||||
|
||||
|
||||
|
||||
public ClassicImageHandler(RunConfiguration config, DetectionProcessor detectProcessor, ResultSaver resultSaver) {
|
||||
this.config = config;
|
||||
this.detectProcessor = detectProcessor;
|
||||
this.resultSaver = resultSaver;
|
||||
}
|
||||
|
||||
|
||||
public void handleImage(File inFile, File outFile, PrintWriter writer) throws IOException, SeedCounterProcessorException, SeedCounterException {
|
||||
Mat srcImg = Imgcodecs.imread(inFile.getCanonicalPath(), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
|
||||
|
||||
if(srcImg.empty())
|
||||
throw new SeedCounterException("Input file is not image.");
|
||||
|
||||
Mat region = srcImg;
|
||||
if(!config.withoutsheet) {
|
||||
Quad quad = detectProcessor.detectQuad(srcImg);
|
||||
region = detectProcessor.getTransformedField(srcImg, quad);
|
||||
}
|
||||
|
||||
|
||||
List<PlanarObject> containers;
|
||||
|
||||
// if colorchecker is on the image
|
||||
Double transformationDeviance = 0.0;
|
||||
if(config.colorchecker) {
|
||||
MyColorChecker myColorChecker = new MyColorChecker(region);
|
||||
|
||||
// fill ColorChecker
|
||||
myColorChecker.fillColorChecker();
|
||||
|
||||
if(config.calibrate) {
|
||||
Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
|
||||
// resources need to be release
|
||||
if(!region.empty()) {
|
||||
region.release();
|
||||
}
|
||||
|
||||
region = calibratedPair.getLeft();
|
||||
transformationDeviance = calibratedPair.getRight();
|
||||
|
||||
print("# Transformation deviance: " + transformationDeviance.toString());
|
||||
}
|
||||
|
||||
containers = detectProcessor.detectSeedContours(region, myColorChecker.getPixPerMM());
|
||||
} else {
|
||||
containers = detectProcessor.detectSeedContours(region.clone());
|
||||
}
|
||||
|
||||
|
||||
print("Seed count: " + containers.size());
|
||||
|
||||
resultSaver.saveResults(writer, region, containers, inFile.getName(), transformationDeviance);
|
||||
|
||||
new JSONSaver().saveInJSON(region, containers, inFile, outFile);
|
||||
|
||||
if(config.draw) {
|
||||
if(config.markdescriptor) {
|
||||
ColorDescriptorsSet.drawContoursWithRectAndNumAndMarkGCH(region, containers, config.colorSpace, config.markGCHDimension, config.markGCHBinNumber);
|
||||
} else {
|
||||
drawContoursWithRectAndNum(region, containers);
|
||||
}
|
||||
|
||||
Imgcodecs.imwrite(outFile.getCanonicalPath(), region);
|
||||
}
|
||||
|
||||
|
||||
// Releases
|
||||
for(PlanarObject container : containers) {
|
||||
container.getContour().release();
|
||||
}
|
||||
containers.clear();
|
||||
|
||||
if(!srcImg.empty()) {
|
||||
srcImg.release();
|
||||
}
|
||||
|
||||
if(!region.empty()) {
|
||||
region.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void drawContoursWithRectAndNum(Mat img, List<PlanarObject> containers) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
Rect rect = Imgproc.boundingRect(container.getContour());
|
||||
|
||||
Imgproc.rectangle(img, rect.tl(), rect.br(), new Scalar(255, 255, 0));
|
||||
Imgproc.drawContours(img, Arrays.asList(container.getContour()), -1, new Scalar(255, 255, 0), 1);
|
||||
Imgproc.putText(img, Integer.toString(i+1), rect.tl(), 1, 1, new Scalar(255, 255, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ResultSaver getResultSaver() {
|
||||
return resultSaver;
|
||||
}
|
||||
|
||||
|
||||
public void setResultSaver(ResultSaver resultSaver) {
|
||||
this.resultSaver = resultSaver;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.desktop.ColorSpace;
|
||||
import org.wheatdb.seedcounter.processor.ImageArea;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
import ru.delkom07.improc.color.descriptors.ColorLayoutDescriptor;
|
||||
import ru.delkom07.improc.color.descriptors.DominantColorDescriptor;
|
||||
import ru.delkom07.improc.color.descriptors.GlobalColorHistogram;
|
||||
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
|
||||
|
||||
public class ColorDescriptorsSet {
|
||||
// Mean color
|
||||
MeanColorDescriptor meanColorRGB;
|
||||
MeanColorDescriptor meanColorHSV;
|
||||
MeanColorDescriptor meanColorLab;
|
||||
MeanColorDescriptor meanColorYCrCb;
|
||||
|
||||
// GCH4:
|
||||
GlobalColorHistogram gch4RGB;
|
||||
GlobalColorHistogram gch4HSV;
|
||||
GlobalColorHistogram gch4Lab;
|
||||
GlobalColorHistogram gch4YCrCb;
|
||||
|
||||
// GCH8:
|
||||
GlobalColorHistogram gch8RGB;
|
||||
GlobalColorHistogram gch8HSV;
|
||||
GlobalColorHistogram gch8Lab;
|
||||
GlobalColorHistogram gch8YCrCb;
|
||||
|
||||
// Color layout descriptor:
|
||||
ColorLayoutDescriptor cldRGB;
|
||||
ColorLayoutDescriptor cldHSV;
|
||||
ColorLayoutDescriptor cldLab;
|
||||
ColorLayoutDescriptor cldYCrCb;
|
||||
|
||||
// Dominant color descriptor:
|
||||
DominantColorDescriptor dcdRGB;
|
||||
DominantColorDescriptor dcdHSV;
|
||||
DominantColorDescriptor dcdLab;
|
||||
DominantColorDescriptor dcdYCrCb;
|
||||
|
||||
|
||||
ColorDescriptorsSet(Mat region, ImageArea imageArea) {
|
||||
MatOfPoint contour = imageArea.getContour();
|
||||
Rect currentSeedRoi = Imgproc.boundingRect(contour);
|
||||
Mat subMatBGR = region.submat(currentSeedRoi);
|
||||
|
||||
Mat subMatRGB = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatRGB, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Mat subMatHSV = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatHSV, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat subMatLab = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatLab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
Mat subMatYCrCb = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatYCrCb, Imgproc.COLOR_BGR2YCrCb);
|
||||
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -currentSeedRoi.x, -currentSeedRoi.y);
|
||||
Mat seedMask = Mat.zeros(currentSeedRoi.height, currentSeedRoi.width, CvType.CV_8UC1);
|
||||
Imgproc.drawContours(seedMask, Arrays.asList(shiftedContour), -1, new Scalar(255), -1);
|
||||
|
||||
// Mean color descriptor:
|
||||
int rejectionSigma = 3;
|
||||
meanColorRGB = new MeanColorDescriptor(rejectionSigma).calculate(subMatRGB, seedMask);
|
||||
meanColorHSV = new MeanColorDescriptor(rejectionSigma).calculate(subMatHSV, seedMask);
|
||||
meanColorLab = new MeanColorDescriptor(rejectionSigma).calculate(subMatLab, seedMask);
|
||||
meanColorYCrCb = new MeanColorDescriptor(rejectionSigma).calculate(subMatYCrCb, seedMask);
|
||||
|
||||
// GCH4:
|
||||
int GCH4Dimension = 4;
|
||||
gch4RGB = new GlobalColorHistogram(GCH4Dimension).calculate(subMatRGB, seedMask);
|
||||
gch4HSV = new GlobalColorHistogram(GCH4Dimension, 180, 256, 256).calculate(subMatHSV, seedMask);
|
||||
gch4Lab = new GlobalColorHistogram(GCH4Dimension).calculate(subMatLab, seedMask);
|
||||
gch4YCrCb = new GlobalColorHistogram(GCH4Dimension).calculate(subMatYCrCb, seedMask);
|
||||
|
||||
// GCH8:
|
||||
int GCH8Dimension = 8;
|
||||
gch8RGB = new GlobalColorHistogram(GCH8Dimension).calculate(subMatRGB, seedMask);
|
||||
gch8HSV = new GlobalColorHistogram(GCH8Dimension, 180, 256, 256).calculate(subMatHSV, seedMask);
|
||||
gch8Lab = new GlobalColorHistogram(GCH8Dimension).calculate(subMatLab, seedMask);
|
||||
gch8YCrCb = new GlobalColorHistogram(GCH8Dimension).calculate(subMatYCrCb, seedMask);
|
||||
|
||||
// Color layout descriptor:
|
||||
cldRGB = new ColorLayoutDescriptor(12).calculate(subMatRGB, seedMask, null);
|
||||
cldHSV = new ColorLayoutDescriptor(12).calculate(subMatHSV, seedMask, null);
|
||||
cldLab = new ColorLayoutDescriptor(12).calculate(subMatLab, seedMask, null);
|
||||
cldYCrCb = new ColorLayoutDescriptor(12).calculate(subMatYCrCb, seedMask, null);
|
||||
|
||||
// Dominant color descriptor:
|
||||
int colorsAmount = 3;
|
||||
dcdRGB = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatRGB, seedMask, null);
|
||||
dcdHSV = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatHSV, seedMask, null);
|
||||
dcdLab = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatLab, seedMask, null);
|
||||
dcdYCrCb = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatYCrCb, seedMask, null);
|
||||
|
||||
subMatBGR.release();
|
||||
subMatRGB.release();
|
||||
subMatHSV.release();
|
||||
subMatLab.release();
|
||||
subMatYCrCb.release();
|
||||
seedMask.release();
|
||||
shiftedContour.release();
|
||||
}
|
||||
|
||||
|
||||
private static MatOfPoint shiftContour(MatOfPoint contour, int shiftX, int shiftY) {
|
||||
Point[] pointArr = new Point[contour.rows()*contour.cols()];
|
||||
|
||||
int i = 0;
|
||||
for(Point srcPoint : contour.toArray()) {
|
||||
pointArr[i++] = new Point(srcPoint.x+shiftX, srcPoint.y+shiftY);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = new MatOfPoint();
|
||||
shiftedContour.fromArray(pointArr);
|
||||
return shiftedContour;
|
||||
}
|
||||
|
||||
|
||||
static void drawContoursWithRectAndNumAndMarkGCH(Mat img, List<PlanarObject> containers, ColorSpace colorSpace, int gchDimension, int binNumber) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
Rect roi = Imgproc.boundingRect(container.getContour());
|
||||
|
||||
MatOfPoint contour = container.getContour();
|
||||
Mat subMatBGR = img.submat(roi);
|
||||
|
||||
Mat subMatColorSpace = new Mat();
|
||||
if(ColorSpace.RGB == colorSpace) {
|
||||
Imgproc.cvtColor(subMatBGR, subMatColorSpace, Imgproc.COLOR_BGR2RGB);
|
||||
} else if(ColorSpace.HSV == colorSpace) {
|
||||
Imgproc.cvtColor(subMatBGR, subMatColorSpace, Imgproc.COLOR_BGR2HSV);
|
||||
} else if(ColorSpace.Lab == colorSpace) {
|
||||
Imgproc.cvtColor(subMatBGR, subMatColorSpace, Imgproc.COLOR_BGR2Lab);
|
||||
} else if(ColorSpace.YCrCb == colorSpace) {
|
||||
Imgproc.cvtColor(subMatBGR, subMatColorSpace, Imgproc.COLOR_BGR2YCrCb);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -roi.x, -roi.y);
|
||||
Mat seedMask = Mat.zeros(roi.height, roi.width, CvType.CV_8UC1);
|
||||
Imgproc.drawContours(seedMask, Arrays.asList(shiftedContour), -1, new Scalar(255), -1);
|
||||
|
||||
GlobalColorHistogram gch = new GlobalColorHistogram(gchDimension);
|
||||
Mat mark = gch.markPixels(subMatColorSpace, seedMask, binNumber);
|
||||
|
||||
for(int ii=0; ii<mark.rows(); ii++) {
|
||||
for(int jj=0; jj<mark.cols(); jj++) {
|
||||
if(0 != mark.get(ii, jj)[0]) {
|
||||
|
||||
double[] imgPix = img.get(ii+roi.y, jj+roi.x);
|
||||
img.put(ii+roi.y, jj+roi.x, new double[] {imgPix[0], imgPix[1]+100, imgPix[2]-100});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mark.release();
|
||||
subMatBGR.release();
|
||||
subMatColorSpace.release();
|
||||
shiftedContour.release();
|
||||
seedMask.release();
|
||||
|
||||
|
||||
//Imgproc.rectangle(img, roi.tl(), roi.br(), new Scalar(255, 255, 0));
|
||||
//Imgproc.drawContours(img, Arrays.asList(container.getSeedContour()), -1, new Scalar(255, 255, 0), 1);
|
||||
Imgproc.putText(img, Integer.toString(i+1), roi.tl(), 1, 1, new Scalar(255, 255, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public class ColorResultSaver implements ResultSaver {
|
||||
private ColorDescriptorsSet cd;
|
||||
|
||||
private ResultSaver baseSaver;
|
||||
|
||||
|
||||
|
||||
public ColorResultSaver() {}
|
||||
|
||||
public ColorResultSaver(ResultSaver baseSaver) {
|
||||
this.baseSaver = baseSaver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveResults(PrintWriter writer, Mat region, List<PlanarObject> containers, String fileName, double transformationDeviance) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
|
||||
writeData(writer, region, container, i+1, transformationDeviance);
|
||||
|
||||
writer.println();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
public void writeData(PrintWriter writer, Mat region, PlanarObject container, int seedNumber, double transformationDeviance) {
|
||||
if(null != baseSaver)
|
||||
baseSaver.writeData(writer, region, container, seedNumber, transformationDeviance);
|
||||
|
||||
cd = new ColorDescriptorsSet(region, container);
|
||||
writer.print(cd.meanColorRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch4RGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch8RGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.cldRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.dcdRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(cd.meanColorHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch4HSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch8HSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.cldHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.dcdHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(cd.meanColorLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch4Lab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch8Lab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.cldLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.dcdLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(cd.meanColorYCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch4YCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch8YCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.cldYCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.dcdYCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
}
|
||||
|
||||
|
||||
public void writeFileHead(PrintWriter writer) {
|
||||
if(null != baseSaver)
|
||||
baseSaver.writeFileHead(writer);
|
||||
|
||||
List<String> columnNames = new LinkedList<String>();
|
||||
|
||||
// RGB
|
||||
columnNames.add("RGB_meanColor_R");
|
||||
columnNames.add("RGB_meanColor_G");
|
||||
columnNames.add("RGB_meanColor_B");
|
||||
|
||||
for(int i=0; i<64; i++) {
|
||||
columnNames.add("RGB_GCH4_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<512; i++) {
|
||||
columnNames.add("RGB_GCH8_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<12; i++) {
|
||||
columnNames.add("RGB_colorLayout_R_" + (i+1));
|
||||
columnNames.add("RGB_colorLayout_G_" + (i+1));
|
||||
columnNames.add("RGB_colorLayout_B_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++) {
|
||||
columnNames.add("RGB_dominantColor_R_" + (i+1));
|
||||
columnNames.add("RGB_dominantColor_G_" + (i+1));
|
||||
columnNames.add("RGB_dominantColor_B_" + (i+1));
|
||||
columnNames.add("RGB_dominantColor_percent_" + (i+1));
|
||||
columnNames.add("RGB_dominantColor_variance_" + (i+1));
|
||||
|
||||
}
|
||||
|
||||
columnNames.add("RGB_dominantColor_spatialCoherency");
|
||||
|
||||
// HSV
|
||||
columnNames.add("HSV_meanColor_H");
|
||||
columnNames.add("HSV_meanColor_S");
|
||||
columnNames.add("HSV_meanColor_V");
|
||||
|
||||
for(int i=0; i<64; i++) {
|
||||
columnNames.add("HSV_GCH4_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<512; i++) {
|
||||
columnNames.add("HSV_GCH8_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<12; i++) {
|
||||
columnNames.add("HSV_colorLayout_H_" + (i+1));
|
||||
columnNames.add("HSV_colorLayout_S_" + (i+1));
|
||||
columnNames.add("HSV_colorLayout_V_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++) {
|
||||
columnNames.add("HSV_dominantColor_H_" + (i+1));
|
||||
columnNames.add("HSV_dominantColor_S_" + (i+1));
|
||||
columnNames.add("HSV_dominantColor_V_" + (i+1));
|
||||
columnNames.add("HSV_dominantColor_persent_" + (i+1));
|
||||
columnNames.add("HSV_dominantColor_variance_" + (i+1));
|
||||
}
|
||||
|
||||
columnNames.add("HSV_dominantColor_spatialCoherency");
|
||||
|
||||
// L'a'b'
|
||||
columnNames.add("Lab_meanColor_L");
|
||||
columnNames.add("Lab_meanColor_a");
|
||||
columnNames.add("Lab_meanColor_b");
|
||||
|
||||
for(int i=0; i<64; i++) {
|
||||
columnNames.add("Lab_GCH4_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<512; i++) {
|
||||
columnNames.add("Lab_GCH8_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<12; i++) {
|
||||
columnNames.add("Lab_colorLayout_L_" + (i+1));
|
||||
columnNames.add("Lab_colorLayout_a_" + (i+1));
|
||||
columnNames.add("Lab_colorLayout_b_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++) {
|
||||
columnNames.add("Lab_dominantColor_L_" + (i+1));
|
||||
columnNames.add("Lab_dominantColor_a_" + (i+1));
|
||||
columnNames.add("Lab_dominantColor_b_" + (i+1));
|
||||
columnNames.add("Lab_dominantColor_persent_" + (i+1));
|
||||
columnNames.add("Lab_dominantColor_variance_" + (i+1));
|
||||
}
|
||||
|
||||
columnNames.add("Lab_dominantColor_spatialCoherency");
|
||||
|
||||
// YCrCb
|
||||
columnNames.add("YCrCb_meanColor_Y");
|
||||
columnNames.add("YCrCb_meanColor_Cr");
|
||||
columnNames.add("YCrCb_meanColor_Cb");
|
||||
|
||||
for(int i=0; i<64; i++) {
|
||||
columnNames.add("YCrCb_GCH4_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<512; i++) {
|
||||
columnNames.add("YCrCb_GCH8_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<12; i++) {
|
||||
columnNames.add("YCrCb_colorLayout_Y_" + (i+1));
|
||||
columnNames.add("YCrCb_colorLayout_Cr_" + (i+1));
|
||||
columnNames.add("YCrCb_colorLayout_Cb_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++) {
|
||||
columnNames.add("YCrCb_dominantColor_Y_" + (i+1));
|
||||
columnNames.add("YCrCb_dominantColor_Cr_" + (i+1));
|
||||
columnNames.add("YCrCb_dominantColor_Cb_" + (i+1));
|
||||
columnNames.add("YCrCb_dominantColor_persent_" + (i+1));
|
||||
columnNames.add("YCrCb_dominantColor_variance_" + (i+1));
|
||||
}
|
||||
|
||||
columnNames.add("YCrCb_dominantColor_spatialCoherency");
|
||||
|
||||
|
||||
for(String columnName : columnNames) {
|
||||
writer.print(columnName + SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInfo(AddInfo info) {
|
||||
this.baseSaver.setInfo(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Дополнительная информация о входные данных, извлекаемая из файловой структуры, специальных файлов описания и т.п.
|
||||
* Функционал класса планируется постоянно расширять.
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class FileStructureAddInfo implements AddInfo {
|
||||
public String file;
|
||||
public String year;
|
||||
public String sampleName;
|
||||
public String group;
|
||||
public String mixing;
|
||||
public String duplicate;
|
||||
|
||||
public FileStructureAddInfo() {}
|
||||
|
||||
private FileStructureAddInfo(String file, String year, String sampleName, String group, String mixing, String duplicate) {
|
||||
this.file = file;
|
||||
this.year = year;
|
||||
this.sampleName = sampleName;
|
||||
this.group = group;
|
||||
this.mixing = mixing;
|
||||
this.duplicate = duplicate;
|
||||
}
|
||||
|
||||
public FileStructureAddInfo from(File inFile) {
|
||||
try {
|
||||
String group = inFile.getParentFile().getName();
|
||||
String[] splitedSampleDirName = inFile.getParentFile().getParentFile().getName().split(" ");
|
||||
String sampleName;
|
||||
String year;
|
||||
if(splitedSampleDirName.length == 3) {
|
||||
sampleName = splitedSampleDirName[0] + "_" + splitedSampleDirName[1];
|
||||
year = splitedSampleDirName[2];
|
||||
} else
|
||||
if(splitedSampleDirName.length == 2){
|
||||
sampleName = splitedSampleDirName[0] + "_" + splitedSampleDirName[1];
|
||||
year = "-";
|
||||
} else {
|
||||
sampleName = splitedSampleDirName[0];
|
||||
year = "-";
|
||||
}
|
||||
|
||||
String mixing = FilenameUtils.getBaseName(inFile.getName()).split("_")[0];
|
||||
String duplicate = FilenameUtils.getBaseName(inFile.getName()).split("_")[1];
|
||||
|
||||
return new FileStructureAddInfo(inFile.getName(), year, sampleName, group, mixing, duplicate);
|
||||
} catch(Exception ex) {
|
||||
return new FileStructureAddInfo(inFile.getName(), "-", "-", "-", "-", "-");
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> headers() {
|
||||
var columnNames = new LinkedList<String>();
|
||||
|
||||
columnNames.add("File");
|
||||
columnNames.add("Year");
|
||||
columnNames.add("Sample");
|
||||
columnNames.add("Group");
|
||||
columnNames.add("Mixing");
|
||||
columnNames.add("Duplicate");
|
||||
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
public String toString(String separator) {
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.append(file + separator);
|
||||
builder.append(year + separator);
|
||||
builder.append(sampleName + separator);
|
||||
builder.append(group + separator);
|
||||
builder.append(mixing + separator);
|
||||
builder.append(duplicate + separator);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
|
||||
public interface ImageHandler {
|
||||
public void handleImage(File inFile, File outFile, PrintWriter writer) throws IOException, SeedCounterProcessorException, SeedCounterException;
|
||||
|
||||
public ResultSaver getResultSaver();
|
||||
|
||||
public void setResultSaver(ResultSaver resultSaver);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public interface ResultSaver {
|
||||
public static final String SEPARATOR = " ";
|
||||
|
||||
public void saveResults(PrintWriter writer, Mat region, List<PlanarObject> containers, String fileName, double transformationDeviance);
|
||||
|
||||
public void writeData(PrintWriter writer, Mat region, PlanarObject container, int seedNumber, double transformationDeviance);
|
||||
|
||||
public void writeFileHead(PrintWriter writer);
|
||||
|
||||
public void setInfo(AddInfo info);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SimpleAddInfo implements AddInfo {
|
||||
public String file;
|
||||
|
||||
@Override
|
||||
public List<String> headers() {
|
||||
var columnNames = new LinkedList<String>();
|
||||
|
||||
columnNames.add("File");
|
||||
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String separator) {
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.append(file + separator);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddInfo from(File inFile) {
|
||||
var info = new SimpleAddInfo();
|
||||
info.file = inFile.getName();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.ImageArea;
|
||||
|
||||
import ru.delkom07.improc.texture.MaskedGLCM;
|
||||
import ru.delkom07.improc.texture.MaskedGLRM;
|
||||
|
||||
public class TextureDescriptorsSet {
|
||||
MaskedGLCM maskedGLCM;
|
||||
MaskedGLRM maskedGLRM;
|
||||
|
||||
|
||||
TextureDescriptorsSet(Mat region, ImageArea imageArea) {
|
||||
MatOfPoint contour = imageArea.getContour();
|
||||
Rect currentSeedRoi = Imgproc.boundingRect(contour);
|
||||
Mat subMatBGR = region.submat(currentSeedRoi);
|
||||
|
||||
Mat subMatGray = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatGray, Imgproc.COLOR_BGR2GRAY);
|
||||
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -currentSeedRoi.x, -currentSeedRoi.y);
|
||||
Mat seedMask = Mat.zeros(currentSeedRoi.height, currentSeedRoi.width, CvType.CV_8UC1);
|
||||
Imgproc.drawContours(seedMask, Arrays.asList(shiftedContour), -1, new Scalar(255), -1);
|
||||
|
||||
// Descriptors
|
||||
// GLCM
|
||||
int d = 3;
|
||||
maskedGLCM = new MaskedGLCM(d).calculate(subMatGray, seedMask);
|
||||
|
||||
// GLRM
|
||||
int maxLength = 10;
|
||||
maskedGLRM = new MaskedGLRM(maxLength).calculate(subMatGray, seedMask);
|
||||
|
||||
subMatBGR.release();
|
||||
subMatGray.release();
|
||||
|
||||
seedMask.release();
|
||||
shiftedContour.release();
|
||||
}
|
||||
|
||||
|
||||
private static MatOfPoint shiftContour(MatOfPoint contour, int shiftX, int shiftY) {
|
||||
Point[] pointArr = new Point[contour.rows()*contour.cols()];
|
||||
|
||||
int i = 0;
|
||||
for(Point srcPoint : contour.toArray()) {
|
||||
pointArr[i++] = new Point(srcPoint.x+shiftX, srcPoint.y+shiftY);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = new MatOfPoint();
|
||||
shiftedContour.fromArray(pointArr);
|
||||
return shiftedContour;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
import ru.delkom07.improc.texture.GLCM;
|
||||
import ru.delkom07.improc.texture.GLRM;
|
||||
|
||||
public class TextureResultSaver implements ResultSaver {
|
||||
private TextureDescriptorsSet td;
|
||||
|
||||
private ResultSaver baseSaver;
|
||||
|
||||
public TextureResultSaver() {}
|
||||
|
||||
public TextureResultSaver(ResultSaver baseSaver) {
|
||||
this.baseSaver = baseSaver;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void saveResults(PrintWriter writer, Mat region, List<PlanarObject> containers, String fileName, double transformationDeviance) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
|
||||
writeData(writer, region, container, i+1, transformationDeviance);
|
||||
|
||||
writer.println();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
public void writeData(PrintWriter writer, Mat region, PlanarObject container, int seedNumber, double transformationDeviance) {
|
||||
if(null != baseSaver)
|
||||
baseSaver.writeData(writer, region, container, seedNumber, transformationDeviance);
|
||||
|
||||
td = new TextureDescriptorsSet(region, container);
|
||||
writer.print(td.maskedGLCM.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(td.maskedGLRM.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void writeFileHead(PrintWriter writer) {
|
||||
if(null != baseSaver)
|
||||
baseSaver.writeFileHead(writer);
|
||||
|
||||
List<String> columnNames = new LinkedList<String>();
|
||||
|
||||
// GLCM
|
||||
columnNames.addAll(Arrays.asList(GLCM.ChNames()));
|
||||
|
||||
// GLRM
|
||||
columnNames.addAll(Arrays.asList(GLRM.ChNames()));
|
||||
|
||||
for(String columnName : columnNames) {
|
||||
writer.print(columnName + SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInfo(AddInfo info) {
|
||||
this.baseSaver.setInfo(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.RotatedRect;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.SeedDataContainer;
|
||||
|
||||
import ru.delkom07.geometry.AdditionalIndexies;
|
||||
import ru.delkom07.geometry.ContoursFunctions;
|
||||
import ru.delkom07.geometry.SimpleGeometry;
|
||||
import ru.delkom07.geometry.obj.Circle;
|
||||
import ru.delkom07.geometry.obj.Vector;
|
||||
|
||||
|
||||
/**
|
||||
* Процессор для поиска контуров зерен по бинарной маске.
|
||||
* Необходим рефакторинг, т.к. частично повторяет DetectionProcessor.
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class BinaryMaskProcessor implements Processor {
|
||||
|
||||
public List<PlanarObject> detectSeedContours(Mat mask, double pixPerMM) throws SeedCounterProcessorException {
|
||||
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
|
||||
// Find contours
|
||||
Imgproc.findContours(mask, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
|
||||
// Gauge seeds
|
||||
List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
MatOfPoint contour = contours.get(i);
|
||||
|
||||
double xScale = 1.0 / pixPerMM; // this may affect the output
|
||||
double yScale = 1.0 / pixPerMM; // this may affect the output
|
||||
|
||||
if(contour.cols()*contour.rows() > 5) { // There should be at least 5 points to fit the ellipse
|
||||
SeedDataContainer seedDataContainer = gaugeSeed(contour, xScale, yScale);
|
||||
|
||||
seedDataList.add(seedDataContainer);
|
||||
}
|
||||
}
|
||||
|
||||
return seedDataList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get info about seed on image (size in millimeters).
|
||||
* @param image - source image that containing seed.
|
||||
* @return information about seeds.
|
||||
* @throws SeedCounterException - if DetectionProcessor isn't initialized.
|
||||
*/
|
||||
private SeedDataContainer gaugeSeed(MatOfPoint contour, double xScale, double yScale) throws SeedCounterProcessorException {
|
||||
// get contour ellipse approximation
|
||||
MatOfPoint2f forFillEllipse = new MatOfPoint2f();
|
||||
contour.convertTo(forFillEllipse, CvType.CV_32FC2);
|
||||
RotatedRect rotatedRect = Imgproc.fitEllipse(forFillEllipse);
|
||||
Point[] points = new Point[4];
|
||||
rotatedRect.points(points);
|
||||
|
||||
// determine seed orientation related to sheet
|
||||
Vector seedVector = new Vector(points[0], points[1]);
|
||||
double dx = Math.abs(seedVector.x());
|
||||
double dy = Math.abs(seedVector.y());
|
||||
|
||||
double lamdaX = dx/(dx+dy);
|
||||
double lamdaY = dy/(dx+dy);
|
||||
|
||||
double lengthDistortionFactor = lamdaX*xScale + lamdaY*yScale;
|
||||
double widthDistortionFactor = lamdaX*yScale + lamdaY*xScale;
|
||||
|
||||
double seedLength = SimpleGeometry.distance(points[0], points[1])*lengthDistortionFactor;
|
||||
double seedWidth = SimpleGeometry.distance(points[1], points[2])*widthDistortionFactor;
|
||||
|
||||
// area
|
||||
double areaInPix = Imgproc.contourArea(contour, false);
|
||||
double area = areaInPix * lengthDistortionFactor*widthDistortionFactor;
|
||||
|
||||
// calculate mass center
|
||||
Rect seedRect = Imgproc.boundingRect(contour);
|
||||
double[] globalMassCenter = ContoursFunctions.massCenter(contour);
|
||||
double[] localMassCenter = new double[]{globalMassCenter[0]-seedRect.x, globalMassCenter[1]-seedRect.y};
|
||||
|
||||
// axis points
|
||||
double[] lengthAxisPoint1 = new double[]{(points[0].x+points[1].x)/2.0, (points[0].y+points[1].y)/2.0};
|
||||
double[] lengthAxisPoint2 = new double[]{(points[2].x+points[3].x)/2.0, (points[2].y+points[3].y)/2.0};
|
||||
double[] widthAxisPoint1 = new double[]{(points[1].x+points[2].x)/2.0, (points[1].y+points[2].y)/2.0};
|
||||
double[] widthAxisPoint2 = new double[]{(points[0].x+points[3].x)/2.0, (points[0].y+points[3].y)/2.0};
|
||||
|
||||
if(SimpleGeometry.distance(lengthAxisPoint1, lengthAxisPoint2)<SimpleGeometry.distance(widthAxisPoint1, widthAxisPoint2)) {
|
||||
double[] tmp1 = widthAxisPoint1;
|
||||
double[] tmp2 = widthAxisPoint2;
|
||||
|
||||
widthAxisPoint1 = lengthAxisPoint1;
|
||||
widthAxisPoint2 = lengthAxisPoint2;
|
||||
lengthAxisPoint1 = tmp1;
|
||||
lengthAxisPoint2 = tmp2;
|
||||
}
|
||||
|
||||
// calculate intersection point of length and width lines
|
||||
double[] lengthWidthIntersectPoint = SimpleGeometry.computeIntersect(lengthAxisPoint1, lengthAxisPoint2, widthAxisPoint1, widthAxisPoint2);
|
||||
lengthWidthIntersectPoint = new double[]{lengthWidthIntersectPoint[0]-seedRect.x, lengthWidthIntersectPoint[1]-seedRect.y}; // in seedRect
|
||||
|
||||
// distance from mass center to intersection point of length and width lines
|
||||
double distaceFromMassCenterToWidthLengthIntersectPoint = SimpleGeometry.distance(lengthWidthIntersectPoint, localMassCenter) * Math.sqrt(lengthDistortionFactor * widthDistortionFactor); // this may affect the output
|
||||
|
||||
SeedDataContainer seedDataContainer = new SeedDataContainer(contour, seedLength, seedWidth);
|
||||
seedDataContainer.setArea(area);
|
||||
seedDataContainer.setMassCenter(localMassCenter);
|
||||
seedDataContainer.setLengthWidthIntersectPoint(lengthWidthIntersectPoint);
|
||||
seedDataContainer.setDistaceFromMassCenterToWidthLengthIntersectPoint(distaceFromMassCenterToWidthLengthIntersectPoint);
|
||||
|
||||
seedDataContainer.setCircularityIndex(AdditionalIndexies.circularityIndex(contour));
|
||||
seedDataContainer.setRoundness(AdditionalIndexies.roundness(contour));
|
||||
seedDataContainer.setRugosity(AdditionalIndexies.rugosity(contour));
|
||||
seedDataContainer.setSolidity(AdditionalIndexies.solidity(contour));
|
||||
|
||||
Circle maxInscribed = AdditionalIndexies.getMaxInscribedCircle(contour);
|
||||
Circle minCircumscribed = AdditionalIndexies.getMinCircumscribedCircle(contour);
|
||||
|
||||
double equivalentAreaCircleRadius = Math.sqrt(areaInPix/Math.PI);
|
||||
seedDataContainer.setMaxInscribedMinCircumscribedCirclesDiametersRatio(maxInscribed.radius()/minCircumscribed.radius());
|
||||
seedDataContainer.setMaxInscribedCircleToEqAreaCircleRatio(maxInscribed.radius()/equivalentAreaCircleRadius);
|
||||
|
||||
return seedDataContainer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
/**
|
||||
* Калибровка цвета.
|
||||
* @author Komyshev.
|
||||
* v.0.1. (D)
|
||||
*/
|
||||
public class Calibration {
|
||||
// Количетсво цветов принятых на анализ калибровки
|
||||
private int count = 0;
|
||||
|
||||
private Scalar targetColor;
|
||||
private Scalar minChannels;
|
||||
private Scalar maxChannels;
|
||||
|
||||
|
||||
public Calibration() {}
|
||||
|
||||
public Calibration(Scalar targetColor) {
|
||||
this.targetColor = targetColor;
|
||||
this.minChannels = targetColor;
|
||||
this.maxChannels = targetColor;
|
||||
count=1;
|
||||
}
|
||||
|
||||
public void addColor(Scalar color) {
|
||||
if(0==count) {
|
||||
this.targetColor = color;
|
||||
this.minChannels = color;
|
||||
this.maxChannels = color;
|
||||
count=1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
double newH = (targetColor.val[0]*count + color.val[0])/(double)(count+1);
|
||||
double newS = (targetColor.val[1]*count + color.val[1])/(double)(count+1);
|
||||
double newV = (targetColor.val[2]*count + color.val[2])/(double)(count+1);
|
||||
count++;
|
||||
|
||||
targetColor = new Scalar(newH, newS, newV);
|
||||
|
||||
if(color.val[0]>maxChannels.val[0]) {
|
||||
maxChannels.val[0] = color.val[0];
|
||||
}
|
||||
if(color.val[1]>maxChannels.val[1]) {
|
||||
maxChannels.val[1] = color.val[1];
|
||||
}
|
||||
if(color.val[2]>maxChannels.val[2]) {
|
||||
maxChannels.val[2] = color.val[2];
|
||||
}
|
||||
|
||||
if(color.val[0]<minChannels.val[0]) {
|
||||
minChannels.val[0] = color.val[0];
|
||||
}
|
||||
if(color.val[1]<minChannels.val[1]) {
|
||||
minChannels.val[1] = color.val[1];
|
||||
}
|
||||
if(color.val[2]<minChannels.val[2]) {
|
||||
minChannels.val[2] = color.val[2];
|
||||
}
|
||||
}
|
||||
|
||||
public Scalar getTargetColor() {
|
||||
if(0==count) return new Scalar(255/2, 255/2, 255/2);
|
||||
return targetColor;
|
||||
}
|
||||
|
||||
public Scalar getMinChannels() {
|
||||
if(0==count) return new Scalar(0, 0, 0);
|
||||
return minChannels;
|
||||
}
|
||||
|
||||
public Scalar getMaxChannels() {
|
||||
if(0==count) return new Scalar(255, 255, 255);
|
||||
return maxChannels;
|
||||
}
|
||||
|
||||
public static Scalar RGBtoHSV(Scalar rgb, boolean inrange256) {
|
||||
double r_ = rgb.val[0]/255;
|
||||
double g_ = rgb.val[1]/255;
|
||||
double b_ = rgb.val[2]/255;
|
||||
|
||||
double cMax = (r_ > g_ ? r_ : g_) > b_ ? (r_ > g_ ? r_ : g_) : b_;
|
||||
double cMin = (r_ > g_ ? g_ : r_) > b_ ? b_ : (r_ > g_ ? g_ : r_);
|
||||
double delta = cMax - cMin;
|
||||
|
||||
double hh = 0;
|
||||
if(cMax==r_) {
|
||||
double k = ((g_-b_)/delta);
|
||||
hh = (k-( ((int)(k/6))*6))*60;
|
||||
} else
|
||||
if(cMax==g_) {
|
||||
hh = (((b_-r_)/delta)+2)*60;
|
||||
} else {
|
||||
hh = (((r_-g_)/delta)+4)*60;
|
||||
}
|
||||
|
||||
double h = inrange256 ? Math.round(hh/360f*255f) : hh;
|
||||
double s = inrange256 ? Math.round(delta/cMax*255f) : delta/cMax;
|
||||
double v = inrange256 ? Math.round(cMax*255f) : cMax;
|
||||
|
||||
return new Scalar(h, s, v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.RotatedRect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.SeedDataContainer;
|
||||
import org.wheatdb.seedcounter.SheetFormat;
|
||||
import org.wheatdb.seedcounter.processor.ex.QuadNotFoundException;
|
||||
import org.wheatdb.seedcounter.processor.filters.GrayBinarization;
|
||||
import org.wheatdb.seedcounter.processor.filters.HSVBinarization;
|
||||
import org.wheatdb.seedcounter.processor.filters.SeedsBinarizationForWhiteSeeds;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.geometry.AdditionalIndexies;
|
||||
import ru.delkom07.geometry.ContoursFunctions;
|
||||
import ru.delkom07.geometry.SimpleGeometry;
|
||||
import ru.delkom07.geometry.obj.Circle;
|
||||
import ru.delkom07.geometry.obj.Vector;
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
/**
|
||||
* SeedCountProcessor produces detection and measurement of seed.
|
||||
* @version 2.0 (20.12.23)
|
||||
*/
|
||||
public class DetectionProcessor implements Processor {
|
||||
protected double MIN_SIDE_OF_QUAD = 0.33; // ~ minimal side size of quad relation by image height
|
||||
|
||||
// in millimeters
|
||||
protected double mmInBigSideOfSheet = 297; // A4 for default
|
||||
protected double mmInSmallSideOfSheet = 210; // A4 for default
|
||||
|
||||
double minSeedLength = 4; // !!!!!!!!!!!!!!!! SeedCounterApplication.config.getMinSeedLength()
|
||||
double maxSeedLength = 14; // !!!!!!!!!!!!!!!! SeedCounterApplication.config.getMaxSeedLength()
|
||||
|
||||
|
||||
// paddings for sheet transformation
|
||||
protected int widthPadding = -1;
|
||||
protected int heightPadding = -1;
|
||||
|
||||
public DetectionProcessor() {
|
||||
setSheetFormat(SheetFormat.A4);
|
||||
}
|
||||
|
||||
|
||||
public DetectionProcessor(SheetFormat sheetFormat) {
|
||||
setSheetFormat(sheetFormat);
|
||||
}
|
||||
|
||||
public void setSheetFormat(SheetFormat sheetFormat) {
|
||||
mmInBigSideOfSheet = sheetFormat.getBigSideSize();
|
||||
mmInSmallSideOfSheet = sheetFormat.getSmallSideSize();
|
||||
}
|
||||
|
||||
|
||||
// -- Detection --
|
||||
/**
|
||||
* Detection of white sheet.
|
||||
* @param image - source image.
|
||||
* @return detected quad of white sheet.
|
||||
* @throws SeedCounterProcessorException
|
||||
*/
|
||||
public Quad detectQuad(Mat image) throws QuadNotFoundException {
|
||||
// int width = image.cols();
|
||||
// int height = image.rows();
|
||||
|
||||
Mat gray = new Mat(image.rows(), image.cols(), CvType.CV_8UC1);
|
||||
Imgproc.cvtColor(image, gray, Imgproc.COLOR_RGBA2GRAY);
|
||||
Mat hulled = new Mat(image.rows(), image.cols(), CvType.CV_8UC1);
|
||||
Imgproc.threshold(gray, hulled, 120, 255, Imgproc.THRESH_BINARY ); /// 185!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
gray.release();
|
||||
|
||||
List<MatOfPoint> contours = new LinkedList<MatOfPoint>();
|
||||
Imgproc.findContours(hulled.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
|
||||
|
||||
MatOfPoint contour = ContoursFunctions.getMaxAreaContour(contours);
|
||||
|
||||
MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray());
|
||||
double perimeter = Imgproc.arcLength(contour2f, true);
|
||||
|
||||
MatOfPoint2f approx2f = new MatOfPoint2f();
|
||||
Imgproc.approxPolyDP(contour2f, approx2f, 0.02 * perimeter, true);
|
||||
|
||||
if(approx2f.size().height*approx2f.size().width==4) {
|
||||
return new Quad(approx2f.toArray());
|
||||
} else {
|
||||
throw new QuadNotFoundException(String.format("DetectionProcessor: the approximation has %s vertices.", approx2f.size().height*approx2f.size().width));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Detect contours of seeds in image.
|
||||
* @param image - source image.
|
||||
* @return list of contours.
|
||||
*/
|
||||
public List<PlanarObject> detectSeedContours(Mat image, double pixPerMM) throws SeedCounterProcessorException {
|
||||
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
|
||||
HSVBinarization seedContourBinarization = new SeedsBinarizationForWhiteSeeds();
|
||||
Mat hulled = seedContourBinarization.apply(image);
|
||||
|
||||
// Find contours
|
||||
Imgproc.findContours(hulled, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
hulled.release();
|
||||
|
||||
// Gauge seeds
|
||||
List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
MatOfPoint contour = contours.get(i);
|
||||
|
||||
double xScale = 1.0 / pixPerMM; // this may affect the output
|
||||
double yScale = 1.0 / pixPerMM; // this may affect the output
|
||||
|
||||
if(contour.cols()*contour.rows() > 5) { // There should be at least 5 points to fit the ellipse
|
||||
SeedDataContainer seedDataContainer = gaugeSeed(contour, xScale, yScale);
|
||||
|
||||
double seedLen = seedDataContainer.getLenght();
|
||||
if(minSeedLength<seedLen && seedLen<maxSeedLength) { // ElementProcessor.filterCircularContours(...)
|
||||
seedDataList.add(seedDataContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seedDataList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detect contours of seeds in image.
|
||||
* @param image - source image.
|
||||
* @return list of contours.
|
||||
*/
|
||||
public List<PlanarObject> detectSeedContours(Mat image) throws SeedCounterProcessorException {
|
||||
// init size
|
||||
//prepareSize(image.cols(), image.rows());
|
||||
if(-1 == widthPadding || -1 == heightPadding)
|
||||
throw new SeedCounterProcessorException("Padding isn't initialized.");
|
||||
|
||||
|
||||
int width = image.cols();
|
||||
int height = image.rows();
|
||||
|
||||
// Clean
|
||||
GrayBinarization binarization = new GrayBinarization(true, width*height/(210*297)); // ?????????????????????????????
|
||||
Mat hulled = binarization.apply(image);
|
||||
|
||||
// Find contours
|
||||
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
Imgproc.findContours(hulled, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
|
||||
/* // disabled for desktop
|
||||
if(SeedCounterApplication.config.getUseWatershed()) {
|
||||
double maxSeedAreaInMM2 = 78;
|
||||
double maxSeedAreaInPix = maxSeedAreaInMM2 * ((width+2*widthPadding)/mmInBigSideOfSheet);
|
||||
contours = clarifyContours(contours, maxSeedAreaInPix);
|
||||
}
|
||||
*/
|
||||
|
||||
double pixInMM = (width+2*widthPadding)/mmInBigSideOfSheet;
|
||||
|
||||
// Gauge seeds
|
||||
List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
MatOfPoint contour = contours.get(i);
|
||||
|
||||
// sheet size in pixels
|
||||
double sheetWidth = width+widthPadding*2;
|
||||
double sheetHeight = height+heightPadding*2;
|
||||
|
||||
// scales of X and Y pixels coordinates
|
||||
double yScale = mmInSmallSideOfSheet/sheetHeight;
|
||||
double xScale = mmInBigSideOfSheet/sheetWidth;
|
||||
|
||||
if(contour.cols()*contour.rows() > 5) { // There should be at least 5 points to fit the ellipse
|
||||
SeedDataContainer seedDataContainer = gaugeSeed(contour, xScale, yScale);
|
||||
|
||||
double seedLen = seedDataContainer.getLenght();
|
||||
if(minSeedLength<seedLen && seedLen<maxSeedLength) { // ElementProcessor.filterCircularContours(...)
|
||||
seedDataList.add(seedDataContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seedDataList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get info about seed on image (size in millimeters).
|
||||
* @param image - source image that containing seed.
|
||||
* @return information about seeds.
|
||||
* @throws SeedCounterException - if DetectionProcessor isn't initialized.
|
||||
*/
|
||||
private SeedDataContainer gaugeSeed(MatOfPoint contour, double xScale, double yScale) throws SeedCounterProcessorException {
|
||||
// get contour ellipse approximation
|
||||
MatOfPoint2f forFillEllipse = new MatOfPoint2f();
|
||||
contour.convertTo(forFillEllipse, CvType.CV_32FC2);
|
||||
RotatedRect rotatedRect = Imgproc.fitEllipse(forFillEllipse);
|
||||
Point[] points = new Point[4];
|
||||
rotatedRect.points(points);
|
||||
|
||||
// determine seed orientation related to sheet
|
||||
Vector seedVector = new Vector(points[0], points[1]);
|
||||
double dx = Math.abs(seedVector.x());
|
||||
double dy = Math.abs(seedVector.y());
|
||||
|
||||
double lamdaX = dx/(dx+dy);
|
||||
double lamdaY = dy/(dx+dy);
|
||||
|
||||
double lengthDistortionFactor = lamdaX*xScale + lamdaY*yScale;
|
||||
double widthDistortionFactor = lamdaX*yScale + lamdaY*xScale;
|
||||
|
||||
double seedLength = SimpleGeometry.distance(points[0], points[1])*lengthDistortionFactor;
|
||||
double seedWidth = SimpleGeometry.distance(points[1], points[2])*widthDistortionFactor;
|
||||
|
||||
// area
|
||||
double areaInPix = Imgproc.contourArea(contour, false);
|
||||
double area = areaInPix * lengthDistortionFactor*widthDistortionFactor;
|
||||
|
||||
// calculate mass center
|
||||
Rect seedRect = Imgproc.boundingRect(contour);
|
||||
double[] globalMassCenter = ContoursFunctions.massCenter(contour);
|
||||
double[] localMassCenter = new double[]{globalMassCenter[0]-seedRect.x, globalMassCenter[1]-seedRect.y};
|
||||
|
||||
// axis points
|
||||
double[] lengthAxisPoint1 = new double[]{(points[0].x+points[1].x)/2.0, (points[0].y+points[1].y)/2.0};
|
||||
double[] lengthAxisPoint2 = new double[]{(points[2].x+points[3].x)/2.0, (points[2].y+points[3].y)/2.0};
|
||||
double[] widthAxisPoint1 = new double[]{(points[1].x+points[2].x)/2.0, (points[1].y+points[2].y)/2.0};
|
||||
double[] widthAxisPoint2 = new double[]{(points[0].x+points[3].x)/2.0, (points[0].y+points[3].y)/2.0};
|
||||
|
||||
if(SimpleGeometry.distance(lengthAxisPoint1, lengthAxisPoint2)<SimpleGeometry.distance(widthAxisPoint1, widthAxisPoint2)) {
|
||||
double[] tmp1 = widthAxisPoint1;
|
||||
double[] tmp2 = widthAxisPoint2;
|
||||
|
||||
widthAxisPoint1 = lengthAxisPoint1;
|
||||
widthAxisPoint2 = lengthAxisPoint2;
|
||||
lengthAxisPoint1 = tmp1;
|
||||
lengthAxisPoint2 = tmp2;
|
||||
}
|
||||
|
||||
// calculate intersection point of length and width lines
|
||||
double[] lengthWidthIntersectPoint = SimpleGeometry.computeIntersect(lengthAxisPoint1, lengthAxisPoint2, widthAxisPoint1, widthAxisPoint2);
|
||||
lengthWidthIntersectPoint = new double[]{lengthWidthIntersectPoint[0]-seedRect.x, lengthWidthIntersectPoint[1]-seedRect.y}; // in seedRect
|
||||
|
||||
// distance from mass center to intersection point of length and width lines
|
||||
double distaceFromMassCenterToWidthLengthIntersectPoint = SimpleGeometry.distance(lengthWidthIntersectPoint, localMassCenter) * Math.sqrt(lengthDistortionFactor * widthDistortionFactor); // this may affect the output
|
||||
|
||||
SeedDataContainer seedDataContainer = new SeedDataContainer(contour, seedLength, seedWidth);
|
||||
seedDataContainer.setArea(area);
|
||||
seedDataContainer.setMassCenter(localMassCenter);
|
||||
seedDataContainer.setLengthWidthIntersectPoint(lengthWidthIntersectPoint);
|
||||
seedDataContainer.setDistaceFromMassCenterToWidthLengthIntersectPoint(distaceFromMassCenterToWidthLengthIntersectPoint);
|
||||
|
||||
seedDataContainer.setCircularityIndex(AdditionalIndexies.circularityIndex(contour));
|
||||
seedDataContainer.setRoundness(AdditionalIndexies.roundness(contour));
|
||||
seedDataContainer.setRugosity(AdditionalIndexies.rugosity(contour));
|
||||
seedDataContainer.setSolidity(AdditionalIndexies.solidity(contour));
|
||||
|
||||
Circle maxInscribed = AdditionalIndexies.getMaxInscribedCircle(contour);
|
||||
Circle minCircumscribed = AdditionalIndexies.getMinCircumscribedCircle(contour);
|
||||
|
||||
double equivalentAreaCircleRadius = Math.sqrt(areaInPix/Math.PI);
|
||||
seedDataContainer.setMaxInscribedMinCircumscribedCirclesDiametersRatio(maxInscribed.radius()/minCircumscribed.radius());
|
||||
seedDataContainer.setMaxInscribedCircleToEqAreaCircleRatio(maxInscribed.radius()/equivalentAreaCircleRadius);
|
||||
|
||||
return seedDataContainer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private List<MatOfPoint> clarifyContours(List<MatOfPoint> contours, double maxContourArea) {
|
||||
List<MatOfPoint> clarifyedContours = new LinkedList<MatOfPoint>();
|
||||
|
||||
for(MatOfPoint contour : contours) {
|
||||
boolean isGlue = isGlueContours(contour);
|
||||
|
||||
double contourArea = Imgproc.contourArea(contour);
|
||||
|
||||
if(isGlue || contourArea > maxContourArea) {
|
||||
Rect rect = Imgproc.boundingRect(contour);
|
||||
Rect roi = new Rect(rect.x, rect.y, rect.width, rect.height);
|
||||
|
||||
clarifyedContours.addAll(clarifyContour(contour, roi));
|
||||
} else {
|
||||
clarifyedContours.add(contour);
|
||||
}
|
||||
}
|
||||
|
||||
return clarifyedContours;
|
||||
}
|
||||
|
||||
private List<MatOfPoint> clarifyContour(MatOfPoint contour, Rect roi) {
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -roi.x, -roi.y);
|
||||
|
||||
Mat imprint = Mat.zeros(roi.size(), CvType.CV_8UC3);
|
||||
Imgproc.drawContours(imprint, Arrays.asList(shiftedContour), -1, new Scalar(255, 255, 255), -1);
|
||||
|
||||
List<MatOfPoint> separatedAndShiftedContours = watershedContours(imprint);
|
||||
List<MatOfPoint> separatedContours = new ArrayList<MatOfPoint>();
|
||||
|
||||
if(null == separatedAndShiftedContours) {
|
||||
separatedContours.add(contour);
|
||||
return separatedContours;
|
||||
}
|
||||
|
||||
for(MatOfPoint separatedAndShiftedContour : separatedAndShiftedContours) {
|
||||
separatedContours.add(shiftContour(separatedAndShiftedContour, roi.x, roi.y));
|
||||
}
|
||||
|
||||
return separatedContours;
|
||||
}
|
||||
|
||||
|
||||
private MatOfPoint shiftContour(MatOfPoint contour, int shiftX, int shiftY) {
|
||||
Point[] pointArr = new Point[contour.rows()*contour.cols()];
|
||||
|
||||
int i = 0;
|
||||
for(Point srcPoint : contour.toArray()) {
|
||||
pointArr[i++] = new Point(srcPoint.x+shiftX, srcPoint.y+shiftY);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = new MatOfPoint();
|
||||
shiftedContour.fromArray(pointArr);
|
||||
return shiftedContour;
|
||||
}
|
||||
|
||||
|
||||
private static List<MatOfPoint> watershedContours(Mat imprint) {
|
||||
Mat binarized = new Mat();
|
||||
Imgproc.cvtColor(imprint, binarized, Imgproc.COLOR_RGBA2GRAY);
|
||||
|
||||
// noise removal
|
||||
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
|
||||
Mat opening = new Mat();
|
||||
Imgproc.morphologyEx(binarized, opening, Imgproc.MORPH_OPEN, kernel);
|
||||
|
||||
// sure background area
|
||||
Mat sureBG = new Mat();
|
||||
Imgproc.dilate(opening, sureBG, kernel);
|
||||
|
||||
// finding sure foreground area
|
||||
Mat distTransform = new Mat();
|
||||
Imgproc.distanceTransform(opening, distTransform, Imgproc.CV_DIST_L2, 5);
|
||||
Mat sureFG = new Mat();
|
||||
//double epsilon = 0.55+((SeedCounterApplication.config.getWatershedSensivity()/100.0)*0.7)*(Core.minMaxLoc(distTransform)).maxVal; //from 0.5 to 0.8 // optimal = 0.65
|
||||
double epsilon = 0.55+((71/100.0)*0.7)*(Core.minMaxLoc(distTransform)).maxVal; //from 0.5 to 0.8 // optimal = 0.65
|
||||
|
||||
sureFG.convertTo(sureFG, CvType.CV_8UC1);
|
||||
Mat unknown = new Mat();
|
||||
Core.subtract(sureBG, sureFG, unknown);
|
||||
|
||||
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
Imgproc.findContours(sureFG.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
|
||||
if(contours.size() < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Mat markers = Mat.zeros(binarized.size(), CvType.CV_32SC1);
|
||||
markers.setTo(new Scalar(1));
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
Imgproc.drawContours(markers, Arrays.asList(contours.get(i)), -1, new Scalar(i+2), 1);
|
||||
Imgproc.drawContours(markers, Arrays.asList(contours.get(i)), -1, new Scalar(i+2), -1);
|
||||
}
|
||||
|
||||
Mat unknownInv = new Mat(unknown.size(), unknown.type());
|
||||
Core.bitwise_not(unknown, unknownInv);
|
||||
unknownInv.convertTo(unknownInv, CvType.CV_32SC1);
|
||||
Core.bitwise_and(markers, unknownInv, markers, unknown);
|
||||
|
||||
Imgproc.watershed(imprint, markers);
|
||||
|
||||
return separateMarkers(markers, contours.size());
|
||||
}
|
||||
|
||||
|
||||
private static List<MatOfPoint> separateMarkers(Mat markers, int markersNumber) {
|
||||
List<MatOfPoint> separatedContours = new ArrayList<MatOfPoint>();
|
||||
|
||||
for(int k=0; k<markersNumber; k++) {
|
||||
Mat imprint = Mat.zeros(markers.size(), CvType.CV_8UC1);
|
||||
|
||||
for(int i=0; i<markers.rows(); i++) {
|
||||
for(int j=0; j<markers.cols(); j++) {
|
||||
double pix = markers.get(i, j)[0];
|
||||
if((k+2) == pix) {
|
||||
imprint.put(i, j, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<MatOfPoint> contour = new ArrayList<MatOfPoint>();
|
||||
Imgproc.findContours(imprint, contour, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
separatedContours.addAll(contour);
|
||||
|
||||
imprint.release();
|
||||
}
|
||||
|
||||
return separatedContours;
|
||||
}
|
||||
|
||||
|
||||
private static boolean isGlueContours(MatOfPoint contour) {
|
||||
boolean isGlueContours = false;
|
||||
|
||||
MatOfPoint2f contour2f = new MatOfPoint2f( contour.toArray() );
|
||||
|
||||
double contourLength = Imgproc.arcLength(contour2f, true);
|
||||
double approxRadius = contourLength/(2*Math.PI);
|
||||
double[] massCenter = ContoursFunctions.massCenter(contour);
|
||||
|
||||
//int vectLength = (int)contourLength/64 > 4 ? (int)contourLength/64 : 4;
|
||||
int vectLength = 8;
|
||||
List<Vector> vectors = ContoursFunctions.getVectors(contour, vectLength);
|
||||
List<Pair<Point, Vector>> bendListIndxs = ContoursFunctions.getBendsIndxs(vectors);
|
||||
|
||||
{
|
||||
short toCenterVectorsCount = 0;
|
||||
if(bendListIndxs.size() >= 2) {
|
||||
for(Pair<Point, Vector> bend : bendListIndxs) {
|
||||
Vector vector = bend.getRight();
|
||||
|
||||
double val = SimpleGeometry.fromLineToPointDistance(vector, massCenter);
|
||||
if(val < approxRadius/2.0) {
|
||||
toCenterVectorsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(toCenterVectorsCount >= 3) {
|
||||
isGlueContours = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(bendListIndxs.size()>=2) {
|
||||
for(int i=0; i<bendListIndxs.size(); i++) {
|
||||
for(int j=i+1; j<bendListIndxs.size(); j++) {
|
||||
Vector v1 = bendListIndxs.get(i).getRight();
|
||||
Vector v2 = bendListIndxs.get(j).getRight();
|
||||
|
||||
|
||||
if(SimpleGeometry.angleInDegree(v1, v2) > 140) {
|
||||
if(SimpleGeometry.fromLineToPointDistance(v1, v2.p1()) < approxRadius/4.0) {
|
||||
isGlueContours = true;
|
||||
}
|
||||
|
||||
if(SimpleGeometry.fromLineToPointDistance(v2, v1.p1()) < approxRadius/4.0) {
|
||||
isGlueContours = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isGlueContours;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort contours by place on sheet in text order.
|
||||
* @param contours
|
||||
* @return
|
||||
*/
|
||||
public static List<MatOfPoint> sortSeedContours(List<MatOfPoint> contours, final int stringHeight) {
|
||||
List<MatOfPoint> sorted = new ArrayList<MatOfPoint>();
|
||||
int counts = contours.size();
|
||||
|
||||
while(counts != sorted.size()) {
|
||||
MatOfPoint next = Collections.min(contours, new Comparator<MatOfPoint>() {
|
||||
@Override
|
||||
public int compare(MatOfPoint o1, MatOfPoint o2) {
|
||||
double[] p1 = o1.get(0, 0);
|
||||
double[] p2 = o2.get(0, 0);
|
||||
|
||||
if(p1[1]<p2[1] && (p2[1]-p1[1])>stringHeight) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(p1[0]<p2[0]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
contours.remove(next);
|
||||
sorted.add(next);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
||||
// -- Extraction --
|
||||
/**
|
||||
* Getting transformed field of image by quad.
|
||||
* @param image - source image.
|
||||
* @param quad - quad for transformation.
|
||||
* @return transformed field.
|
||||
*/
|
||||
public Mat getTransformedField(Mat image, Quad quad) {
|
||||
widthPadding = (int) (image.cols()*0.02);
|
||||
heightPadding = (int) (image.rows()*0.02);
|
||||
|
||||
// init size
|
||||
//prepareSize(image.cols(), image.rows());
|
||||
|
||||
// Define the destination image
|
||||
Mat transformed = new Mat(image.rows(), image.cols(), image.type());
|
||||
|
||||
// Corners of the destination image
|
||||
Point[] quad_pts = new Point[4];
|
||||
quad_pts[0] = new Point(0, 0);
|
||||
quad_pts[1] = new Point(transformed.cols(), 0);
|
||||
quad_pts[2] = new Point(transformed.cols(), transformed.rows());
|
||||
quad_pts[3] = new Point(0, transformed.rows());
|
||||
|
||||
// Get transformation matrix
|
||||
Mat transmtx = Imgproc.getPerspectiveTransform(new MatOfPoint2f(quad.getPoints()), new MatOfPoint2f(quad_pts));
|
||||
|
||||
// Apply perspective transformation
|
||||
Imgproc.warpPerspective(image, transformed, transmtx, transformed.size());
|
||||
|
||||
transformed = transformed.submat(heightPadding, transformed.rows()-heightPadding,
|
||||
widthPadding, transformed.cols()-widthPadding);
|
||||
|
||||
return transformed;
|
||||
}
|
||||
|
||||
|
||||
public List<RotatedRect> getSeedsRotatedRects(List<MatOfPoint> contours) {
|
||||
List<RotatedRect> quads = new ArrayList<RotatedRect>();
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
MatOfPoint2f forFillEllipse = new MatOfPoint2f();
|
||||
contours.get(i).convertTo(forFillEllipse, CvType.CV_32FC2);
|
||||
RotatedRect rotatedRect = Imgproc.fitEllipse(forFillEllipse);
|
||||
|
||||
quads.add(rotatedRect);
|
||||
}
|
||||
|
||||
return quads;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get four lines of quad from given lines.
|
||||
* @param lines - given lines.
|
||||
* @return lines of quad.
|
||||
*/
|
||||
//private static Quad getQuad(Mat lines) {
|
||||
// // Filter corners
|
||||
// Point[] corners = new Point[4];
|
||||
//
|
||||
// if(ElementsProcessor.nearAngle(lines.get(0, 0), lines.get(0, 2))) {
|
||||
// corners[0] = ElementsProcessor.computeIntersectPoint(lines.get(0, 0), lines.get(0, 1));
|
||||
// corners[1] = ElementsProcessor.computeIntersectPoint(lines.get(0, 1), lines.get(0, 2));
|
||||
// corners[2] = ElementsProcessor.computeIntersectPoint(lines.get(0, 2), lines.get(0, 3));
|
||||
// corners[3] = ElementsProcessor.computeIntersectPoint(lines.get(0, 3), lines.get(0, 0));
|
||||
// } else
|
||||
// if (ElementsProcessor.nearAngle(lines.get(0, 0), lines.get(0, 1))) {
|
||||
// corners[0] = ElementsProcessor.computeIntersectPoint(lines.get(0, 0), lines.get(0, 2));
|
||||
// corners[1] = ElementsProcessor.computeIntersectPoint(lines.get(0, 2), lines.get(0, 1));
|
||||
// corners[2] = ElementsProcessor.computeIntersectPoint(lines.get(0, 1), lines.get(0, 3));
|
||||
// corners[3] = ElementsProcessor.computeIntersectPoint(lines.get(0, 3), lines.get(0, 0));
|
||||
// } else {
|
||||
// corners[0] = ElementsProcessor.computeIntersectPoint(lines.get(0, 0), lines.get(0, 2));
|
||||
// corners[1] = ElementsProcessor.computeIntersectPoint(lines.get(0, 2), lines.get(0, 3));
|
||||
// corners[2] = ElementsProcessor.computeIntersectPoint(lines.get(0, 3), lines.get(0, 1));
|
||||
// corners[3] = ElementsProcessor.computeIntersectPoint(lines.get(0, 1), lines.get(0, 0));
|
||||
// }
|
||||
//
|
||||
// ElementsProcessor.sortQuadCorners(corners);
|
||||
// Quad quad = new Quad(corners[0], corners[1], corners[2], corners[3]);
|
||||
// return quad;
|
||||
//}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
public class Drawing {
|
||||
// Colors
|
||||
private Scalar lineColor = new Scalar(0, 0, 255); // red
|
||||
private Scalar quadColor = new Scalar(255, 0, 0); // blue
|
||||
private Scalar seedColor = new Scalar(0, 255, 0); // green
|
||||
|
||||
|
||||
/**
|
||||
* Draw quad on image.
|
||||
* @param image - input image.
|
||||
* @param quad - quad for drawing.
|
||||
* @param drawCorners - is draw corners of quad?
|
||||
* @return input image with drawing quad.
|
||||
*/
|
||||
public void drawQuad(Mat image, Quad quad, boolean drawCorners) {
|
||||
Imgproc.line(image, quad.tl(), quad.tr(), quadColor, 3);
|
||||
Imgproc.line(image, quad.bl(), quad.br(), quadColor, 3);
|
||||
Imgproc.line(image, quad.tl(), quad.bl(), quadColor, 3);
|
||||
Imgproc.line(image, quad.tr(), quad.br(), quadColor, 3);
|
||||
|
||||
if(drawCorners) {
|
||||
Imgproc.circle(image, quad.tl(), 5, quadColor);
|
||||
Imgproc.circle(image, quad.tr(), 5, quadColor);
|
||||
Imgproc.circle(image, quad.br(), 5, quadColor);
|
||||
Imgproc.circle(image, quad.bl(), 5, quadColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Draw lines on image.
|
||||
* @param image - input image.
|
||||
* @param lines - lines for drawing.
|
||||
*/
|
||||
public void drawLines(Mat image, Mat lines) {
|
||||
for(int i=0; i<lines.cols(); i++) {
|
||||
double[] line = lines.get(0, i);
|
||||
Imgproc.line(image, new Point(line[0], line[1]), new Point(line[2], line[3]), lineColor, 3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw contours on image.
|
||||
* @param img - input image.
|
||||
* @param contours - contours for drawing.
|
||||
*/
|
||||
public void drawContours(Mat img, List<MatOfPoint> contours) {
|
||||
Imgproc.drawContours(img, contours, -1, seedColor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
/**
|
||||
* DELETE IT!
|
||||
* Processor for elementary operations with points, lines and etc.
|
||||
* @author Komyshev
|
||||
* v.1.0. (20.12.23)
|
||||
*/
|
||||
public class ElementsProcessor {
|
||||
/**
|
||||
* Is targeting point near to middle normal of vector defined by two points.
|
||||
* @param p1 - first point.
|
||||
* @param p2 - second point.
|
||||
* @param p - targeting point.
|
||||
* @param sizeOfRegion - threshold of close.
|
||||
* @return true if targeting point lies on middle normal of given points and false otherwise.
|
||||
* @deprecated !!! Error: function calculates incorrect distance from point to line: |f(Point.x) - Point.y| is not correct distance.
|
||||
*/
|
||||
// @Deprecated
|
||||
// private static boolean isOnMiddleNormal(Point p1, Point p2, Point p, double sizeOfRegion) {
|
||||
// // Middle point
|
||||
// double xMdl = (p1.x+p2.x)/2.0;
|
||||
// double yMdl = (p1.y+p2.y)/2.0;
|
||||
//
|
||||
// // y(x) = k*x + b;
|
||||
// double k = (double)(p2.x-p1.x)/(double)(p2.y-p1.y);
|
||||
// double b = yMdl + k*(double)xMdl;
|
||||
// double y = k*(double)p.x + b; //??
|
||||
//
|
||||
// // y - value of function in p.x - should be near to p.y
|
||||
// if(Math.abs(p.y - y)<sizeOfRegion)
|
||||
// return true;
|
||||
// else
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Is targeting point near to middle normal of vector defined by two points.
|
||||
* @param p1 - first point.
|
||||
* @param p2 - second point.
|
||||
* @param p - targeting point.
|
||||
* @param sizeOfRegion - threshold of close.
|
||||
* @return true if targeting point lies on middle normal of given points and false otherwise.
|
||||
* @deprecated !!! Error: function calculates incorrect distance from point to line: |f(Point.x) - Point.y| is not correct distance.
|
||||
*/
|
||||
// @Deprecated
|
||||
// private static boolean isOnMiddleNormal(double[] p1, double[] p2, double[] p, double sizeOfRegion) {
|
||||
// // Middle point
|
||||
// double xMdl = (p1[0]+p2[0])/2.0;
|
||||
// double yMdl = (p1[1]+p2[1])/2.0;
|
||||
//
|
||||
// // y(x) = k*x + b;
|
||||
// double k = (double)(p2[0]-p1[0])/(double)(p2[1]-p1[1]);
|
||||
// double b = yMdl + k*(double)xMdl;
|
||||
// double y = k*(double)p[0] + b; //??
|
||||
//
|
||||
// // y - value of function in p.x - should be near to p.y
|
||||
// if(Math.abs(p[1] - y)<sizeOfRegion)
|
||||
// return true;
|
||||
// else
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Filter out contours that are larger or smaller than the specified size values.
|
||||
* @param contours
|
||||
* @param minSeedLength
|
||||
* @param maxSeedLength
|
||||
* @return
|
||||
*/
|
||||
// private static List<MatOfPoint> filterCircularContours(List<MatOfPoint> contours, double minSeedLength, double maxSeedLength) {
|
||||
// List<MatOfPoint> filtered = new ArrayList<MatOfPoint>();
|
||||
//
|
||||
// for(MatOfPoint contour : contours) {
|
||||
// // Filter big length object
|
||||
// if(containsTwoDistantPoints(contour, maxSeedLength))
|
||||
// continue;
|
||||
//
|
||||
// // Filter small length object
|
||||
// if(!containsTwoDistantPoints(contour, minSeedLength))
|
||||
// continue;
|
||||
//
|
||||
// filtered.add(contour);
|
||||
// }
|
||||
//
|
||||
// return filtered;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Calculate whether mat contains two point with distance over threshold.
|
||||
* @param mat - matrix of points.
|
||||
* @param thres - threshold of distance.
|
||||
* @return true if contains, and false otherwise.
|
||||
*/
|
||||
// private static boolean containsTwoDistantPoints(MatOfPoint mat, double thres) {
|
||||
// for(int i=0; i<mat.rows(); i++) {
|
||||
// for(int j=0; j<mat.cols(); j++) {
|
||||
// for(int ii=0; ii<mat.rows(); ii++) {
|
||||
// for(int jj=0; jj<mat.cols(); jj++) {
|
||||
// if(SimpleGeometry.distance(mat.get(i, j), mat.get(ii, jj)) > thres) return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Find two points which have max distance between them.
|
||||
* @param points - mat of points for search.
|
||||
* @return two points which max distance between them.
|
||||
*/
|
||||
// private static double[][] findMaxDistancePoints(MatOfPoint points) {
|
||||
// double maxDistance = 0;
|
||||
// double[][] maxPoints = new double[2][2];
|
||||
//
|
||||
// for(int i=0; i<points.rows(); i++) {
|
||||
// for(int j=i+1; j<points.rows(); j++) {
|
||||
// double[] p1 = points.get(i, 0);
|
||||
// double[] p2 = points.get(j, 0);
|
||||
//
|
||||
// double distance = SimpleGeometry.distance(p1, p2);
|
||||
// if(distance>maxDistance) {
|
||||
// maxDistance = distance;
|
||||
// maxPoints[0] = p1;
|
||||
// maxPoints[1] = p2;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return maxPoints;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Calculate whether list contains two point with distance over threshold.
|
||||
* @param list - list of points.
|
||||
* @param thres - threshold of distance.
|
||||
* @return true if contains, and false otherwise.
|
||||
*/
|
||||
// private static boolean containsTwoDistantPoints(List<double[]> list, double thres) {
|
||||
// for(int i=0; i<list.size(); i++) {
|
||||
// for(int j=i+1; j<list.size(); j++) {
|
||||
// if(SimpleGeometry.distance(list.get(i), list.get(j))>thres) return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Calculate whether two mats contains two near point.
|
||||
* @param mat1 - first mat of point.
|
||||
* @param mat2 - second mat of point.
|
||||
* @param thres - threshold of distance.
|
||||
* @return true if contains two near point, and false otherwise.
|
||||
*/
|
||||
// private static boolean containsNearPoints(MatOfPoint mat1, MatOfPoint mat2, double thres) {
|
||||
// // for each point in mat1
|
||||
// for(int i=0; i<mat1.rows(); i++) {
|
||||
// for(int j=0; j<mat1.cols(); j++) {
|
||||
// // for each point in mat1
|
||||
// for(int ii=0; ii<mat2.rows(); ii++) {
|
||||
// for(int jj=0; jj<mat2.cols(); jj++) {
|
||||
//
|
||||
// if(SimpleGeometry.distance(mat1.get(i, j), mat2.get(ii, jj))<thres)
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
|
||||
/**
|
||||
* Интерфейс области изображения.
|
||||
* Область изображения характеризуется контуром.
|
||||
* Для такой области можно вычислять цветовые характеристики.
|
||||
* @author Komyshev
|
||||
*/
|
||||
|
||||
public interface ImageArea {
|
||||
public MatOfPoint getContour();
|
||||
|
||||
public MatOfPoint2f getContourApprox();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import org.wheatdb.seedcounter.WritableData;
|
||||
|
||||
public interface PlanarObject extends WritableData, ImageArea {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
public interface Processor {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
public class SeedCounterProcessorException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SeedCounterProcessorException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SeedCounterProcessorException(String mes) {
|
||||
super(mes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class Utils {
|
||||
/**
|
||||
* Do action for each element of mat.
|
||||
* @param mat - matrix of element.
|
||||
* @param act - action.
|
||||
*/
|
||||
// public static void forEach(Mat mat, Action act) {
|
||||
// for(int i=0; i<mat.rows(); i++) {
|
||||
// for(int j=0; j<mat.cols(); j++) {
|
||||
// double[] point = mat.get(i, j);
|
||||
// act.doAction(point);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
//interface Action {
|
||||
// void doAction(double[] point);
|
||||
//}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.wheatdb.seedcounter.processor.ex;
|
||||
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
|
||||
public class QuadNotFoundException extends SeedCounterProcessorException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public QuadNotFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public QuadNotFoundException(String mes) {
|
||||
super(mes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import org.opencv.calib3d.Calib3d;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
|
||||
public class GrayBinarization implements IFilter {
|
||||
// Outer parameters:
|
||||
private boolean inv = true;
|
||||
|
||||
public static int blockSize = 3;
|
||||
public static int C = 1;
|
||||
|
||||
public static float medianBlur = 0; //8*log11(x/22530,7)
|
||||
public static int maxSpecklesSize = 0;
|
||||
|
||||
private final double maxDiff = 20;
|
||||
//private Size objSizes;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public GrayBinarization(boolean withSeeds, double dpmm) {
|
||||
//this.withSeeds = withSeeds;
|
||||
//this.dpmm = dpmm;
|
||||
|
||||
|
||||
// Recalculate parameters
|
||||
//int resolution = height*width;
|
||||
if(dpmm<0.340339907) { //~ 17649px(to 26473) 0.22671156004 dpmm
|
||||
blockSize = withSeeds ? 5 : 3;
|
||||
C = withSeeds ? 9 : 15;
|
||||
medianBlur = withSeeds ? 0 : 0;
|
||||
maxSpecklesSize = withSeeds ? 4 : 9;
|
||||
} else
|
||||
if(dpmm<1.362089145) { //~ 70818px (to 106227) 0.91008497675 dpmm
|
||||
blockSize = withSeeds ? 7 : 5;
|
||||
C = withSeeds ? 11 : 7;
|
||||
medianBlur = withSeeds ? 0 : 3;
|
||||
maxSpecklesSize = withSeeds ? 6 : 30;
|
||||
} else
|
||||
if(dpmm<5.436050986) { //~ 282828px (to 424242) 3.62486772487 dpmm
|
||||
blockSize = withSeeds ? 15 : 7;
|
||||
C = withSeeds ? 5 : 7;
|
||||
medianBlur = withSeeds ? 3 : 3;
|
||||
maxSpecklesSize = withSeeds ? 15 : 80;
|
||||
} else
|
||||
if(dpmm<21.79067661) { //~ 1132200px (to 1698300) 14.5303992304 dpmm
|
||||
blockSize = withSeeds ? 27 : 7; // old 35 witout seeds
|
||||
C = withSeeds ? 5 : 7;
|
||||
medianBlur = withSeeds ? 5 : 5;
|
||||
maxSpecklesSize = withSeeds ? 60 : 300;
|
||||
} else
|
||||
if(dpmm<60.08225108) {
|
||||
blockSize = withSeeds ? 71 : 25;
|
||||
C = withSeeds ? 7 : 3;
|
||||
medianBlur = withSeeds ? 7 : 7;
|
||||
maxSpecklesSize = withSeeds ? 200 : 1500;
|
||||
} else
|
||||
if(dpmm<90.0) {
|
||||
blockSize = withSeeds ? 131 : 35;
|
||||
C = withSeeds ? 7 : 7;
|
||||
medianBlur = withSeeds ? 0 : 0;
|
||||
maxSpecklesSize = withSeeds ? 200 : 1500;
|
||||
} else
|
||||
if(dpmm<150.0) {
|
||||
blockSize = withSeeds ? 171 : 35;
|
||||
C = withSeeds ? 5 : 7;
|
||||
medianBlur = withSeeds ? 0 : 0;
|
||||
maxSpecklesSize = withSeeds ? 200 : 2500;
|
||||
} else
|
||||
if(dpmm<348.7240019) { // >~ 18106549px (to 27159823) 232.486387686 dpmm
|
||||
blockSize = withSeeds ? 201 : 33;
|
||||
C = withSeeds ? 9 : 9;
|
||||
medianBlur = withSeeds ? 0 : 5;
|
||||
maxSpecklesSize = withSeeds ? 340 : 3500;
|
||||
} else { // >~ 72419094 px 930.111992945 dpmm
|
||||
blockSize = withSeeds ? 3551 : 71; // 251
|
||||
C = withSeeds ? 41 : 27 ; // 15
|
||||
medianBlur = withSeeds ? 7 : 7;
|
||||
maxSpecklesSize = withSeeds ? 2500 : 5000;
|
||||
}
|
||||
|
||||
// 1551
|
||||
// 39
|
||||
|
||||
//medianBlur = medianBlur%2 == 0 ? medianBlur-1 : medianBlur; // should be odd
|
||||
//blockSize = blockSize%2 == 0 ? blockSize+1 : blockSize; // should be odd
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mat apply(Mat img) {
|
||||
int height = img.height();
|
||||
int width = img.width();
|
||||
|
||||
|
||||
Mat blured;
|
||||
if(0!=medianBlur) {
|
||||
blured = new Mat(img.rows(), img.cols(), CvType.CV_8UC3);
|
||||
Imgproc.medianBlur(img, blured, (int)medianBlur);
|
||||
img.release();
|
||||
} else {
|
||||
blured = img;
|
||||
}
|
||||
|
||||
// to gray and threshold
|
||||
Mat gray = new Mat(height, width, CvType.CV_8UC1);
|
||||
Imgproc.cvtColor(blured, gray, Imgproc.COLOR_RGBA2GRAY);
|
||||
blured.release();
|
||||
|
||||
Mat outImg = new Mat(height, width, CvType.CV_8UC1);
|
||||
Imgproc.adaptiveThreshold(gray, outImg, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
inv?Imgproc.THRESH_BINARY_INV:Imgproc.THRESH_BINARY, blockSize, C);
|
||||
gray.release();
|
||||
|
||||
|
||||
try {
|
||||
Calib3d.filterSpeckles(outImg, inv? 0:255, maxSpecklesSize, maxDiff);
|
||||
} catch(Exception e) {
|
||||
int blur = (maxSpecklesSize/150);
|
||||
Imgproc.medianBlur(outImg, outImg, blur%2==0 ? blur+1 : blur);
|
||||
}
|
||||
|
||||
|
||||
//String text = "\nblockSize="+blockSize+"\nC="+C+"\nmedianBlur="+medianBlur+"\nmaxSpecklesSize=" + maxSpecklesSize;
|
||||
//System.out.println(text);
|
||||
//Core.putText(outImg, text, new Point(outImg.cols()/10, outImg.rows()/10), 2, 0.3, new Scalar(0));
|
||||
|
||||
return outImg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set invertion for binaryzation.
|
||||
* @param inv - true for invertion and false otherwise.
|
||||
*/
|
||||
public void setInverting(boolean inv) {
|
||||
this.inv = inv;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
|
||||
public class HSVBinarization {
|
||||
private final List<Pair<Scalar, Scalar>> targetsAndRanges;
|
||||
|
||||
public HSVBinarization(List<Pair<Scalar, Scalar>> targetsAndRanges) {
|
||||
this.targetsAndRanges = targetsAndRanges;
|
||||
}
|
||||
|
||||
|
||||
public Mat apply(Mat input) {
|
||||
Mat hsvImg = new Mat(input.size(), CvType.CV_8UC3);
|
||||
Imgproc.cvtColor(input, hsvImg, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat output = Mat.zeros(input.size(), CvType.CV_8UC1);
|
||||
for(int i=0; i<hsvImg.rows(); i++) {
|
||||
for(int j=0; j<hsvImg.cols(); j++) {
|
||||
double[] value = hsvImg.get(i, j);
|
||||
|
||||
boolean hitting = false;
|
||||
for(Pair<Scalar, Scalar> targetAndRange : targetsAndRanges) {
|
||||
Scalar target = targetAndRange.getLeft();
|
||||
Scalar range = targetAndRange.getRight();
|
||||
|
||||
if(Math.abs(value[0]-target.val[0])<range.val[0] && Math.abs(value[1]-target.val[1])<range.val[1] && Math.abs(value[2]-target.val[2])<range.val[2])
|
||||
hitting = true;
|
||||
}
|
||||
|
||||
if(hitting) {
|
||||
output.put(i, j, 255);
|
||||
} else {
|
||||
output.put(i, j, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hsvImg.release();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public Mat applyToRoi(Mat input, Rect roi) {
|
||||
Mat hsvImg = new Mat(input.size(), CvType.CV_8UC3);
|
||||
Imgproc.cvtColor(input, hsvImg, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat output = Mat.zeros(input.size(), CvType.CV_8UC1);
|
||||
for(int i=roi.y; i<roi.y+roi.height && i<hsvImg.rows(); i++) {
|
||||
for(int j=roi.x; j<roi.x+roi.width && j<hsvImg.cols(); j++) {
|
||||
double[] value = hsvImg.get(i, j);
|
||||
|
||||
boolean hitting = false;
|
||||
for(Pair<Scalar, Scalar> targetAndRange : targetsAndRanges) {
|
||||
Scalar target = targetAndRange.getLeft();
|
||||
Scalar range = targetAndRange.getRight();
|
||||
|
||||
if(Math.abs(value[0]-target.val[0])<range.val[0] && Math.abs(value[1]-target.val[1])<range.val[1] && Math.abs(value[2]-target.val[2])<range.val[2])
|
||||
hitting = true;
|
||||
}
|
||||
|
||||
if(hitting) {
|
||||
output.put(i, j, 255);
|
||||
} else {
|
||||
output.put(i, j, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hsvImg.release();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public interface IFilter {
|
||||
Mat apply(Mat img);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
public class SeedsBinarization extends HSVBinarization {
|
||||
static List<Pair<Scalar, Scalar>> targetsAndRanges = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
static {
|
||||
Scalar target1 = new Scalar(12, 153, 76.5);
|
||||
Scalar range1 = new Scalar(30, 80, 120);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(19.125, 147.9, 153);
|
||||
Scalar range2 = new Scalar(30, 80, 120);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
Scalar target3 = new Scalar(22.7, 119.85, 193.8);
|
||||
Scalar range3 = new Scalar(30, 30, 100);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(32.6, 71.4, 204);
|
||||
Scalar range4 = new Scalar(30, 30, 40);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
Scalar target5 = new Scalar(20, 66, 71);
|
||||
Scalar range5 = new Scalar(20, 20, 10);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target5, range5));
|
||||
}
|
||||
|
||||
|
||||
public SeedsBinarization() {
|
||||
super(targetsAndRanges);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
public class SeedsBinarization2 extends HSVBinarization {
|
||||
static List<Pair<Scalar, Scalar>> targetsAndRanges = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
static {
|
||||
Scalar target1 = new Scalar(161.0, 81.0, 144.0);
|
||||
Scalar range1 = new Scalar(29.0, 43.0, 108.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(161.0, 72.0, 142.0);
|
||||
Scalar range2 = new Scalar(27.0, 38.0, 109.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
Scalar target3 = new Scalar(15.0, 138.0, 207.0);
|
||||
Scalar range3 = new Scalar(41.0, 94.0, 144.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(161.0, 72.0, 142.0);
|
||||
Scalar range4 = new Scalar(27.0, 38.0, 109.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
Scalar target5 = new Scalar(37.0, 231.0, 41.0);
|
||||
Scalar range5 = new Scalar(51.0, 198.0, 145.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target5, range5));
|
||||
}
|
||||
|
||||
|
||||
public SeedsBinarization2() {
|
||||
super(targetsAndRanges);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
public class SeedsBinarizationForWhiteSeeds extends HSVBinarization {
|
||||
static List<Pair<Scalar, Scalar>> targetsAndRanges = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
static {
|
||||
Scalar target1 = new Scalar(19.125, 147.9, 153);
|
||||
Scalar range1 = new Scalar(30, 120, 120);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(32.6, 71.4, 204);
|
||||
Scalar range2 = new Scalar(30, 30, 40);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
}
|
||||
|
||||
|
||||
public SeedsBinarizationForWhiteSeeds() {
|
||||
super(targetsAndRanges);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class WhiteBalanser {
|
||||
|
||||
public static void whiteBalance(Mat img) {
|
||||
double[] rowR = new double[img.cols()*img.rows()];
|
||||
double[] rowG = new double[img.cols()*img.rows()];
|
||||
double[] rowB = new double[img.cols()*img.rows()];
|
||||
|
||||
for(int i=0; i<img.rows(); i++) {
|
||||
for(int j=0; j<img.cols(); j++) {
|
||||
double[] pixel = img.get(i, j);
|
||||
|
||||
rowR[i*img.cols()+j] = pixel[0];
|
||||
rowG[i*img.cols()+j] = pixel[1];
|
||||
rowB[i*img.cols()+j] = pixel[2];
|
||||
}
|
||||
}
|
||||
|
||||
double[] sortedRowR = rowR.clone();
|
||||
double[] sortedRowG = rowG.clone();
|
||||
double[] sortedRowB = rowB.clone();
|
||||
|
||||
Arrays.sort(sortedRowR);
|
||||
Arrays.sort(sortedRowG);
|
||||
Arrays.sort(sortedRowB);
|
||||
|
||||
double percentForBalance = 0.01;
|
||||
|
||||
double rPerc05 = percentile(sortedRowR, percentForBalance);
|
||||
double rPerc95 = percentile(sortedRowR, 100 - percentForBalance);
|
||||
|
||||
double gPerc05 = percentile(sortedRowG, percentForBalance);
|
||||
double gPerc95 = percentile(sortedRowG, 100 - percentForBalance);
|
||||
|
||||
double bPerc05 = percentile(sortedRowB, percentForBalance);
|
||||
double bPerc95 = percentile(sortedRowB, 100 - percentForBalance);
|
||||
|
||||
|
||||
for(int l=0; l < img.cols()*img.rows(); l++) {
|
||||
double rValueBalanced = (rowR[l] - rPerc05) * 255.0 / (rPerc95 - rPerc05);
|
||||
double gValueBalanced = (rowG[l] - gPerc05) * 255.0 / (gPerc95 - gPerc05);
|
||||
double bValueBalanced = (rowB[l] - bPerc05) * 255.0 / (bPerc95 - bPerc05);
|
||||
|
||||
rowR[l] = rValueBalanced < 0 ? 0 : (rValueBalanced > 255 ? 255 : rValueBalanced);
|
||||
rowG[l] = gValueBalanced < 0 ? 0 : (gValueBalanced > 255 ? 255 : gValueBalanced);
|
||||
rowB[l] = bValueBalanced < 0 ? 0 : (bValueBalanced > 255 ? 255 : bValueBalanced);
|
||||
}
|
||||
|
||||
|
||||
for(int i=0; i < img.rows(); i++) {
|
||||
for(int j=0; j < img.cols(); j++) {
|
||||
img.put(i, j, new double[] {rowR[i*img.cols()+j], rowG[i*img.cols()+j], rowB[i*img.cols()+j]});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static double percentile(double[] array, double percentile) {
|
||||
int nArray = array.length;
|
||||
|
||||
double nPercent = (nArray + 1) * percentile / 100.0;
|
||||
|
||||
if(nPercent == 1) {
|
||||
return array[0];
|
||||
} else if(nPercent == nArray) {
|
||||
return array[nArray - 1];
|
||||
} else {
|
||||
int intNPercent = (int)nPercent;
|
||||
double d = nPercent - intNPercent;
|
||||
return array[intNPercent - 1] + d * (array[intNPercent] - array[intNPercent - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class WhiteBalanser {
|
||||
|
||||
public static void whiteBalance(Mat img) {
|
||||
double[] rowR = new double[img.cols()*img.rows()];
|
||||
double[] rowG = new double[img.cols()];
|
||||
double[] rowB = new double[img.cols()];
|
||||
|
||||
for(int i=0; i<img.rows(); i++) {
|
||||
|
||||
|
||||
//Mat mRow = img.row(i);
|
||||
|
||||
for(int j=0; j<img.cols(); j++) {
|
||||
double[] pixel = img.get(i, j);
|
||||
|
||||
rowR[j] = pixel[0];
|
||||
rowG[j] = pixel[1];
|
||||
rowB[j] = pixel[2];
|
||||
}
|
||||
}
|
||||
|
||||
double[] sortedRowR = rowR.clone();
|
||||
double[] sortedRowG = rowG.clone();
|
||||
double[] sortedRowB = rowB.clone();
|
||||
|
||||
Arrays.sort(sortedRowR);
|
||||
Arrays.sort(sortedRowG);
|
||||
Arrays.sort(sortedRowB);
|
||||
|
||||
double percentForBalance = 0.6;
|
||||
|
||||
double rPerc05 = percentile(sortedRowR, percentForBalance);
|
||||
double rPerc95 = percentile(sortedRowR, 100 - percentForBalance);
|
||||
|
||||
double gPerc05 = percentile(sortedRowG, percentForBalance);
|
||||
double gPerc95 = percentile(sortedRowG, 100 - percentForBalance);
|
||||
|
||||
double bPerc05 = percentile(sortedRowB, percentForBalance);
|
||||
double bPerc95 = percentile(sortedRowB, 100 - percentForBalance);
|
||||
|
||||
|
||||
for(int j=0; j < img.cols(); j++) {
|
||||
double rValueBalanced = (rowR[j] - rPerc05) * 255.0 / (rPerc95 - rPerc05);
|
||||
double gValueBalanced = (rowG[j] - gPerc05) * 255.0 / (gPerc95 - gPerc05);
|
||||
double bValueBalanced = (rowB[j] - bPerc05) * 255.0 / (bPerc95 - bPerc05);
|
||||
|
||||
rowR[j] = rValueBalanced < 0 ? 0 : (rValueBalanced > 255 ? 255 : rowR[j]);
|
||||
rowG[j] = gValueBalanced < 0 ? 0 : (gValueBalanced > 255 ? 255 : rowG[j]);
|
||||
rowB[j] = bValueBalanced < 0 ? 0 : (bValueBalanced > 255 ? 255 : rowB[j]);
|
||||
}
|
||||
|
||||
for(int j=0; j < img.cols(); j++) {
|
||||
img.put(i, j, new double[] {rowR[j], rowG[j], rowB[j]});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static double percentile(double[] array, double percentile) {
|
||||
int nArray = array.length;
|
||||
|
||||
double nPercent = (nArray + 1) * percentile / 100.0;
|
||||
|
||||
if(nPercent == 1) {
|
||||
return array[0];
|
||||
} else if(nPercent == nArray) {
|
||||
return array[nArray - 1];
|
||||
} else {
|
||||
int intNPercent = (int)nPercent;
|
||||
double d = nPercent - intNPercent;
|
||||
return array[intNPercent - 1] + d * (array[intNPercent] - array[intNPercent - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.wheatdb.seedcounter.processor.subjects;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.ex.QuadNotFoundException;
|
||||
|
||||
/**
|
||||
* Quad.
|
||||
* @author Komyshev
|
||||
* @version 0.2.
|
||||
*/
|
||||
public class Quad {
|
||||
private Point tl = null;
|
||||
private Point tr = null;
|
||||
private Point br = null;
|
||||
private Point bl = null;
|
||||
|
||||
public Quad(Point[] points) throws QuadNotFoundException {
|
||||
double cx = (points[0].x + points[1].x + points[2].x + points[3].x)/4.0;
|
||||
double cy = (points[0].y + points[1].y + points[2].y + points[3].y)/4.0;
|
||||
|
||||
definePoint(points[0], cx, cy);
|
||||
definePoint(points[1], cx, cy);
|
||||
definePoint(points[2], cx, cy);
|
||||
definePoint(points[3], cx, cy);
|
||||
|
||||
if(tl == tr || tr == br || br == bl || bl == tl || tl == br || tr == bl || tl == bl)
|
||||
throw new QuadNotFoundException("Quad: Weird rectangle");
|
||||
}
|
||||
|
||||
private void definePoint(Point p, double cx, double cy) {
|
||||
if(p.x<cx) {
|
||||
if(p.y<cy) {
|
||||
tl = p;
|
||||
} else {
|
||||
bl = p;
|
||||
}
|
||||
} else {
|
||||
if(p.y<cy) {
|
||||
tr = p;
|
||||
} else {
|
||||
br = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Quad(Point tl, Point tr, Point br, Point bl) {
|
||||
this.tl = tl;
|
||||
this.tr = tr;
|
||||
this.br = br;
|
||||
this.bl = bl;
|
||||
}
|
||||
|
||||
public Quad(Point tl, Point br) {
|
||||
this.tl = tl;
|
||||
this.br = br;
|
||||
|
||||
this.tr = new Point(br.x, tl.y);
|
||||
this.bl = new Point(tl.x, br.y);
|
||||
}
|
||||
|
||||
public Quad(double[][] points) {
|
||||
this.tl = new Point(points[0][0], points[0][1]);
|
||||
this.tr = new Point(points[1][0], points[1][1]);
|
||||
this.br = new Point(points[2][0], points[2][1]);
|
||||
this.bl = new Point(points[3][0], points[3][1]);
|
||||
}
|
||||
|
||||
public Quad(double tlx, double tly, double trx, double tryy, double blx, double bly, double brx, double bry) {
|
||||
this.tl = new Point(tlx, tly);
|
||||
this.tr = new Point(trx, tryy);
|
||||
this.br = new Point(blx, bly);
|
||||
this.bl = new Point(brx, bry);
|
||||
}
|
||||
|
||||
public Point[] getPoints() {
|
||||
Point[] points = {tl, tr, br, bl};
|
||||
return points;
|
||||
}
|
||||
|
||||
public double getBigSideSize() {
|
||||
if(Math.abs(tl.x-tr.x) > Math.abs(tl.y-bl.y)) {
|
||||
return tl.x-tr.x;
|
||||
} else {
|
||||
return tl.y-bl.y;
|
||||
}
|
||||
}
|
||||
|
||||
public double getSmallSideSize() {
|
||||
if(Math.abs(tl.x-tr.x) > Math.abs(tl.y-bl.y)) {
|
||||
return tl.y-bl.y;
|
||||
} else {
|
||||
return tl.x-tr.x;
|
||||
}
|
||||
}
|
||||
|
||||
public Point tl() {
|
||||
return tl;
|
||||
}
|
||||
|
||||
public Point tr() {
|
||||
return tr;
|
||||
}
|
||||
|
||||
public Point bl() {
|
||||
return bl;
|
||||
}
|
||||
|
||||
public Point br() {
|
||||
return br;
|
||||
}
|
||||
|
||||
public boolean isInside(Point point) {
|
||||
double minX = Math.min(Math.min(tl.x, tr.x), Math.min(bl.x, br.x));
|
||||
double minY = Math.min(Math.min(tl.y, tr.y), Math.min(bl.y, br.y));
|
||||
double maxX = Math.max(Math.max(tl.x, tr.x), Math.max(bl.x, br.x));
|
||||
double maxY = Math.max(Math.max(tl.y, tr.y), Math.max(bl.y, br.y));
|
||||
return point.x >= minX && point.x <= maxX && point.y >= minY && point.y <= maxY;
|
||||
}
|
||||
|
||||
public double getArea() {
|
||||
double dx1 = tr.x - tl.x;
|
||||
double dy1 = tr.y - tl.y;
|
||||
double dx2 = bl.x - tl.x;
|
||||
double dy2 = bl.y - tl.y;
|
||||
|
||||
return Math.abs(dx1 * dy2 - dy1 * dx2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate relational points coordinates at source image for distinct image. The result is quad that has corresponding coordinates in distinct image.
|
||||
* @param from is the source image.
|
||||
* @param to is the distinct image.
|
||||
* @param srcQuad is the quad for recalculate.
|
||||
* @return recalculated quad.
|
||||
*/
|
||||
public static Quad recalculateQuadPoints(Mat from, Mat to, Quad srcQuad) {
|
||||
int width = from.cols();
|
||||
int height = from.rows();
|
||||
|
||||
int newWidth = to.cols();
|
||||
int newHeight = to.rows();
|
||||
|
||||
Point newTl = new Point(srcQuad.tl().x/width*newWidth, srcQuad.tl().y/height*newHeight);
|
||||
Point newTr = new Point(srcQuad.tr().x/width*newWidth, srcQuad.tr().y/height*newHeight);
|
||||
Point newBl = new Point(srcQuad.bl().x/width*newWidth, srcQuad.bl().y/height*newHeight);
|
||||
Point newBr = new Point(srcQuad.br().x/width*newWidth, srcQuad.br().y/height*newHeight);
|
||||
|
||||
return new Quad(newTl, newTr, newBr, newBl);
|
||||
}
|
||||
|
||||
public Mat getTransformedField(Mat image) {
|
||||
// Define the destination image
|
||||
Mat transformed = new Mat(image.rows(), image.cols(), image.type());
|
||||
|
||||
// Corners of the destination image
|
||||
Point[] quad_pts = new Point[4];
|
||||
quad_pts[0] = new Point(0, 0);
|
||||
quad_pts[1] = new Point(transformed.cols(), 0);
|
||||
quad_pts[2] = new Point(transformed.cols(), transformed.rows());
|
||||
quad_pts[3] = new Point(0, transformed.rows());
|
||||
|
||||
// Get transformation matrix
|
||||
Mat transmtx = Imgproc.getPerspectiveTransform(new MatOfPoint2f(getPoints()),
|
||||
new MatOfPoint2f(quad_pts));
|
||||
|
||||
// Apply perspective transformation
|
||||
Imgproc.warpPerspective(image, transformed, transmtx, transformed.size());
|
||||
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package org.wheatdb.seedcounter.processor.test;
|
||||
|
||||
public class SeedSizesFromMicroscope {
|
||||
public static double[] arrData = {
|
||||
6.768271966, 3.977383901, // 1
|
||||
7.194846395, 3.907059088,
|
||||
7.152581919, 4.270789414,
|
||||
7.18656747, 3.855635604,
|
||||
6.907621932, 4.414672834,
|
||||
7.116211957, 3.877207884,
|
||||
6.572688757, 3.118555435,
|
||||
7.295257783, 3.365848667,
|
||||
7.439182136, 2.9111525,
|
||||
7.072391985, 4.073834913,
|
||||
7.068605579, 2.974672015,
|
||||
7.084068442, 3.103235842,
|
||||
7.366084038, 4.339364293,
|
||||
7.222691828, 3.798747416,
|
||||
6.932601977, 3.138551751,
|
||||
6.742821179, 2.959669662,
|
||||
6.880073272, 3.636893919,
|
||||
7.042264475, 3.051198346,
|
||||
7.108905217, 3.277574244,
|
||||
7.168351788, 3.076362594,
|
||||
7.477905811, 3.602304591,
|
||||
6.728719376, 3.695603676,
|
||||
7.095775599, 2.990707956,
|
||||
6.150361244, 3.45383655,
|
||||
6.943060644, 3.968941239,
|
||||
|
||||
6.803567408, 4.121564093, // 2
|
||||
6.762121615, 3.087711578,
|
||||
6.465287869, 3.603133507,
|
||||
7.611023558, 3.417313085,
|
||||
7.189842199, 3.452137784,
|
||||
6.765406578, 3.858941034,
|
||||
6.399895618, 3.180273849,
|
||||
7.014705581, 3.397562373,
|
||||
6.918960683, 3.902484701,
|
||||
7.031672773, 3.921038089,
|
||||
7.214279867, 3.871272437,
|
||||
6.812644549, 3.93849649,
|
||||
7.437718742, 3.430360834,
|
||||
6.426206021, 3.655662212,
|
||||
6.648560142, 4.069987106,
|
||||
6.88743118, 4.208876563,
|
||||
6.655089134, 2.839804335,
|
||||
7.370126282, 3.182494525,
|
||||
6.887871221, 4.117920956,
|
||||
7.158322929, 3.859790417,
|
||||
6.364712745, 3.509128308,
|
||||
7.11236415, 4.188951882,
|
||||
7.620591907, 3.686117194,
|
||||
6.613049796, 3.106981314,
|
||||
6.67876952, 3.135369123,
|
||||
|
||||
7.703022985, 2.273593401,
|
||||
7.898636894, 2.023189177,
|
||||
8.957909495, 2.593391187,
|
||||
8.4808326, 2.009292044,
|
||||
8.395710105, 2.196289322,
|
||||
7.728504472, 2.153318733,
|
||||
7.804590761, 1.888741071,
|
||||
8.253832457, 2.471540555,
|
||||
7.74772304, 2.106766409,
|
||||
8.227307149, 2.22559815,
|
||||
8.947072187, 2.44148468,
|
||||
7.767299781, 2.719099859,
|
||||
7.707566671, 2.327217094,
|
||||
8.220829325, 2.41692421,
|
||||
8.008964572, 1.947041487,
|
||||
8.007040668, 2.736097751,
|
||||
6.919912401, 2.227266215,
|
||||
9.063468348, 2.295595489,
|
||||
8.529063223, 2.233897542,
|
||||
6.993174236, 1.654393254,
|
||||
7.576853804, 1.963363966,
|
||||
8.189054217, 2.569414028,
|
||||
8.834564768, 2.425919483,
|
||||
8.756513641, 2.282261201,
|
||||
8.295544321, 2.359585747,
|
||||
|
||||
7.536339262, 2.543369696,
|
||||
8.696934035, 2.53204118,
|
||||
7.552938046, 2.59844655,
|
||||
7.163521562, 2.516844389,
|
||||
7.477762541, 1.937401502,
|
||||
7.392609345, 2.293323646,
|
||||
8.64426206, 2.745164657,
|
||||
8.630385395, 2.515554964,
|
||||
7.939417508, 2.336437504,
|
||||
8.39703023, 2.237479277,
|
||||
7.707566671, 2.074940134,
|
||||
7.824750814, 2.373155406,
|
||||
7.917927096, 1.942160093,
|
||||
8.414447696, 2.745164657,
|
||||
8.177418695, 2.198489531,
|
||||
7.886049653, 2.674502139,
|
||||
8.094250803, 2.104873207,
|
||||
8.563232977, 2.194764526,
|
||||
7.351961768, 1.899363474,
|
||||
7.366687816, 2.068605579,
|
||||
7.109201989, 2.697957388,
|
||||
7.882181379, 2.258601281,
|
||||
8.429572852, 2.414498864,
|
||||
7.40329315, 1.870474222,
|
||||
6.053715794, 2.136822285,
|
||||
|
||||
7.504789292, 3.371231503,
|
||||
7.97874496, 3.435917641,
|
||||
6.439796148, 2.281278782,
|
||||
6.614850897, 2.863505188,
|
||||
6.757659797, 2.868632186,
|
||||
5.652684255, 2.329847111,
|
||||
6.550737837, 2.571870075,
|
||||
6.65383041, 2.887553982,
|
||||
7.1010254, 3.304304222,
|
||||
7.364415972, 2.531887677,
|
||||
7.304508893, 2.484721341,
|
||||
7.03465073, 3.036983974,
|
||||
6.986297304, 2.726652203,
|
||||
6.794203729, 2.873728484,
|
||||
6.815387134, 2.826664484,
|
||||
7.29040709, 3.167451237,
|
||||
7.130057922, 2.869737408,
|
||||
6.888884341, 2.765068872,
|
||||
6.134509507, 2.853834503,
|
||||
7.033750179, 3.484741808,
|
||||
7.027036984, 3.345207638,
|
||||
7.515012587, 3.329755009,
|
||||
7.722630426, 3.087476207,
|
||||
7.469688287, 2.565208048,
|
||||
6.986297304, 3.411285536,
|
||||
|
||||
7.256841114, 3.087445506,
|
||||
7.753904091, 3.000624245,
|
||||
7.19390491, 2.825375059,
|
||||
7.116119855, 2.977199697,
|
||||
6.811672363, 2.557717104,
|
||||
7.20332999, 2.816400254,
|
||||
6.794142328, 2.744079903,
|
||||
5.941075339, 2.595867701,
|
||||
7.638367547, 2.924558423,
|
||||
7.523199411, 2.492918398,
|
||||
7.644978407, 2.457408052,
|
||||
6.337624593, 2.375161178,
|
||||
6.740610737, 2.921529299,
|
||||
6.834841073, 3.042643116,
|
||||
6.80018011, 3.117603717,
|
||||
6.606633374, 3.06237336,
|
||||
7.791174604, 3.493153769,
|
||||
6.886049653, 2.910927362,
|
||||
6.575861151, 2.607892098,
|
||||
7.07455126, 2.631275712,
|
||||
6.152305614, 2.864538775,
|
||||
7.746658753, 2.811109519,
|
||||
6.858337256, 2.856444053,
|
||||
6.674757977, 2.902699605,
|
||||
7.309861029, 2.836324935,
|
||||
|
||||
4.884975133, 2.565238748,
|
||||
5.290642461, 2.620673776,
|
||||
4.862758141, 2.100483023,
|
||||
4.912247488, 2.476012608,
|
||||
5.119097812, 2.362154363,
|
||||
5.403579688, 2.810945783,
|
||||
4.894870955, 2.852268773,
|
||||
5.047954318, 2.326367711,
|
||||
5.253279846, 2.565351317,
|
||||
4.858746597, 2.59479318,
|
||||
5.479021265, 2.48573446,
|
||||
4.768333367, 2.376102663,
|
||||
4.918305737, 2.044751223,
|
||||
5.234112446, 2.346138889,
|
||||
4.494146421, 1.968337461,
|
||||
4.946918684, 2.278669232,
|
||||
4.901185043, 2.360690968,
|
||||
5.248316584, 2.052201232,
|
||||
4.948494648, 2.156941403,
|
||||
5.328424651, 2.597658569,
|
||||
5.197415011, 2.522759369,
|
||||
5.579217749, 2.475112057,
|
||||
5.253371948, 2.606172865,
|
||||
5.368632186, 2.198868172,
|
||||
4.92802759, 2.238400295,
|
||||
|
||||
5.868335414, 2.757209521,
|
||||
4.782680775, 2.42910211,
|
||||
4.864426206, 2.785157289,
|
||||
5.970476268, 2.72406312,
|
||||
5.326858921, 1.832507829,
|
||||
5.182351256, 2.711588448,
|
||||
5.529759103, 2.331013733,
|
||||
5.035766184, 2.212509466,
|
||||
4.953099736, 2.574479625,
|
||||
5.142972636, 2.623365194,
|
||||
4.93849649, 2.718997523,
|
||||
4.942722937, 2.30922655,
|
||||
5.034967969, 2.646155263,
|
||||
4.93849649, 2.348318631,
|
||||
5.145848257, 2.432049367,
|
||||
4.896702757, 2.162068401,
|
||||
5.368212612, 2.529022289,
|
||||
5.186495835, 2.479318038,
|
||||
5.026064799, 2.495456313,
|
||||
5.371548742, 2.170582697,
|
||||
5.420454778, 2.88029841,
|
||||
5.203340224, 2.378916883,
|
||||
4.776233652, 2.248357519,
|
||||
5.08554207, 2.64475327,
|
||||
5.318743732, 2.226764772,
|
||||
|
||||
6.377586524, 2.622198571,
|
||||
6.014736282, 2.593462822,
|
||||
6.935283162, 2.401502282,
|
||||
6.777062568, 2.784727481,
|
||||
6.838709347, 2.726652203,
|
||||
6.568380442, 2.388290796,
|
||||
5.97265601, 2.575666714,
|
||||
6.445158517, 2.750179087,
|
||||
6.601342639, 2.506651794,
|
||||
6.150780818, 2.714064962,
|
||||
6.69355697, 2.793170143,
|
||||
7.099776909, 2.516844389,
|
||||
7.121697128, 3.028080804,
|
||||
6.223316073, 2.383143331,
|
||||
6.213747723, 2.75676948,
|
||||
5.843058597, 2.4721034,
|
||||
6.576915205, 3.044270247,
|
||||
6.848349332, 2.567602693,
|
||||
7.083495364, 2.757076485,
|
||||
6.216060501, 2.967682515,
|
||||
6.841155161, 2.492355554,
|
||||
6.477783008, 2.142614462,
|
||||
6.567694795, 2.483370515,
|
||||
6.417722426, 2.638510817,
|
||||
6.855635604, 2.783652961,
|
||||
|
||||
6.082789251, 2.419769132,
|
||||
6.18482777, 2.5,
|
||||
6.389457418, 3.207546204,
|
||||
7.013590127, 2.701958697,
|
||||
6.092275732, 2.677183323,
|
||||
6.358562394, 2.265386111,
|
||||
6.470813975, 2.520763831,
|
||||
6.829181932, 2.689811498,
|
||||
6.183159704, 2.509619517,
|
||||
6.262663992, 2.640342619,
|
||||
6.667348902, 2.542346344,
|
||||
6.904705377, 2.701651692,
|
||||
7.477609038, 2.578992611,
|
||||
6.823788862, 2.86011789,
|
||||
6.00629362, 2.293323646,
|
||||
6.243332856, 1.989367363,
|
||||
6.59548906, 2.619814159,
|
||||
7.123385661, 2.397787511,
|
||||
6.920772017, 2.928119691,
|
||||
7.180243149, 2.413997421,
|
||||
6.952925766, 2.678851389,
|
||||
5.800794122, 2.394983524,
|
||||
7.165752471, 2.624961624,
|
||||
6.765416812, 2.807793856,
|
||||
6.048773, 2.625780307
|
||||
};
|
||||
|
||||
public static double[][][] structData = new double[10][][]; // [image№][seed№][length/width]
|
||||
|
||||
static {
|
||||
for(int imageNum = 0; imageNum<10; imageNum++) {
|
||||
structData[imageNum] = new double[25][];
|
||||
|
||||
for(int seedNum = 0; seedNum<25; seedNum++) {
|
||||
structData[imageNum][seedNum] = new double[2];
|
||||
|
||||
structData[imageNum][seedNum][0] = arrData[(imageNum+1)*(seedNum+1)*(2)-2]; // length
|
||||
structData[imageNum][seedNum][1] = arrData[(imageNum+1)*(seedNum+1)*(2)-1]; // width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.wheatdb.seedcounter.processor.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.wheatdb.seedcounter.desktop.Display;
|
||||
|
||||
/**
|
||||
* Main class of test application.
|
||||
*/
|
||||
public class TestMain {
|
||||
private static File imgSrcDir = ImgDirs.SonyL4;
|
||||
|
||||
private static File imgOutDir = new File(imgSrcDir, "out");
|
||||
|
||||
private static List<File> inputImgs = new LinkedList<File>();
|
||||
private static List<File> markedImgs = new LinkedList<File>();
|
||||
private static List<File> outputImgs = new LinkedList<File>();
|
||||
|
||||
private static int fromImg = 1;
|
||||
private static int toImg = 10;
|
||||
|
||||
static {
|
||||
if(!imgOutDir.exists()) {
|
||||
imgOutDir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
linkImage();
|
||||
doTests();
|
||||
//display(outputImgs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute tests.
|
||||
*/
|
||||
private static void doTests() {
|
||||
/*
|
||||
DetectionTester detectTester = new DetectionTester(new DetectionProcessor(SheetFormat.A4), inputImgs, markedImgs, outputImgs);
|
||||
|
||||
double appraisal = detectTester.doTests();
|
||||
|
||||
System.out.println("Appraisal: " + appraisal);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Link to source, marked and output image files.
|
||||
*/
|
||||
private static void linkImage() {
|
||||
// download source and marked image
|
||||
for(int i=fromImg; i<toImg+1; i++) {
|
||||
File sourceImgJPG = new File(imgSrcDir+"/"+i+".jpg");
|
||||
File markedImgJPG = new File(imgSrcDir+"/"+i+"b.jpg");
|
||||
File outputImgJPG = new File(imgOutDir+"/"+i+"_out"+".jpg");
|
||||
|
||||
// check: whether file exist or not
|
||||
if(!sourceImgJPG.exists()) {
|
||||
System.out.println("Source image file №"+i+" not found.");
|
||||
} else {
|
||||
inputImgs.add(sourceImgJPG);
|
||||
outputImgs.add(outputImgJPG);
|
||||
|
||||
if(!outputImgJPG.exists())
|
||||
try {outputImgJPG.createNewFile();} catch (IOException e) {}
|
||||
|
||||
if(!markedImgJPG.exists()) {
|
||||
System.out.println("Marked image file №"+i+" not found.");
|
||||
} else {
|
||||
markedImgs.add(markedImgJPG);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To display images on windows.
|
||||
* @param images
|
||||
*/
|
||||
private static void display(List<File> images) {
|
||||
for(File out : images) {
|
||||
Display display = new Display();
|
||||
|
||||
Mat img = Imgcodecs.imread(out.getAbsolutePath());
|
||||
display.setImage(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ImgDirs {
|
||||
public static File SonyL1 = new File("../data/seedcounter/estimate/Sony/L1");
|
||||
public static File SonyL2 = new File("../data/seedcounter/estimate/Sony/L2");
|
||||
public static File SonyL3 = new File("../data/seedcounter/estimate/Sony/L3");
|
||||
public static File SonyL4 = new File("../data/seedcounter/estimate/Sony/L4");
|
||||
public static File SonyDark = new File("../data/seedcounter/estimate/Sony/dark");
|
||||
public static File SonyOutsideSun = new File("../data/seedcounter/estimate/Sony/outside_sun");
|
||||
|
||||
public static File SamsungL1 = new File("../data/seedcounter/estimate/Samsung/L1");
|
||||
public static File SamsungL2 = new File("../data/seedcounter/estimate/Samsung/L2");
|
||||
public static File SamsungL3 = new File("../data/seedcounter/estimate/Samsung/L3");
|
||||
public static File SamsungL4 = new File("../data/seedcounter/estimate/Samsung/L4");
|
||||
public static File SamsungDark = new File("../data/seedcounter/estimate/Samsung/dark");
|
||||
public static File SamsungOutsideSun = new File("../data/seedcounter/estimate/Samsung/outside_sun");
|
||||
|
||||
public static File DNSL1 = new File("../data/seedcounter/estimate/DNS/L1");
|
||||
public static File DNSL2 = new File("../data/seedcounter/estimate/DNS/L2");
|
||||
public static File DNSL3 = new File("../data/seedcounter/estimate/DNS/L3");
|
||||
public static File DNSL4 = new File("../data/seedcounter/estimate/DNS/L4");
|
||||
public static File DNSDark = new File("../data/seedcounter/estimate/DNS/dark");
|
||||
public static File DNSOutsideSun = new File("../data/seedcounter/estimate/DNS/outside_sun");
|
||||
|
||||
public static File Lenovo = new File("../data/seedcounter/estimate/Lenovo");
|
||||
|
||||
public static File testWatersheld = new File("../data/seedcounter/test/watersheld");
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.wheatdb.seedcounter.processor.test.testers;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
import org.wheatdb.seedcounter.desktop.Display;
|
||||
import org.wheatdb.seedcounter.processor.Calibration;
|
||||
|
||||
public class CalibrationDisplay extends Display implements MouseListener {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int COLORS_NUM = 5;
|
||||
|
||||
List<Scalar> colors = new ArrayList<Scalar>(10);
|
||||
|
||||
|
||||
public CalibrationDisplay() {
|
||||
addMouseListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
BufferedImage screenshot = getScreenShot();
|
||||
|
||||
int[] pixel = new int[3];
|
||||
screenshot.getData().getPixel(e.getX(), e.getY(), pixel);
|
||||
|
||||
synchronized (colors) {
|
||||
colors.add(new Scalar(pixel[0], pixel[1], pixel[2]));
|
||||
|
||||
if(COLORS_NUM==colors.size()) {
|
||||
colors.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Calibration getCalibration() {
|
||||
synchronized (colors) {
|
||||
try {
|
||||
while(COLORS_NUM>colors.size())
|
||||
colors.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
Calibration calibration = new Calibration();
|
||||
|
||||
for(Scalar color : colors) {
|
||||
calibration.addColor(Calibration.RGBtoHSV(color, true));
|
||||
}
|
||||
|
||||
return calibration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public BufferedImage getScreenShot() {
|
||||
|
||||
BufferedImage image = new BufferedImage(
|
||||
this.getWidth(),
|
||||
this.getHeight(),
|
||||
BufferedImage.TYPE_INT_RGB
|
||||
);
|
||||
this.paint( image.getGraphics() );
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.wheatdb.seedcounter.processor.test.testers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfFloat;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.filters.GrayBinarization;
|
||||
|
||||
public class FiltersTester {
|
||||
public Mat doTests(Mat img) {
|
||||
Mat outImg = binarizationFilterTest(img);
|
||||
|
||||
return outImg;
|
||||
}
|
||||
|
||||
public Mat binarizationFilterTest(Mat srcImg) {
|
||||
//Calibration calibration = calibrate(srcImg);
|
||||
|
||||
GrayBinarization binarization = new GrayBinarization(false, srcImg.height()*srcImg.width()/(297*210*4/3));
|
||||
//HSVBinarization binarization = new HSVBinarization(calibration.targetColor);
|
||||
|
||||
Mat outImg = binarization.apply(srcImg.clone());
|
||||
|
||||
//FrameProcessor fp = new FrameProcessor();
|
||||
//Mat outImg = fp.treshHullFilterLines(srcImg, false);
|
||||
|
||||
return outImg;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static Mat adjastImage(Mat img) {
|
||||
Mat histB = histogram(img, 0);
|
||||
Mat histG = histogram(img, 1);
|
||||
Mat histR = histogram(img, 2);
|
||||
|
||||
List<Mat> rgb = new LinkedList<Mat>(); Core.split(img, rgb);
|
||||
|
||||
HistAnalyzer histAnalyzerR = new HistAnalyzer(histR);
|
||||
HistAnalyzer histAnalyzerG = new HistAnalyzer(histG);
|
||||
HistAnalyzer histAnalyzerB = new HistAnalyzer(histB);
|
||||
|
||||
contrastAndBrightAdjast(rgb.get(0), histAnalyzerR);
|
||||
contrastAndBrightAdjast(rgb.get(1), histAnalyzerG);
|
||||
contrastAndBrightAdjast(rgb.get(2), histAnalyzerB);
|
||||
|
||||
Mat out = new Mat(); Core.merge(rgb, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
private static void contrastAndBrightAdjast(Mat src, HistAnalyzer histAnalyzer) {
|
||||
int histLenght = histAnalyzer.getHistLength();
|
||||
int leftCenter = histAnalyzer.getLeftCenterPos();
|
||||
int leftWidth = histAnalyzer.getLeftWidth();
|
||||
int rightCenter = histAnalyzer.getRightCenterPos();
|
||||
int rightWidth = histAnalyzer.getRightWidth();
|
||||
|
||||
|
||||
float contrast = (float)(histLenght/(float)((rightCenter+rightWidth/2)-(leftCenter-leftWidth/2)));
|
||||
float bright = -(float)((256.0/histLenght)*leftCenter*contrast*0.4);
|
||||
|
||||
Mat kernel2 = new MatOfFloat(contrast);
|
||||
Imgproc.filter2D(src, src, -1, kernel2, new Point(), bright);
|
||||
}
|
||||
|
||||
|
||||
private static Mat histogram(Mat img, int channel) {
|
||||
MatOfInt channels = new MatOfInt(channel);
|
||||
|
||||
MatOfInt histSize = new MatOfInt(256);
|
||||
MatOfFloat ranges=new MatOfFloat(0, 256);
|
||||
|
||||
/// Compute the histograms:
|
||||
Mat hist = new Mat();
|
||||
Imgproc.calcHist(Arrays.asList(img, img, img), channels, new Mat(), hist, histSize, ranges);
|
||||
Core.normalize(hist, hist, 256, 0, Core.NORM_INF);
|
||||
|
||||
return hist;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package org.wheatdb.seedcounter.processor.test.testers;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
|
||||
/**
|
||||
* It's analyzer for image histograms.
|
||||
* @author Komyshev
|
||||
* @version 0.1
|
||||
*/
|
||||
public class HistAnalyzer {
|
||||
public Mat hist; // histogram for analyzing
|
||||
private int histLength;
|
||||
|
||||
private static final float persentOfNonSignificantMinValues = 0.1f; // 10% from average value of histogram
|
||||
private double nonSignHistValueThres; // histogram values under non-significant threshold must be deleted
|
||||
|
||||
private double sumValue; // sum values of histogram
|
||||
private double averageValue; // average value of histogram
|
||||
|
||||
private int leftLimitPos; // minimal by position(left) position of significant histogram value
|
||||
private int rightLimitPos; // maximal by position(right) position of significant histogram value
|
||||
|
||||
private int leftCenterPos; // center of left contrast part
|
||||
private int leftWidth; // significant width of left contrast part
|
||||
|
||||
private int rightCenterPos; // center of right contrast part
|
||||
private int rightWidth; // significant width of right contrast part
|
||||
|
||||
// center of contrast division.
|
||||
private int centerPos; // it is on non-significant area between contrast parts. it will be new histogram center of future image.
|
||||
|
||||
|
||||
|
||||
HistAnalyzer(Mat hist) {
|
||||
this.hist = hist;
|
||||
histLength = hist.cols()*hist.rows();
|
||||
|
||||
sumValue = getSumValues();
|
||||
|
||||
averageValue = getAverageValue();
|
||||
|
||||
nonSignHistValueThres = averageValue*persentOfNonSignificantMinValues;
|
||||
|
||||
determLimits(nonSignHistValueThres);
|
||||
|
||||
detectSideCenterPos(sumValue);
|
||||
|
||||
determCenterPos(leftCenterPos, leftWidth, rightCenterPos, rightWidth);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detect centers and widths of right and left local highs in histogram.
|
||||
* @param leftLim
|
||||
* @param rightLim
|
||||
* @param average
|
||||
* @param sum
|
||||
*/
|
||||
private void detectSideCenterPos(double sum) {
|
||||
HistIterator histIter = new HistIterator();
|
||||
|
||||
// getting big-half side of maximal histogram values
|
||||
int massCenterPos = 0;
|
||||
int massAccum = 0;
|
||||
List<Integer> positionCollection = new LinkedList<Integer>();
|
||||
while(positionCollection.size()!=histLength) {
|
||||
int curPosition = histIter.getNextPosOfMaxValue();
|
||||
double curValue = hist.get(curPosition, 0)[0];
|
||||
|
||||
massCenterPos = (int)(massCenterPos*(massAccum/(massAccum+curValue))+curPosition*(curValue/(massAccum+curValue)));
|
||||
|
||||
massAccum +=curValue;
|
||||
positionCollection.add(curPosition);
|
||||
|
||||
if(massAccum>=(sum) && positionCollection.size()>=2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(positionCollection);
|
||||
|
||||
// divide to left and right side by positions of values
|
||||
List<Integer> leftPositions = new LinkedList<Integer>();
|
||||
List<Integer> rightPositions = new LinkedList<Integer>();
|
||||
while(true) {
|
||||
if(!positionCollection.isEmpty()) {
|
||||
int pos = positionCollection.get(0);
|
||||
if(pos>massCenterPos) {
|
||||
rightPositions.add(positionCollection.get(0));
|
||||
positionCollection.remove(0);
|
||||
} else {
|
||||
leftPositions.add(positionCollection.get(0));
|
||||
positionCollection.remove(0);
|
||||
}
|
||||
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
// determining of center of left and right positions
|
||||
for(Integer cur : leftPositions) {
|
||||
leftCenterPos +=cur;
|
||||
}
|
||||
leftCenterPos /= leftPositions.size();
|
||||
leftWidth = Collections.max(leftPositions)-Collections.min(leftPositions);
|
||||
|
||||
for(Integer cur : rightPositions) {
|
||||
rightCenterPos +=cur;
|
||||
}
|
||||
rightCenterPos /= rightPositions.size();
|
||||
rightWidth = Collections.max(rightPositions)-Collections.min(rightPositions);
|
||||
}
|
||||
|
||||
|
||||
private void determCenterPos(int leftCenterPos, int leftWidth, int rightCenterPos, int rightWidth) {
|
||||
double normLeftWidth = (double)leftWidth/((double)(leftWidth+rightWidth));
|
||||
double normRightWidth = (double)rightWidth/((double)(leftWidth+rightWidth));
|
||||
centerPos = (int)Math.round(leftCenterPos*normLeftWidth + rightCenterPos*normRightWidth);
|
||||
}
|
||||
|
||||
|
||||
private void determLimits(double threshold) {
|
||||
// to establish left limit
|
||||
for(int i=0; i<histLength; i++) {
|
||||
int curLeftPosition = i;
|
||||
double curValue = hist.get(curLeftPosition, 0)[0];
|
||||
|
||||
if(curValue<threshold) {
|
||||
leftLimitPos = curLeftPosition;
|
||||
} else break;
|
||||
}
|
||||
|
||||
// to establish right limit
|
||||
for(int i=0; i<histLength; i++) {
|
||||
int curRightPosition = histLength-i-1;
|
||||
double curValue = hist.get(curRightPosition, 0)[0];
|
||||
|
||||
if(curValue<threshold) {
|
||||
rightLimitPos = curRightPosition;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
private double getSumValues() {
|
||||
double sum = 0;
|
||||
for(int i=0; i<hist.rows(); i++)
|
||||
sum += hist.get(i, 0)[0];
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
private double getAverageValue() {
|
||||
return getSumValues()/histLength;
|
||||
}
|
||||
|
||||
|
||||
public int getLeftLimitPos() {
|
||||
return leftLimitPos;
|
||||
}
|
||||
|
||||
public int getRightLimitPos() {
|
||||
return rightLimitPos;
|
||||
}
|
||||
|
||||
public int getLeftCenterPos() {
|
||||
return leftCenterPos;
|
||||
}
|
||||
|
||||
public int getLeftWidth() {
|
||||
return leftWidth;
|
||||
}
|
||||
|
||||
public int getCenterPos() {
|
||||
return centerPos;
|
||||
}
|
||||
|
||||
public int getRightCenterPos() {
|
||||
return rightCenterPos;
|
||||
}
|
||||
|
||||
public int getRightWidth() {
|
||||
return rightWidth;
|
||||
}
|
||||
|
||||
public int getHistLength() {
|
||||
return histLength;
|
||||
}
|
||||
|
||||
|
||||
|
||||
class HistIterator {
|
||||
int curPosOfPosOfMinHistVal;
|
||||
int curPosOfPosOfMaxHistVal;
|
||||
List<Integer> sortedPosHist; // sorted position of histogram by value of histogram
|
||||
|
||||
private HistIterator() {
|
||||
// forming sorted histogram
|
||||
List<Double> initHistValues = new LinkedList<Double>();
|
||||
for(int i=0; i<hist.rows(); i++)
|
||||
initHistValues.add(hist.get(i, 0)[0]);
|
||||
|
||||
List<Double> reducedHistValues = new LinkedList<Double>();
|
||||
reducedHistValues.addAll(initHistValues);
|
||||
|
||||
sortedPosHist = new LinkedList<Integer>();
|
||||
while(!reducedHistValues.isEmpty()) {
|
||||
Double curMin = Collections.min(reducedHistValues);
|
||||
int posMin = initHistValues.indexOf(curMin);
|
||||
reducedHistValues.remove(curMin);
|
||||
|
||||
sortedPosHist.add(posMin);
|
||||
}
|
||||
|
||||
curPosOfPosOfMinHistVal = 0;
|
||||
curPosOfPosOfMaxHistVal = sortedPosHist.size()-1;
|
||||
}
|
||||
|
||||
public boolean hasNextPosOfMinValue() {
|
||||
if(curPosOfPosOfMinHistVal<sortedPosHist.size())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasNextPosOfMaxValue() {
|
||||
if(curPosOfPosOfMaxHistVal>=0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getNextPosOfMinValue() {
|
||||
return sortedPosHist.get(curPosOfPosOfMinHistVal++);
|
||||
}
|
||||
|
||||
public int getNextPosOfMaxValue() {
|
||||
return sortedPosHist.get(curPosOfPosOfMaxHistVal--);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.wheatdb.seedcounter.server;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Директории и файлы по умолчанию.
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class DefaultFilesAndDirs {
|
||||
// Директория входных/выходных данных
|
||||
public static final File dataDir = new File("../data/seedcounter");
|
||||
|
||||
// Директория входных файлов
|
||||
public static final File inputDir = new File(dataDir, "input/");
|
||||
|
||||
// Директория выходных файлов
|
||||
public static final File outputDir = new File(dataDir, "output/");
|
||||
|
||||
// Директория выходных файлов
|
||||
public static final File markedDir = new File(dataDir, "marked/");
|
||||
|
||||
// Директория шаблонов для распознавания
|
||||
public static final File templateDir = new File(dataDir, "templates/");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.delkom07.recognition.potatoes;
|
||||
|
||||
|
||||
|
||||
public enum PotatoType {
|
||||
WHITE, DARK;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package ru.delkom07.recognition.potatoes.refactor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.SeedDataContainer;
|
||||
import org.wheatdb.seedcounter.processor.ElementsProcessor;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.filters.GrayBinarization;
|
||||
import org.wheatdb.seedcounter.processor.filters.HSVBinarization;
|
||||
|
||||
import ru.delkom07.recognition.potatoes.training.PotatoBinarizationTraining;
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
public class DetectionProcessor {
|
||||
|
||||
/**
|
||||
* Detect contours of potatoes in image.
|
||||
* @param image - source image.
|
||||
* @return list of contours.
|
||||
*/
|
||||
// @Deprecated
|
||||
// public List<PlanarObject> detectPotatoesContours(Mat image, double pixPerMM) throws SeedCounterProcessorException {
|
||||
// List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
// synchronized (DetectionProcessor.class) {
|
||||
// List<Pair<Scalar, Scalar>> targetsAndRanges = PotatoBinarizationTraining.getCommonPotatoesHSVPreset2().get(0).getParams(); // differ
|
||||
// HSVBinarization seedContourBinarization = new HSVBinarization(targetsAndRanges); // differ
|
||||
//
|
||||
// Mat hulled = seedContourBinarization.apply(image);
|
||||
//
|
||||
// // Find contours
|
||||
// Imgproc.findContours(hulled, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
// hulled.release();
|
||||
// }
|
||||
//
|
||||
//
|
||||
// contours = ElementsProcessor.filterCircularContours(contours, 8*pixPerMM, 100*pixPerMM); // differ
|
||||
//
|
||||
// // Gauge seeds
|
||||
// List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
// for(int i=0; i<contours.size(); i++) {
|
||||
// MatOfPoint contour = contours.get(i);
|
||||
//
|
||||
// SeedDataContainer seedDataContainer = gaugeSeed(contour, pixPerMM);
|
||||
// seedDataList.add(seedDataContainer);
|
||||
// }
|
||||
//
|
||||
// return seedDataList;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Detect contours of seeds in image.
|
||||
* @param image - source image.
|
||||
* @return list of contours.
|
||||
*/
|
||||
// @Deprecated
|
||||
// public List<PlanarObject> detectPotatoesContours(Mat image) throws SeedCounterProcessorException {
|
||||
// // init size
|
||||
// //prepareSize(image.cols(), image.rows());
|
||||
// if(-1 == widthPadding || -1 == heightPadding)
|
||||
// throw new SeedCounterProcessorException("Padding isn't initialized.");
|
||||
//
|
||||
//
|
||||
// int width = image.cols();
|
||||
// int height = image.rows();
|
||||
//
|
||||
// // Clean
|
||||
// GrayBinarization binarization = new GrayBinarization(true, width*height/(210*297)); // ?????????????????????????????
|
||||
// Mat hulled = binarization.apply(image);
|
||||
//
|
||||
// // Find contours
|
||||
// List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
// Imgproc.findContours(hulled, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
//
|
||||
// /* // disabled for desktop
|
||||
// if(SeedCounterApplication.config.getUseWatershed()) {
|
||||
// double maxSeedAreaInMM2 = 78;
|
||||
// double maxSeedAreaInPix = maxSeedAreaInMM2 * ((width+2*widthPadding)/mmInBigSideOfSheet);
|
||||
// contours = clarifyContours(contours, maxSeedAreaInPix);
|
||||
// }
|
||||
// */
|
||||
//
|
||||
// // Filter
|
||||
// double pixInMM = (width+2*widthPadding)/mmInBigSideOfSheet;
|
||||
// //contours = ElementsProcessor.filterCircularContours(contours, SeedCounterApplication.config.getMinSeedLength()*pixInMM, SeedCounterApplication.config.getMaxSeedLength()*pixInMM);
|
||||
// contours = ElementsProcessor.filterCircularContours(contours, 8*pixInMM, 100*pixInMM);
|
||||
//
|
||||
// // Gauge seeds
|
||||
// List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
// for(int i=0; i<contours.size(); i++) {
|
||||
// MatOfPoint contour = contours.get(i);
|
||||
//
|
||||
// SeedDataContainer seedDataContainer = gaugeSeed(width, height, contour);
|
||||
// seedDataList.add(seedDataContainer);
|
||||
// }
|
||||
//
|
||||
// return seedDataList;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package ru.delkom07.recognition.potatoes.training;
|
||||
|
||||
import java.util.List;
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
|
||||
public class HSVParameters {
|
||||
List<Pair<Scalar, Scalar>> params;
|
||||
Double jIndex = -1.0;
|
||||
|
||||
|
||||
|
||||
HSVParameters(List<Pair<Scalar, Scalar>> params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
HSVParameters(List<Pair<Scalar, Scalar>> params, Double jIndex) {
|
||||
this.params = params;
|
||||
this.jIndex = jIndex;
|
||||
}
|
||||
|
||||
public List<Pair<Scalar, Scalar>> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public Double getJIndex() {
|
||||
return jIndex;
|
||||
}
|
||||
|
||||
public void setJIndex(Double jIndex) {
|
||||
this.jIndex = jIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\nJaccard Index: " + jIndex + "\n" + "Parameters: " + params.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package ru.delkom07.recognition.potatoes.training;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder;
|
||||
import org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer;
|
||||
import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
|
||||
import org.apache.commons.math3.fitting.leastsquares.LevenbergMarquardtOptimizer;
|
||||
import org.apache.commons.math3.fitting.leastsquares.MultivariateJacobianFunction;
|
||||
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
|
||||
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
|
||||
import org.apache.commons.math3.linear.ArrayRealVector;
|
||||
import org.apache.commons.math3.linear.RealMatrix;
|
||||
import org.apache.commons.math3.linear.RealVector;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.wheatdb.seedcounter.processor.filters.HSVBinarization;
|
||||
import org.wheatdb.seedcounter.server.DefaultFilesAndDirs;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
|
||||
public class LMTraining {
|
||||
final Vector2D[] observedPoints;
|
||||
public double rms;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
File inputDir = DefaultFilesAndDirs.inputDir;
|
||||
File markedDir = DefaultFilesAndDirs.markedDir;
|
||||
|
||||
File[] inputImgFiles = inputDir.listFiles(new PotatoBinarizationTraining.ImageFilter());
|
||||
|
||||
List<Mat> inputs = new ArrayList<Mat>();
|
||||
List<Mat> markets = new ArrayList<Mat>();
|
||||
|
||||
for(File inputImgFile: inputImgFiles) {
|
||||
File markedImgFile = new File(markedDir, PotatoBinarizationTraining.changeExtension(inputImgFile.getName(), "png"));
|
||||
|
||||
Mat input = Imgcodecs.imread(inputImgFile.getCanonicalPath());
|
||||
Mat marked = Imgcodecs.imread(markedImgFile.getCanonicalPath());
|
||||
|
||||
/*
|
||||
Size newSize = new Size(input.size().width/8, input.size().height/8);
|
||||
Imgproc.resize(input, input, newSize);
|
||||
Imgproc.resize(marked, marked, newSize);
|
||||
*/
|
||||
|
||||
inputs.add(input);
|
||||
markets.add(marked);
|
||||
}
|
||||
|
||||
LMTraining training = new LMTraining(inputs, markets);
|
||||
|
||||
for(int i=0; i<inputs.size(); i++) {
|
||||
inputs.get(i).release();
|
||||
markets.get(i).release();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public LMTraining(List<Mat> imgs, List<Mat> marked) {
|
||||
observedPoints = new Vector2D[imgs.size()];
|
||||
for(int i=0; i<imgs.size(); i++) {
|
||||
observedPoints[i] = new Vector2D(1, 1);
|
||||
}
|
||||
|
||||
MultivariateJacobianFunction distances = new MultivariateJacobianFunction() {
|
||||
public org.apache.commons.math3.util.Pair<RealVector, RealMatrix> value(final RealVector currentParam) {
|
||||
|
||||
double h = currentParam.getEntry(0);
|
||||
double s = currentParam.getEntry(1);
|
||||
double v = currentParam.getEntry(2);
|
||||
//double rh = currentParam.getEntry(3);
|
||||
//double rs = currentParam.getEntry(4);
|
||||
//double rv = currentParam.getEntry(5);
|
||||
|
||||
RealVector value = new ArrayRealVector(observedPoints.length);
|
||||
RealMatrix jacobian = new Array2DRowRealMatrix(observedPoints.length, 3);
|
||||
|
||||
|
||||
for (int i = 0; i < observedPoints.length; ++i) {
|
||||
//Random rand = new Random();
|
||||
double delta = 1;
|
||||
|
||||
double val = function(imgs, marked, h, s, v);
|
||||
|
||||
double derivativeH = (function(imgs, marked, h+delta, s, v)-function(imgs, marked, h, s, v))/delta;
|
||||
double derivativeS = (function(imgs, marked, h, s+delta, v)-function(imgs, marked, h, s, v))/delta;
|
||||
double derivativeV = (function(imgs, marked, h, s, v+delta)-function(imgs, marked, h, s, v))/delta;
|
||||
|
||||
//double derivativeRH = (function(imgs, marked, h, s, v, rh+delta, rs, rv)-function(imgs, marked, h, s, v, rh, rs, rv))/delta;
|
||||
//double derivativeRS = (function(imgs, marked, h, s, v, rh, rs+delta, rv)-function(imgs, marked, h, s, v, rh, rs, rv))/delta;
|
||||
//double derivativeRV = (function(imgs, marked, h, s, v, rh, rs, rv+delta)-function(imgs, marked, h, s, v, rh, rs, rv))/delta;
|
||||
|
||||
value.setEntry(i, val);
|
||||
|
||||
jacobian.setEntry(i, 0, derivativeH);
|
||||
jacobian.setEntry(i, 1, derivativeS);
|
||||
jacobian.setEntry(i, 2, derivativeV);
|
||||
|
||||
//jacobian.setEntry(i, 3, derivativeRH);
|
||||
//jacobian.setEntry(i, 4, derivativeRS);
|
||||
//jacobian.setEntry(i, 5, derivativeRV);
|
||||
|
||||
}
|
||||
|
||||
return new org.apache.commons.math3.util.Pair<RealVector, RealMatrix>(value, jacobian);
|
||||
}
|
||||
};
|
||||
|
||||
double[] zeros = new double[observedPoints.length];
|
||||
Arrays.fill(zeros, 1.0); // prev version
|
||||
|
||||
// least squares problem to solve : modeled radius should be close to target radius
|
||||
LeastSquaresProblem problem = new LeastSquaresBuilder().
|
||||
start(new double[] {10, 244, 74}).
|
||||
model(distances).
|
||||
target(zeros).
|
||||
lazyEvaluation(false).
|
||||
maxEvaluations(1000_000).
|
||||
maxIterations(1000_000).
|
||||
build();
|
||||
|
||||
LevenbergMarquardtOptimizer optimazer = new LevenbergMarquardtOptimizer();
|
||||
|
||||
try {
|
||||
LeastSquaresOptimizer.Optimum optimum = optimazer.optimize(problem);
|
||||
|
||||
/*
|
||||
double H = optimum.getPoint().getEntry(0);
|
||||
double S = optimum.getPoint().getEntry(1);
|
||||
double V = optimum.getPoint().getEntry(2);
|
||||
|
||||
double RH = optimum.getPoint().getEntry(3);
|
||||
double RS = optimum.getPoint().getEntry(4);
|
||||
double RV = optimum.getPoint().getEntry(5);
|
||||
|
||||
|
||||
rms = optimum.getRMS();
|
||||
*/
|
||||
|
||||
//System.out.println(rms);
|
||||
|
||||
} catch(org.apache.commons.math3.exception.TooManyEvaluationsException ex) {
|
||||
System.out.println("Too many evaluations for Levenberg-Marquardt optimizer. Trapeze parameters will be calculating using brute-force method.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private double function(List<Mat> imgs, List<Mat> marked, double h, double s, double v) {
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(h, s, v);
|
||||
Scalar range1 = new Scalar(20, 200, 200);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
HSVBinarization hsv = new HSVBinarization(parameters);
|
||||
|
||||
double sumJIndex = 0;
|
||||
|
||||
for(int i=0; i<imgs.size(); i++) {
|
||||
Mat input = imgs.get(i);
|
||||
Mat mark = marked.get(i);
|
||||
|
||||
Mat binarized = hsv.apply(input);
|
||||
|
||||
sumJIndex += MatFunctions.jaccardIndex(binarized, mark);
|
||||
}
|
||||
|
||||
System.out.println("HSV: " + h + " " + s + " " + v);
|
||||
//System.out.println("R-HSV: " + rh + " " + rs + " " + rv);
|
||||
|
||||
|
||||
double average = sumJIndex/imgs.size();
|
||||
|
||||
System.out.println("JIndex: " + average);
|
||||
|
||||
System.out.println();
|
||||
|
||||
return average;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
package ru.delkom07.recognition.potatoes.training;
|
||||
|
||||
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
|
||||
public class MatFunctions {
|
||||
|
||||
public static double[] calculatePrecisionRecall(Mat img, Mat marked) {
|
||||
int falsePositive = 0;
|
||||
int falseNegative = 0;
|
||||
|
||||
int truePositive = 0;
|
||||
//int trueNegative = 0;
|
||||
|
||||
for(int i=0; i<img.rows(); i++) {
|
||||
for(int j=0; j<img.cols(); j++) {
|
||||
double imgPix = img.get(i, j)[0];
|
||||
double markedPix = marked.get(i, j)[0];
|
||||
|
||||
if(imgPix != markedPix) {
|
||||
if(imgPix != 0) {
|
||||
falsePositive++;
|
||||
}
|
||||
|
||||
if(markedPix != 0) {
|
||||
falseNegative++;
|
||||
}
|
||||
} else {
|
||||
truePositive++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double precision = truePositive / (truePositive + falsePositive);
|
||||
double recall = truePositive / (truePositive + falseNegative);
|
||||
|
||||
return new double[] {precision, recall};
|
||||
}
|
||||
|
||||
|
||||
public static double jaccardIndex(Mat img, Mat marked) {
|
||||
int ABintersectionCount = 0;
|
||||
int Acount = 0;
|
||||
int Bcount = 0;
|
||||
|
||||
for(int i=0; i<img.rows(); i++) {
|
||||
for(int j=0; j<img.cols(); j++) {
|
||||
double imgPix = img.get(i, j)[0];
|
||||
double markedPix = marked.get(i, j)[0];
|
||||
|
||||
if(imgPix != 0) {
|
||||
Acount++;
|
||||
}
|
||||
|
||||
if(markedPix != 0) {
|
||||
Bcount++;
|
||||
}
|
||||
|
||||
if(imgPix != 0 && markedPix != 0) {
|
||||
ABintersectionCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (double)ABintersectionCount / (double)(Acount + Bcount - ABintersectionCount);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Mat fillRectWithColor(Mat src, Rect rect, Scalar color) throws Exception {
|
||||
Mat result = src.clone();
|
||||
|
||||
byte[] colorInByteArr = null;
|
||||
|
||||
if(1 == src.channels()) {
|
||||
colorInByteArr = new byte[]{
|
||||
(byte)(color.val[0])};
|
||||
} else if(3 == src.channels()) {
|
||||
colorInByteArr = new byte[]{
|
||||
(byte)(color.val[0]),
|
||||
(byte)(color.val[1]),
|
||||
(byte)(color.val[2])};
|
||||
} else {
|
||||
throw new Exception("Source image has count of channels != '1' or '3'.");
|
||||
}
|
||||
|
||||
for(int i=rect.x; i<rect.x+rect.width; i++) {
|
||||
for(int j=rect.y; j<rect.y+rect.height; j++) {
|
||||
result.put(j, i, colorInByteArr);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void fillRectWithGradient(Mat img, Rect rect, Scalar xColor1, Scalar xColor2) {
|
||||
double lamdaX = 0;
|
||||
//double lamdaY = 0;
|
||||
double counterX = 1;
|
||||
//double counterY = 1;
|
||||
for(int i=rect.x; i<rect.x+rect.width; i++) {
|
||||
counterX++;
|
||||
for(int j=rect.y; j<rect.y+rect.height; j++) {
|
||||
//counterY++;
|
||||
|
||||
lamdaX = counterX/(double)rect.width;
|
||||
//lamdaY = counterY/(double)rect.height;
|
||||
|
||||
img.put(j, i, new byte[]{
|
||||
(byte)(xColor1.val[0]*(1-lamdaX) + xColor2.val[0]*(lamdaX)),
|
||||
(byte)(xColor1.val[1]*(1-lamdaX) + xColor2.val[1]*(lamdaX)),
|
||||
(byte)(xColor1.val[2]*(1-lamdaX) + xColor2.val[2]*(lamdaX))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static double[] getMiddleColour(Mat img, int row, int col) {
|
||||
double result[] = new double[3];
|
||||
|
||||
double[] pixel1 = img.get(row-1, col-1);
|
||||
double[] pixel2 = img.get(row-1, col);
|
||||
double[] pixel3 = img.get(row-1, col+1);
|
||||
double[] pixel4 = img.get(row, col-1);
|
||||
double[] pixel5 = img.get(row, col);
|
||||
double[] pixel6 = img.get(row, col+1);
|
||||
double[] pixel7 = img.get(row+1, col-1);
|
||||
double[] pixel8 = img.get(row+1, col);
|
||||
double[] pixel9 = img.get(row+1, col+1);
|
||||
|
||||
result[0] = (pixel1[0]+pixel2[0]+pixel3[0]+pixel4[0]+pixel5[0]+pixel6[0]+pixel7[0]+pixel8[0]+pixel9[0])/9.0;
|
||||
result[1] = (pixel1[1]+pixel2[1]+pixel3[1]+pixel4[1]+pixel5[1]+pixel6[1]+pixel7[1]+pixel8[1]+pixel9[1])/9.0;
|
||||
result[2] = (pixel1[2]+pixel2[2]+pixel3[2]+pixel4[2]+pixel5[2]+pixel6[2]+pixel7[2]+pixel8[2]+pixel9[2])/9.0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static Mat skeletonize1(Mat src, Rect contourRect) {
|
||||
Mat skeleton = src;//src.clone();
|
||||
|
||||
int iterationsAmount = 90;
|
||||
for(int k=0; k<iterationsAmount; k++) {
|
||||
System.out.println("Iteration: " + k);
|
||||
|
||||
// Поиск "внешних" точек области
|
||||
List<Point> outsidePoints = new LinkedList<Point>();
|
||||
for(int i=contourRect.y; i<contourRect.y+contourRect.height; i++) {
|
||||
for(int j=contourRect.x; j<contourRect.x+contourRect.width; j++) {
|
||||
if((byte)skeleton.get(i, j)[0] != 0) {
|
||||
int[] fourNeighbors = new int[4];
|
||||
|
||||
// four direct neighbors
|
||||
fourNeighbors[0] = i-1 > 0 ? (int)skeleton.get(i-1, j)[0] & 0xff : 0;
|
||||
fourNeighbors[1] = j+1 < src.cols() ? (int)skeleton.get(i, j+1)[0] & 0xff : 0;
|
||||
fourNeighbors[2] = i+1 < src.rows() ? (int)skeleton.get(i+1, j)[0] & 0xff : 0;
|
||||
fourNeighbors[3] = j-1 > 0 ? (int)skeleton.get(i, j-1)[0] & 0xff : 0;
|
||||
|
||||
for(int neighbor : fourNeighbors) {
|
||||
if(neighbor == 0) {
|
||||
outsidePoints.add(new Point(j, i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(Point removingPoint : outsidePoints) {
|
||||
if(!isTearsUpArea(skeleton, removingPoint)) {
|
||||
skeleton.put((int)removingPoint.y, (int)removingPoint.x, (byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Разрывает ли область удаление данной точки с изображения.
|
||||
* @param img
|
||||
* @param point
|
||||
* @return true, если разрывает; false, если нет.
|
||||
*/
|
||||
public static boolean isTearsUpArea(Mat img, Point point) {
|
||||
int pointRow = (int)point.y;
|
||||
int pointCol = (int)point.x;
|
||||
|
||||
boolean[] neighbors = new boolean[9];
|
||||
|
||||
// считывание соседний пикслелей
|
||||
neighbors[0] = img.get(pointRow-1, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[1] = img.get(pointRow-1, pointCol )[0] != 0.0 ? true : false;
|
||||
neighbors[2] = img.get(pointRow-1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
neighbors[3] = img.get(pointRow, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[4] = img.get(pointRow, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
neighbors[5] = img.get(pointRow+1, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[6] = img.get(pointRow+1, pointCol)[0] != 0.0 ? true : false;
|
||||
neighbors[7] = img.get(pointRow+1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
// вертикальное разделение
|
||||
if(!neighbors[1] && !neighbors[6] && (neighbors[0] || neighbors[3] || neighbors[5]) && (neighbors[2] || neighbors[4] || neighbors[7]))
|
||||
return true;
|
||||
|
||||
// горизонтальное разделение
|
||||
if(!neighbors[3] && !neighbors[4] && (neighbors[0] || neighbors[1] || neighbors[2]) && (neighbors[5] || neighbors[6] || neighbors[7]))
|
||||
return true;
|
||||
|
||||
// случаи изолирования диагональных пикслелей
|
||||
// верхний левый
|
||||
if(!neighbors[3] && !neighbors[1] && (neighbors[5] || neighbors[6] || neighbors[7] || neighbors[4] || neighbors[2]) && neighbors[0])
|
||||
return true;
|
||||
|
||||
// нижний правый
|
||||
if(!neighbors[6] && !neighbors[4] && (neighbors[5] || neighbors[3] || neighbors[0] || neighbors[1] || neighbors[2]) && neighbors[7])
|
||||
return true;
|
||||
|
||||
// нижний левый
|
||||
if(!neighbors[3] && !neighbors[6] && (neighbors[0] || neighbors[1] || neighbors[2] || neighbors[4] || neighbors[7]) && neighbors[5])
|
||||
return true;
|
||||
|
||||
// верхний правый
|
||||
if(!neighbors[1] && !neighbors[4] && (neighbors[0] || neighbors[3] || neighbors[5] || neighbors[6] || neighbors[7]) && neighbors[2])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Разрывает ли область удаление данной точки с изображения.
|
||||
* @param img
|
||||
* @param point
|
||||
* @return true, если разрывает; false, если нет.
|
||||
*/
|
||||
public static boolean isReduceSkeleton(Mat img, Point point) {
|
||||
int pointRow = (int)point.y;
|
||||
int pointCol = (int)point.x;
|
||||
|
||||
boolean[] neighbors = new boolean[9];
|
||||
|
||||
// считывание соседний пикслелей
|
||||
neighbors[0] = img.get(pointRow-1, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[1] = img.get(pointRow-1, pointCol )[0] != 0.0 ? true : false;
|
||||
neighbors[2] = img.get(pointRow-1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
neighbors[3] = img.get(pointRow, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[4] = img.get(pointRow, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
neighbors[5] = img.get(pointRow+1, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[6] = img.get(pointRow+1, pointCol)[0] != 0.0 ? true : false;
|
||||
neighbors[7] = img.get(pointRow+1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
// укорачивание остова толщиной в 1 пиксель
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[7]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow+2, pointCol+2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow+1, pointCol+2 )[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+2, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[7] && neighbors[6]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow+2, pointCol)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow+2, pointCol-1 )[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+2, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[7] && !neighbors[6] && neighbors[5]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow+2, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow+1, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+2, pointCol-1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[7] && !neighbors[5] && !neighbors[6] && neighbors[4]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow, pointCol+2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-1, pointCol+2)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[7] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[3]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-1, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+1, pointCol-2)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[7] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[2]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow-2, pointCol+2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-2, pointCol+1)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow-1, pointCol+2)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!neighbors[0] && !neighbors[7] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[1]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow-2, pointCol)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-2, pointCol+1)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow-2, pointCol-1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!neighbors[7] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[0]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow-2, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-1, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow-2, pointCol-1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Разрывает ли область удаление данной точки с изображения.
|
||||
* @param img
|
||||
* @param point
|
||||
* @return true, если разрывает; false, если нет.
|
||||
*/
|
||||
public static boolean isTearsUpAreaOrReduceSkeleton(Mat img, Point point) {
|
||||
int pointRow = (int)point.y;
|
||||
int pointCol = (int)point.x;
|
||||
|
||||
boolean[] neighbors = new boolean[9];
|
||||
|
||||
// считывание соседний пикслелей
|
||||
neighbors[0] = (pointRow-1 > 0 && pointCol-1 > 0) ? img.get(pointRow-1, pointCol-1)[0] != 0.0 ? true : false : false;
|
||||
neighbors[1] = (pointRow-1 > 0) ? img.get(pointRow-1, pointCol )[0] != 0.0 ? true : false : false;
|
||||
neighbors[2] = (pointRow-1 > 0 && pointCol+1 < img.cols()) ? img.get(pointRow-1, pointCol+1)[0] != 0.0 ? true : false : false;
|
||||
|
||||
neighbors[3] = (pointCol-1 > 0) ? img.get(pointRow, pointCol-1)[0] != 0.0 ? true : false : false;
|
||||
neighbors[4] = (pointCol+1 < img.cols()) ? img.get(pointRow, pointCol+1)[0] != 0.0 ? true : false : false;
|
||||
|
||||
neighbors[5] = (pointRow+1 < img.rows() && pointCol-1 > 0) ? img.get(pointRow+1, pointCol-1)[0] != 0.0 ? true : false : false;
|
||||
neighbors[6] = (pointRow+1 < img.rows()) ? img.get(pointRow+1, pointCol)[0] != 0.0 ? true : false : false;
|
||||
neighbors[7] = (pointRow+1 < img.rows() && pointCol+1 < img.cols()) ? img.get(pointRow+1, pointCol+1)[0] != 0.0 ? true : false : false;
|
||||
|
||||
// вертикальное разделение
|
||||
if(!neighbors[1] && !neighbors[6] && (neighbors[0] || neighbors[3] || neighbors[5]) && (neighbors[2] || neighbors[4] || neighbors[7]))
|
||||
return true;
|
||||
|
||||
// горизонтальное разделение
|
||||
if(!neighbors[3] && !neighbors[4] && (neighbors[0] || neighbors[1] || neighbors[2]) && (neighbors[5] || neighbors[6] || neighbors[7]))
|
||||
return true;
|
||||
|
||||
// случаи изолирования диагональных пикслелей
|
||||
// верхний левый
|
||||
if(!neighbors[3] && !neighbors[1] && (neighbors[5] || neighbors[6] || neighbors[7] || neighbors[4] || neighbors[2]) && neighbors[0])
|
||||
return true;
|
||||
|
||||
// нижний правый
|
||||
if(!neighbors[6] && !neighbors[4] && (neighbors[5] || neighbors[3] || neighbors[0] || neighbors[1] || neighbors[2]) && neighbors[7])
|
||||
return true;
|
||||
|
||||
// нижний левый
|
||||
if(!neighbors[3] && !neighbors[6] && (neighbors[0] || neighbors[1] || neighbors[2] || neighbors[4] || neighbors[7]) && neighbors[5])
|
||||
return true;
|
||||
|
||||
// верхний правый
|
||||
if(!neighbors[1] && !neighbors[4] && (neighbors[0] || neighbors[3] || neighbors[5] || neighbors[6] || neighbors[7]) && neighbors[2])
|
||||
return true;
|
||||
|
||||
|
||||
// укорачивание остова толщиной в 1 пиксель
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[7])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[7] && neighbors[6])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[7] && !neighbors[6] && neighbors[5])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[7] && !neighbors[5] && !neighbors[6] && neighbors[4])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[7] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[3])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[7] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[2])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[7] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[1])
|
||||
return true;
|
||||
|
||||
if(!neighbors[7] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[0])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static Mat skeletonize0(Mat src, Rect contourRect) {
|
||||
Mat skeleton = src; //src.clone();
|
||||
|
||||
List<Point> outsidePoints = new LinkedList<Point>();
|
||||
int iterationsAmount = 100;
|
||||
for(int k=0; k<iterationsAmount; k++) {
|
||||
System.out.println("Iteration: " + k);
|
||||
|
||||
// Выбираются "внешние точки".
|
||||
for(int i=contourRect.y; i<contourRect.y+contourRect.height; i++) {
|
||||
for(int j=contourRect.x; j<contourRect.x+contourRect.width; j++) {
|
||||
if((byte)skeleton.get(i, j)[0] != 0) {
|
||||
int[] fourNeighbors = new int[4];
|
||||
|
||||
// four direct neighbors
|
||||
fourNeighbors[0] = i-1 > 0 ? (int)skeleton.get(i-1, j)[0] & 0xff : 0;
|
||||
fourNeighbors[1] = j+1 < skeleton.cols() ? (int)skeleton.get(i, j+1)[0] & 0xff : 0;
|
||||
fourNeighbors[2] = i+1 < skeleton.rows() ? (int)skeleton.get(i+1, j)[0] & 0xff : 0;
|
||||
fourNeighbors[3] = j-1 > 0 ? (int)skeleton.get(i, j-1)[0] & 0xff : 0;
|
||||
|
||||
for(int neighbor : fourNeighbors) {
|
||||
if(neighbor == 0) {
|
||||
outsidePoints.add(new Point(j, i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Внешние точки удаляются с изображения и их список очищается.
|
||||
//for(Point removingPoint : outsidePoints) {
|
||||
/*
|
||||
for(int l=0; l<outsidePoints.size(); l++) {
|
||||
Point removingPoint = outsidePoints.get(l);
|
||||
|
||||
if(isReduceSkeleton(skeleton, removingPoint)) {
|
||||
outsidePoints.remove(l--);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
for(Point removingPoint : outsidePoints) {
|
||||
if(!isTearsUpArea(skeleton, removingPoint) && !isReduceSkeleton(skeleton, removingPoint)) {
|
||||
skeleton.put((int)removingPoint.y, (int)removingPoint.x, 0);
|
||||
}
|
||||
}
|
||||
|
||||
outsidePoints.clear();
|
||||
}
|
||||
|
||||
return skeleton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
package ru.delkom07.recognition.potatoes.training;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.filters.HSVBinarization;
|
||||
import org.wheatdb.seedcounter.server.DefaultFilesAndDirs;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
|
||||
public class PotatoBinarizationTraining {
|
||||
static class ImageFilter implements java.io.FileFilter {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
if(pathname.getName().matches(".+\\.(png|PNG|jpg|JPG|bmp|BMP)"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String changeExtension(String fileName, String extension) {
|
||||
return FilenameUtils.removeExtension(fileName) + "." + extension;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
File inputDir = DefaultFilesAndDirs.inputDir;
|
||||
File outputDir = DefaultFilesAndDirs.outputDir;
|
||||
File markedDir = DefaultFilesAndDirs.markedDir;
|
||||
|
||||
File outputDataFile = new File(outputDir, "data.txt");
|
||||
PrintWriter pw = new PrintWriter(outputDataFile);
|
||||
|
||||
File[] inputImgFiles = inputDir.listFiles(new PotatoBinarizationTraining.ImageFilter());
|
||||
|
||||
List<Mat> inputs = new ArrayList<Mat>();
|
||||
List<Mat> markeds = new ArrayList<Mat>();
|
||||
|
||||
for(File inputImgFile: inputImgFiles) {
|
||||
File markedImgFile = new File(markedDir, changeExtension(inputImgFile.getName(), "png"));
|
||||
|
||||
Mat input = Imgcodecs.imread(inputImgFile.getCanonicalPath());
|
||||
Mat marked = Imgcodecs.imread(markedImgFile.getCanonicalPath());
|
||||
|
||||
|
||||
|
||||
Size newSize = new Size(input.size().width/8, input.size().height/8);
|
||||
Imgproc.resize(input, input, newSize);
|
||||
Imgproc.resize(marked, marked, newSize);
|
||||
|
||||
|
||||
inputs.add(input);
|
||||
markeds.add(marked);
|
||||
}
|
||||
|
||||
// Training
|
||||
//List<HSVParameters> parentPopulation = generateRandomPopulation(300, 3);
|
||||
//List<HSVParameters> parentPopulation = getWhitePotatoesHSVPreset();
|
||||
//List<HSVParameters> parentPopulation = getDarkPotatoesHSVPreset2();
|
||||
|
||||
List<HSVParameters> parentPopulation = getCommonPotatoesHSVPreset();
|
||||
//parentPopulation.addAll(getCommonPotatoesHSVPreset2());
|
||||
//parentPopulation.addAll(getWhitePotatoesHSVPreset());
|
||||
//parentPopulation.addAll(getWhitePotatoesHSVPreset2());
|
||||
//parentPopulation.addAll(getDarkPotatoesHSVPreset());
|
||||
//parentPopulation.addAll(getDarkPotatoesHSVPreset2());
|
||||
|
||||
for(int i=0; i<100; i++) {
|
||||
System.out.println("Training iteration: " + i);
|
||||
|
||||
List<HSVParameters> childPopulation = new LinkedList<HSVParameters>();
|
||||
if(i==0) {
|
||||
childPopulation.addAll(parentPopulation);
|
||||
} else {
|
||||
childPopulation.addAll(newGeneration(parentPopulation, 10));
|
||||
}
|
||||
|
||||
for(HSVParameters childParam : childPopulation) {
|
||||
HSVBinarization hsv = new HSVBinarization(childParam.getParams());
|
||||
|
||||
double middleJIndex = 0;
|
||||
double sumJIndex = 0;
|
||||
|
||||
for(int imgI=0; imgI<inputs.size(); imgI++) {
|
||||
Mat input = inputs.get(imgI);
|
||||
Mat marked = markeds.get(imgI);
|
||||
|
||||
Mat binarized = hsv.apply(input);
|
||||
|
||||
sumJIndex += MatFunctions.jaccardIndex(binarized, marked);
|
||||
binarized.release();
|
||||
}
|
||||
|
||||
middleJIndex = sumJIndex/((double)inputImgFiles.length);
|
||||
childParam.setJIndex(middleJIndex);
|
||||
System.out.println(middleJIndex);
|
||||
}
|
||||
|
||||
Collections.sort(childPopulation, new Comparator<HSVParameters>() {
|
||||
@Override
|
||||
public int compare(HSVParameters arg0, HSVParameters arg1) {
|
||||
if(arg0.getJIndex() < arg1.getJIndex()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(arg0.getJIndex() > arg1.getJIndex()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
parentPopulation.clear();
|
||||
for(int l=0; l<10 && l<childPopulation.size(); l++) {
|
||||
double jIndex = childPopulation.get(l).getJIndex();
|
||||
|
||||
if(l>2 && jIndex < 0.6) {
|
||||
break;
|
||||
}
|
||||
|
||||
System.out.println("\nOne of best param: " + childPopulation.get(l).getParams().toString());
|
||||
System.out.println("JIndex: " + jIndex + "\n");
|
||||
|
||||
pw.println("\nOne of best param: " + childPopulation.get(l).getParams().toString());
|
||||
pw.println("JIndex: " + jIndex + "\n");
|
||||
pw.flush();
|
||||
|
||||
parentPopulation.add(childPopulation.get(l));
|
||||
}
|
||||
childPopulation.clear();
|
||||
}
|
||||
|
||||
for(int j=0; j<inputs.size(); j++) {
|
||||
inputs.get(j).release();
|
||||
markeds.get(j).release();
|
||||
}
|
||||
|
||||
|
||||
HSVParameters bestParam = null;
|
||||
double bestJIndex = 0;
|
||||
for(HSVParameters hsvParam : parentPopulation) {
|
||||
if(hsvParam.getJIndex() > bestJIndex) {
|
||||
bestJIndex = hsvParam.getJIndex();
|
||||
bestParam = hsvParam;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(bestParam != null) {
|
||||
for(File inputImgFile: inputImgFiles) {
|
||||
File outputImgFile = new File(outputDir, changeExtension("binarized_" + inputImgFile.getName(), "png"));
|
||||
|
||||
Mat input = Imgcodecs.imread(inputImgFile.getCanonicalPath());
|
||||
|
||||
HSVBinarization hsv = new HSVBinarization(bestParam.getParams());
|
||||
Mat binarized = hsv.apply(input);
|
||||
|
||||
Imgcodecs.imwrite(outputImgFile.getCanonicalPath(), binarized);
|
||||
//new Display().setImage(binarized);
|
||||
|
||||
input.release();
|
||||
binarized.release();
|
||||
|
||||
System.out.println("Result best param: " + bestParam.toString());
|
||||
pw.println("Result best param: " + bestParam.toString());
|
||||
pw.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pw.close();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static List<HSVParameters> generateRandomPopulation(int populationSize, int paramDimention) {
|
||||
List<HSVParameters> randomPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
for(int i=0; i<populationSize; i++) {
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
for(int j=0; j<paramDimention; j++) {
|
||||
int randH = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
int randS = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
int randV = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
|
||||
int randDivH = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
int randDivS = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
int randDivV = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
|
||||
Scalar target = new Scalar(randH, randS, randV);
|
||||
Scalar range = new Scalar(randDivH, randDivS, randDivV);
|
||||
|
||||
parameters.add(new Pair<Scalar, Scalar>(target, range));
|
||||
}
|
||||
|
||||
randomPopulation.add(new HSVParameters(parameters));
|
||||
}
|
||||
|
||||
return randomPopulation;
|
||||
}
|
||||
|
||||
|
||||
public static List<HSVParameters> getCommonPotatoesHSVPreset() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target3 = new Scalar(1, 241, 73);
|
||||
Scalar range3 = new Scalar(56, 208, 197);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(171, 81, 144);
|
||||
Scalar range4 = new Scalar(20, 43, 108);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
public static List<HSVParameters> getCommonPotatoesHSVPreset2() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(161.0, 81.0, 144.0);
|
||||
Scalar range1 = new Scalar(29.0, 43.0, 108.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(161.0, 72.0, 142.0);
|
||||
Scalar range2 = new Scalar(27.0, 38.0, 109.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
Scalar target3 = new Scalar(15.0, 138.0, 207.0);
|
||||
Scalar range3 = new Scalar(41.0, 94.0, 144.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(161.0, 72.0, 142.0);
|
||||
Scalar range4 = new Scalar(27.0, 38.0, 109.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
Scalar target5 = new Scalar(37.0, 231.0, 41.0);
|
||||
Scalar range5 = new Scalar(51.0, 198.0, 145.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target5, range5));
|
||||
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<HSVParameters> getWhitePotatoesHSVPreset() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(14.0, 139.0, 211.0);
|
||||
Scalar range1 = new Scalar(41.0, 90.0, 147.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(12.0, 134.0, 201.0);
|
||||
Scalar range2 = new Scalar(37.0, 94.0, 142.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
Scalar target3 = new Scalar(18.0, 138.0, 204.0);
|
||||
Scalar range3 = new Scalar(46.0, 91.0, 149.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(13.0, 133.0, 207.0);
|
||||
Scalar range4 = new Scalar(42.0, 89.0, 141.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
Scalar target5 = new Scalar(42.0, 238.0, 50.0);
|
||||
Scalar range5 = new Scalar(54.0, 199.0, 153.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target5, range5));
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
|
||||
public static List<HSVParameters> getWhitePotatoesHSVPreset2() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(99, 130, 150);
|
||||
Scalar range1 = new Scalar(188, 74, 232);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<HSVParameters> getDarkPotatoesHSVPreset() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(13.0, 192.0, 27.0);
|
||||
Scalar range1 = new Scalar(241.0, 158.0, 141.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(170.0, 35.0, 44.0);
|
||||
Scalar range2 = new Scalar(40.0, 50.0, 30.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
public static List<HSVParameters> getDarkPotatoesHSVPreset2() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
// fiolet
|
||||
Scalar target1 = new Scalar(170, 75, 160);
|
||||
Scalar range1 = new Scalar(25, 40, 100);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
// yellow
|
||||
Scalar target2 = new Scalar(10, 87, 200);
|
||||
Scalar range2 = new Scalar(10, 50, 150);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static List<HSVParameters> newGeneration(List<HSVParameters> generation, int increment) {
|
||||
List<HSVParameters> newGeneration = new LinkedList<HSVParameters>();
|
||||
|
||||
Random generator = new Random();
|
||||
for(int i=0; i<increment; i++) {
|
||||
int rand1 = generator.nextInt(generation.size());
|
||||
int rand2 = generator.nextInt(generation.size());
|
||||
|
||||
|
||||
HSVParameters child = cross(generation.get(rand1), generation.get(rand2));
|
||||
if(generator.nextInt(5) < 2) {
|
||||
child = mutation(child);
|
||||
}
|
||||
|
||||
newGeneration.add(child);
|
||||
|
||||
}
|
||||
|
||||
newGeneration.addAll(generation);
|
||||
|
||||
return newGeneration;
|
||||
}
|
||||
|
||||
|
||||
public static HSVParameters cross(HSVParameters parent1, HSVParameters parent2) {
|
||||
List<Pair<Scalar, Scalar>> params = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> prt1Params = parent1.getParams();
|
||||
List<Pair<Scalar, Scalar>> prt2Params = parent2.getParams();
|
||||
|
||||
int prt1ParamsSize = prt1Params.size();
|
||||
int prt2ParamsSize = prt2Params.size();
|
||||
|
||||
int childParamsSize = Integer.max(prt1ParamsSize, prt2ParamsSize);
|
||||
|
||||
Random generator = new Random();
|
||||
for(int i=0; i<childParamsSize; i++) {
|
||||
if(i%2 !=0) {
|
||||
int rand = generator.nextInt(prt1ParamsSize);
|
||||
params.add(prt1Params.get(rand));
|
||||
} else {
|
||||
int rand = generator.nextInt(prt2ParamsSize);
|
||||
params.add(prt2Params.get(rand));
|
||||
}
|
||||
}
|
||||
|
||||
return new HSVParameters(params);
|
||||
}
|
||||
|
||||
|
||||
public static HSVParameters mutation(HSVParameters parent) {
|
||||
final int mutationSize = 10;
|
||||
|
||||
List<Pair<Scalar, Scalar>> childParameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
for(Pair<Scalar, Scalar> param : parent.getParams()) {
|
||||
Scalar parentTarget = param.getLeft();
|
||||
Scalar parentRange = param.getRight();
|
||||
|
||||
int mutatedH = (int)(parentTarget.val[0]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
int mutatedS = (int)(parentTarget.val[1]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
int mutatedV = (int)(parentTarget.val[2]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
|
||||
int mutatedDivH = (int)(parentRange.val[0]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
int mutatedDivS = (int)(parentRange.val[1]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
int mutatedDivV = (int)(parentRange.val[2]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
|
||||
// проврка что > 0 && < 255
|
||||
mutatedH = mutatedH < 0 ? 0 : (mutatedH > 255 ? 255 : mutatedH);
|
||||
mutatedS = mutatedS < 0 ? 0 : (mutatedS > 255 ? 255 : mutatedS);
|
||||
mutatedV = mutatedV < 0 ? 0 : (mutatedV > 255 ? 255 : mutatedV);
|
||||
|
||||
mutatedDivH = mutatedDivH < 0 ? 0 : (mutatedDivH > 255 ? 255 : mutatedDivH);
|
||||
mutatedDivS = mutatedDivS < 0 ? 0 : (mutatedDivS > 255 ? 255 : mutatedDivS);
|
||||
mutatedDivV = mutatedDivV < 0 ? 0 : (mutatedDivV > 255 ? 255 : mutatedDivV);
|
||||
|
||||
Scalar childTarget = new Scalar(mutatedH, mutatedS, mutatedV);
|
||||
Scalar childRange = new Scalar(mutatedDivH, mutatedDivS, mutatedDivV);
|
||||
|
||||
|
||||
childParameters.add(new Pair<Scalar, Scalar>(childTarget, childRange));
|
||||
}
|
||||
|
||||
return new HSVParameters(childParameters);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,866 @@
|
||||
package ru.delkom07.util.xml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.w3c.dom.Attr;
|
||||
import org.w3c.dom.DOMException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.w3c.dom.TypeInfo;
|
||||
import org.w3c.dom.UserDataHandler;
|
||||
|
||||
/**
|
||||
* Класс-обертка на манер JQuery.
|
||||
* Класс предоставляет удобные методы для работы с элементами DOM дерева.
|
||||
* В JElement переработаны ВСЕ методы класса Element - в разделе "Переработанные делегированные методы".
|
||||
* Так же реализованы дополнительные методы для работы с элементами дерева.
|
||||
* Методы отмечены:
|
||||
* "=" - абсолютные аналоги методам Element (ничем не отличаются).
|
||||
* "+" - аналоги методам Element, при этом они возвращают JElement или List<JElement> вместо Node, NodeList и т.д..
|
||||
* "+ Semantically changed" - аналоги методам Element сематнически реализованные иначе.
|
||||
* "+ from <method()>" - аналоги методам Element <method()> с измененным названием в связи с измененной семантикой.
|
||||
* Таким образом данный класс реализует и расширяет полный функционал Element, Node, и частично другие...
|
||||
* @author Komyshev
|
||||
* Документация для некоторых методов в конце незакончена. Методы выборки по паттерну не умеют работать с
|
||||
* несколькими аттрибутами.
|
||||
* Library class v.0.9 (Development(С). )
|
||||
*/
|
||||
public class JElement {
|
||||
private Element element = null;
|
||||
|
||||
// -- Обервывание --
|
||||
/**
|
||||
* Конструктор
|
||||
* Обертывает элемент класса org.w3c.dom.Element.
|
||||
* @param element
|
||||
*/
|
||||
public JElement(Element element) {
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Конструктор.
|
||||
* Создает новый Element обернутый в JElement.
|
||||
* @param doc элемент документа.
|
||||
* @param tagName имя элемента.
|
||||
*/
|
||||
public JElement(Document doc, String tagName) {
|
||||
this.element = doc.createElement(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Вернуть обертываемый элемент.
|
||||
* @return - обертываемый элемент.
|
||||
*/
|
||||
public Element getElement() {
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
// -- Методы расширяющие функционал --
|
||||
/** ++
|
||||
* Получить первый дочерний элемент.
|
||||
* @return - первый дочерний элемент если он существует, иначе null.
|
||||
*/
|
||||
public JElement get() {
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
if(Node.ELEMENT_NODE==node.getNodeType()){
|
||||
return new JElement((Element) node);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** ++
|
||||
* Получить первый дочерний элемент по паттерну.
|
||||
* Не сохраняет результаты поиска.
|
||||
* Формат паттерна: tagName#id.class&attr=value
|
||||
* @param pattern паттерн.
|
||||
* @return соответствующий элемент, или null если его не существует.
|
||||
*/
|
||||
public JElement get(String pattern) {
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
if(Node.ELEMENT_NODE==node.getNodeType()){
|
||||
if(isMatched((Element) node, pattern))
|
||||
return new JElement((Element) node);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** ++
|
||||
* Получить все дочерние элементы соответсвующие паттерну.
|
||||
* Формат паттерна: tagName#id.class&attr=value
|
||||
* @param pattern паттерн.
|
||||
* @return список элементов соответсвующий паттерну.
|
||||
*/
|
||||
public List<JElement> getAll(String pattern) {
|
||||
List<JElement> list = new ArrayList<JElement>();
|
||||
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
if(Node.ELEMENT_NODE==node.getNodeType()){
|
||||
if(isMatched((Element) node, pattern))
|
||||
list.add(new JElement((Element) node));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** ++
|
||||
* Получить первый элемент-потомок по паттерну.
|
||||
* Формат паттерна: tagName#id.class&attr=value
|
||||
* @param pattern паттерн.
|
||||
* @return соответствующий элемент, или null если его не существует.
|
||||
*/
|
||||
public JElement getGlob(String pattern) {
|
||||
Map<String, String> params = parsePattern(pattern);
|
||||
|
||||
String tagName = params.get("tagName");
|
||||
NodeList nodeList = element.getElementsByTagName(tagName);
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Element el = (Element) nodeList.item(i);
|
||||
if(matched(el, params))
|
||||
return new JElement(el);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** ++
|
||||
* Получить все элементы-потомки соответсвующие паттерну.
|
||||
* Формат паттерна: tagName#id.class&attr=value
|
||||
* @param pattern паттерн.
|
||||
* @return список элементов соответсвующий паттерну.
|
||||
*/
|
||||
public List<JElement> getAllGlob(String pattern) {
|
||||
List<JElement> list = new ArrayList<JElement>();
|
||||
Map<String, String> params = parsePattern(pattern);
|
||||
|
||||
NodeList nodeList = element.getElementsByTagName(params.get("tagName"));
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Element el = (Element) nodeList.item(i);
|
||||
if(matched(el, params))
|
||||
list.add(new JElement(el));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** ++
|
||||
* Удалить элемент.
|
||||
*/
|
||||
public void remove() {
|
||||
element.getParentNode().removeChild(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Соответствует ли элемент заданному паттерну.
|
||||
* @param el элемент.
|
||||
* @param pattern паттерн.
|
||||
* @return true если соответствует, false иначе.
|
||||
*/
|
||||
private boolean isMatched(Element el, String pattern) {
|
||||
Map<String, String> param = parsePattern(pattern);
|
||||
return matched(el, param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Соответствует ли элемент заданному паттерну.
|
||||
* @param el элемент.
|
||||
* @param params карта параметров.
|
||||
* @return true если соответствует, false иначе.
|
||||
*/
|
||||
private boolean matched(Element el, Map<String, String> params) {
|
||||
// Проверим имя
|
||||
String tagName = params.get("tagName");
|
||||
if(null != tagName)
|
||||
if(!el.getNodeName().equals(tagName))
|
||||
return false; // Без разговоров
|
||||
|
||||
// Проверим id если указан
|
||||
String id = params.get("id");
|
||||
if(null != id)
|
||||
if(!el.getAttribute("id").equals(id))
|
||||
return false; // Тоже дальше не заморачиваемся
|
||||
|
||||
// Классов у элемента может быть много, надо проверить все
|
||||
String class_ = params.get("class");
|
||||
if(null != class_) {
|
||||
boolean matchClass = false; // может совпадет кто нибудь
|
||||
String[] array = el.getAttribute("class").split(" ");
|
||||
for(int i=0; i<array.length; i++) {
|
||||
if(el.getAttribute("class").equals(class_))
|
||||
matchClass = true; // совпал
|
||||
}
|
||||
if(!matchClass) return false; // если не совпал не один - на выход
|
||||
}
|
||||
|
||||
// Проверим аттрибут и его значение, если указан
|
||||
String attr = params.get("attr");
|
||||
String value = params.get("value");
|
||||
if(null != attr && null != value)
|
||||
if(!el.getAttribute(attr).equals(value))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Распарсить паттерн на составляющие параметры.
|
||||
* Формат паттерна: tagName#id.class&attr=value - по одному параметру на элемент.
|
||||
* @param pattern паттерн.
|
||||
* @return - карта с составляющими параметрами.
|
||||
*/
|
||||
private Map<String, String> parsePattern(String pattern) {
|
||||
//pattern.matches("\\w+[#\\w+]?[\\.\\w+]?[&\\w]?");
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
if(""==pattern) return map;
|
||||
|
||||
String subStr = ""; int i;
|
||||
for(i=1; i<pattern.length(); i++) {
|
||||
char ch = pattern.charAt(i);
|
||||
if('#'==ch || '.'==ch || '&'==ch) {
|
||||
subStr = pattern.substring(0, i);
|
||||
Map<String, String> subMap = parsePattern(pattern.substring(i));
|
||||
map.putAll(subMap);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(subStr.equals(""))
|
||||
subStr = pattern.substring(0, i);
|
||||
|
||||
char ch0 = pattern.charAt(0);
|
||||
if('#'==ch0) {
|
||||
map.put("id", subStr.substring(1));
|
||||
} else
|
||||
if('.'==ch0) {
|
||||
map.put("class", subStr.substring(1));
|
||||
} else
|
||||
if('&'==ch0) {
|
||||
String[] arr = subStr.substring(1).split("=");
|
||||
map.put("attr", arr[0]);
|
||||
map.put("value", arr[1]);
|
||||
} else {
|
||||
map.put("tagName", subStr);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
// -- Переработанные делегированные методы --
|
||||
/* Должны соответствовать всем методам класса Element.
|
||||
* При этом некоторые методы принимающие параметры, раздваиваются в данном классе
|
||||
* т.к необходимо чтобы методы работали как для Element, так и для JElement.*/
|
||||
/** +
|
||||
* Добавить дочерний элемент в текущий.
|
||||
* @param newChild - добавляемый элемент.
|
||||
* @return - текущий элемент с добавленным элементом.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public JElement appendChild(JElement newChild) throws DOMException {
|
||||
return new JElement((Element) element.appendChild(newChild.getElement()));
|
||||
}
|
||||
public JElement appendChild(Element newChild) throws DOMException {
|
||||
return new JElement((Element) element.appendChild(newChild));
|
||||
}
|
||||
|
||||
/** +
|
||||
* Клонировать элемент.
|
||||
* @param deep - клонировать ли потомков.
|
||||
* @return - клонированный элемент.
|
||||
*/
|
||||
public JElement cloneJElement(boolean deep) {
|
||||
return new JElement((Element) element.cloneNode(deep));
|
||||
}
|
||||
|
||||
/** =
|
||||
* Сравнить позицию элемента в документе.
|
||||
* @return - позиция элемента в документе относительно другого.
|
||||
*/
|
||||
public short compareDocumentPosition(JElement other) throws DOMException {
|
||||
return element.compareDocumentPosition(other.getElement());
|
||||
}
|
||||
public short compareDocumentPosition(Element other) throws DOMException {
|
||||
return element.compareDocumentPosition(other);
|
||||
}
|
||||
|
||||
/** + Semantically changed
|
||||
* Получить все аттрибуты элемента.
|
||||
* @return все аттрибуты элемента в виде карты.
|
||||
*/
|
||||
public Map<String, String> getAttributes() {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
|
||||
NamedNodeMap nodeMap = element.getAttributes();
|
||||
for(int i=0; i<nodeMap.getLength(); i++){
|
||||
Attr attribute = (Attr) nodeMap.item(i);
|
||||
map.put(attribute.getName(), attribute.getValue());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить базовый URI.
|
||||
* @return базовый URI или null, если не удается извлеч.
|
||||
*/
|
||||
public String getBaseURI() {
|
||||
return element.getBaseURI();
|
||||
}
|
||||
|
||||
/** + Semantically changed - from getChildNodes()
|
||||
* Получить все дочерние элементы.
|
||||
* @return все дочерни элементы в виде списка.
|
||||
*/
|
||||
public List<JElement> getChildJElements() {
|
||||
LinkedList<JElement> list = new LinkedList<JElement>();
|
||||
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
|
||||
// Проверка на то что Node это Element
|
||||
if(Node.ELEMENT_NODE == node.getNodeType())
|
||||
list.add(new JElement( (Element) node));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить фитчу.
|
||||
* @return - фитча.
|
||||
*/
|
||||
public Object getFeature(String arg0, String arg1) {
|
||||
return element.getFeature(arg0, arg1);
|
||||
}
|
||||
|
||||
/** + Semantically changed
|
||||
* Получить первый дочерний элемент.
|
||||
* @return первый дочерний элемент.
|
||||
*/
|
||||
public JElement getFirstChild() {
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
if(Node.ELEMENT_NODE == node.getNodeType())
|
||||
return new JElement( (Element) node );
|
||||
}
|
||||
return null; // Если не нашел
|
||||
}
|
||||
|
||||
/** + Semantically changed
|
||||
* Получить последний дочерний элемент.
|
||||
* @return последний дочерний элемент.
|
||||
*/
|
||||
public JElement getLastChild() {
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
for(int i=nodeList.getLength()-1; i>=0; i--) {
|
||||
Node node = nodeList.item(i);
|
||||
if(Node.ELEMENT_NODE == node.getNodeType())
|
||||
return new JElement( (Element) node );
|
||||
}
|
||||
return null; // Если не нашел
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить локальное имя элемента.
|
||||
* @return локальное имя элемента.
|
||||
*/
|
||||
public String getLocalName() {
|
||||
return element.getLocalName();
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить URI пространства имен.
|
||||
* @return URI пространства имен.
|
||||
*/
|
||||
public String getNamespaceURI() {
|
||||
return element.getNamespaceURI();
|
||||
}
|
||||
|
||||
/** +
|
||||
* Получить следующий равноуровневый элемент.
|
||||
* @return следующий равноуровневый элемент.
|
||||
*/
|
||||
public JElement getNextSibling() {
|
||||
Node node = element.getNextSibling();
|
||||
if(null == node) return null;
|
||||
|
||||
while(Node.ELEMENT_NODE != node.getNodeType()) {
|
||||
node = node.getNextSibling();
|
||||
if(null == node) return null;
|
||||
}
|
||||
|
||||
return new JElement( (Element) node );
|
||||
}
|
||||
|
||||
/** + from getNodeName()
|
||||
* Получить название элемента.
|
||||
* @return название элемента.
|
||||
*/
|
||||
public String getJElementName() {
|
||||
return element.getNodeName();
|
||||
}
|
||||
public String getName() {
|
||||
return element.getNodeName();
|
||||
}
|
||||
|
||||
/** + Not userful. Return Node.ELEMENT_NODE always.
|
||||
* Получить тип нода.
|
||||
* @return тип нода.
|
||||
*/
|
||||
public short getNodeType() {
|
||||
return element.getNodeType();
|
||||
}
|
||||
|
||||
/** + from getNodeValue()
|
||||
* Получить значение элемента.
|
||||
* @return значение элемента.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public String getJElementValue() throws DOMException {
|
||||
return element.getNodeValue();
|
||||
}
|
||||
public String getValue() {
|
||||
return element.getNodeValue();
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить докумет-владельца.
|
||||
* @return докумет-владелец.
|
||||
*/
|
||||
public Document getOwnerDocument() {
|
||||
return element.getOwnerDocument();
|
||||
}
|
||||
|
||||
/** + from getParentNode()
|
||||
* Получить родительский элемент.
|
||||
* @return родительский элемент.
|
||||
*/
|
||||
public JElement getParentJElement() {
|
||||
/* По сути - родитель должен быть типа Element, так как мы не используем всякие там
|
||||
* аттрибуты в качестве JElement... не так ли ? */
|
||||
return new JElement( (Element) element.getParentNode() );
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить префикс.
|
||||
* @return префикс.
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return element.getPrefix();
|
||||
}
|
||||
|
||||
/** +
|
||||
* Получить предидующий равноуровневый элемент.
|
||||
* @return предидующий равноуровневый элемент.
|
||||
*/
|
||||
public JElement getPreviousSibling() {
|
||||
Node node = element.getPreviousSibling();
|
||||
if(null == node) return null;
|
||||
|
||||
while(Node.ELEMENT_NODE != node.getNodeType()) {
|
||||
node = node.getPreviousSibling();
|
||||
if(null == node) return null;
|
||||
}
|
||||
|
||||
return new JElement( (Element) node );
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить текстовое содержание.
|
||||
* @return текстовое содержание.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public String getTextContent() throws DOMException {
|
||||
return element.getTextContent();
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить пользовательские данные.
|
||||
* @param key ключ ассоциированный с данными.
|
||||
* @return пользовательские данные.
|
||||
*/
|
||||
public Object getUserData(String key) {
|
||||
return element.getUserData(key);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Есть ли аттрибуты у элемента.
|
||||
* @return true если есть, false иначе.
|
||||
*/
|
||||
public boolean hasAttributes() {
|
||||
return element.hasAttributes();
|
||||
}
|
||||
|
||||
/** + Semantically changed from hasChildNodes()
|
||||
* Есть ли дочерние элементы.
|
||||
* @return true если есть, false иначе.
|
||||
*/
|
||||
public boolean hasChildElements() {
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
if(Node.ELEMENT_NODE == nodeList.item(i).getNodeType())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** + Semantically changed
|
||||
* Вставить newChild перед refChild. Если refChild null то в конец child-списка.
|
||||
* @param newChild вставляемый элемент.
|
||||
* @param refChild элемент относительно которого вставляем.
|
||||
* @return вставленный элемент.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public JElement insertBefore(JElement newChild, JElement refChild) throws DOMException {
|
||||
return new JElement( (Element) element.insertBefore(newChild.getElement(), refChild.getElement()) );
|
||||
}
|
||||
|
||||
/** =
|
||||
* Является ли пространство имен пространством имен по умолчанию.
|
||||
* @param namespace пространство имен для проверки.
|
||||
* @return true если является, false иначе.
|
||||
*/
|
||||
public boolean isDefaultNamespace(String namespace) {
|
||||
return element.isDefaultNamespace(namespace);
|
||||
}
|
||||
|
||||
/** + from isEqualNode()
|
||||
* Является ли данный элемент равным текущему.
|
||||
* @param elem элемент для проверки.
|
||||
* @return true если является, false иначе.
|
||||
*/
|
||||
public boolean isEqualJElement(JElement elem) {
|
||||
return element.isEqualNode(elem.getElement());
|
||||
}
|
||||
|
||||
/** + from isSameNode()
|
||||
* Является ли данный элемент тем же самым что и текущий.
|
||||
* @param elem элемент для проверки.
|
||||
* @return true если является, false иначе.
|
||||
*/
|
||||
public boolean isSameJElement(JElement elem) {
|
||||
return element.isSameNode(elem.getElement());
|
||||
}
|
||||
|
||||
/** =
|
||||
* Поддерживает ли текущая реализация DOM данную фитчу (см. Element документацию).
|
||||
* @param feature фитча.
|
||||
* @param version версия.
|
||||
* @return true если поддерживает, false иначе.
|
||||
*/
|
||||
public boolean isSupported(String feature, String version) {
|
||||
return element.isSupported(feature, version);
|
||||
}
|
||||
|
||||
/** =
|
||||
* См Element.lookupNamespaceURI() documentation.
|
||||
*/
|
||||
public String lookupNamespaceURI(String arg0) {
|
||||
return element.lookupNamespaceURI(arg0);
|
||||
}
|
||||
|
||||
/** =
|
||||
* См Element.lookupPrefix() documentation.
|
||||
*/
|
||||
public String lookupPrefix(String arg0) {
|
||||
return element.lookupPrefix(arg0);
|
||||
}
|
||||
|
||||
/** =
|
||||
* См Element.normalize() documentation.
|
||||
*/
|
||||
public void normalize() {
|
||||
element.normalize();
|
||||
}
|
||||
|
||||
/** + from removeChild(Node)
|
||||
* Удалить дочерний элемент.
|
||||
* @param child удаляемый дочерний элемент.
|
||||
* @return удаленный дочерний элемент.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public JElement removeChild(Element child) {
|
||||
element.removeChild(child);
|
||||
return new JElement(child);
|
||||
}
|
||||
public JElement removeChild(JElement child) throws DOMException {
|
||||
element.removeChild(child.getElement());
|
||||
return child;
|
||||
}
|
||||
|
||||
/** + from replaceChild()
|
||||
* Заменить дочерний элемент.
|
||||
* @param newChild новый дочерний элемент.
|
||||
* @param oldChild старый дочерний элемент.
|
||||
* @return замененный дочерний элемент.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public Node replaceChild(JElement newChild, JElement oldChild) throws DOMException {
|
||||
return element.replaceChild(newChild.getElement(), oldChild.getElement());
|
||||
}
|
||||
|
||||
/** + from setNodeValue()
|
||||
* Установить значение элемента.
|
||||
* @param value значение элемента.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void setElementValue(String value) throws DOMException {
|
||||
element.setNodeValue(value);
|
||||
}
|
||||
public void setJElementValue(String value) throws DOMException {
|
||||
element.setNodeValue(value);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить префикс элемента.
|
||||
* @param prefix префикс элемента.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void setPrefix(String prefix) throws DOMException {
|
||||
element.setPrefix(prefix);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить текстовое содержание элемента.
|
||||
* @param content текстовое содержание элемента.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void setTextContent(String content) throws DOMException {
|
||||
element.setTextContent(content);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить пользовательские данные.
|
||||
* @param key ключ ассоциированный с данными.
|
||||
* @param data данные.
|
||||
* @param handler The handler to associate to that key, or null.
|
||||
* @return Returns the DOMUserData previously associated to the given key on this node, or null if there was none.
|
||||
*/
|
||||
public Object setUserData(String key, Object data, UserDataHandler handler) {
|
||||
return element.setUserData(key, data, handler);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить аттрибут по его имени.
|
||||
* @param attrName - имя аттрибута.
|
||||
* @return аттрибут или null, если такого аттрибута нет.
|
||||
*/
|
||||
public String getAttribute(String attrName) {
|
||||
return element.getAttribute(attrName);
|
||||
}
|
||||
|
||||
|
||||
/** =
|
||||
* Получить аттрибут по его имени и NS.
|
||||
* @param attrName - имя аттрибута.
|
||||
* @param namespace - пространство имен.
|
||||
* @return аттрибут или null, если такого аттрибута нет.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public String getAttributeNS(String attrName, String namespace) throws DOMException {
|
||||
return element.getAttributeNS(attrName, namespace);
|
||||
}
|
||||
|
||||
|
||||
/** =
|
||||
* Получить нод аттрибута.
|
||||
* @param name название аттрибута.
|
||||
* @return нод аттрибута.
|
||||
*/
|
||||
public Attr getAttributeNode(String name) {
|
||||
return element.getAttributeNode(name);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить нод аттрибута с заданным пространством имен.
|
||||
* @param namespaceURI URI пространства имен.
|
||||
* @param localname название аттрибута.
|
||||
* @return нод аттрибута.
|
||||
*/
|
||||
public Attr getAttributeNodeNS(String namespaceURI, String localname) throws DOMException {
|
||||
return element.getAttributeNodeNS(namespaceURI, localname);
|
||||
}
|
||||
|
||||
/** + Semantically changed from getElementsByTagName
|
||||
* Получить элементы по имени тега.
|
||||
*/
|
||||
public List<JElement> getJElementsByTagName(String name) {
|
||||
List<JElement> list = new LinkedList<JElement>();
|
||||
|
||||
NodeList nodeList = element.getElementsByTagName(name);
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
// Проверка на то что Node это Element не нужна
|
||||
list.add(new JElement( (Element) nodeList.item(i) ));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/** + Semantically changed from getElementsByTagNameNS
|
||||
* Получить элементы по имени тега и пространству имен.
|
||||
*/
|
||||
public List<JElement> getJElementsByTagNameNS(String name, String namespace) throws DOMException {
|
||||
LinkedList<JElement> list = new LinkedList<JElement>();
|
||||
|
||||
NodeList nodeList = element.getElementsByTagNameNS(name, namespace);
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
// Проверка на то что Node это Element не нужна
|
||||
list.add(new JElement( (Element) nodeList.item(i) ));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить информацию о типе схемы.
|
||||
* @return
|
||||
*/
|
||||
public TypeInfo getSchemaTypeInfo() {
|
||||
return element.getSchemaTypeInfo();
|
||||
}
|
||||
|
||||
/** =
|
||||
* Получить имя тега.
|
||||
* @return имя тега.
|
||||
*/
|
||||
public String getTagName() {
|
||||
return element.getTagName();
|
||||
}
|
||||
|
||||
/** =
|
||||
* Есть ли аттрибут у элемента.
|
||||
* @param name название аттрибута.
|
||||
* @return true если есть, false иначе.
|
||||
*/
|
||||
public boolean hasAttribute(String name) {
|
||||
return element.hasAttribute(name);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Есть ли аттрибут данного пространства имен у элемента.
|
||||
* @param name название аттрибута.
|
||||
* @param namespace пространство имен.
|
||||
* @return true если есть, false иначе.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public boolean hasAttributeNS(String name, String namespace) throws DOMException {
|
||||
return element.hasAttributeNS(name, namespace);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Удалить аттрибут элемента.
|
||||
* @param name название атрибута элемента.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void removeAttribute(String name) throws DOMException {
|
||||
element.removeAttribute(name);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Удалить аттрибут элемента с заданным пространством имен.
|
||||
* @param name название аттрибута элемента.
|
||||
* @param namespace пространство имен.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void removeAttributeNS(String name, String namespace) throws DOMException {
|
||||
element.removeAttributeNS(name, namespace);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Удалить нод аттрибута.
|
||||
* @param attr удаляемый нод аттрибута.
|
||||
* @return удаляленный нод аттрибута.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public Attr removeAttributeNode(Attr attr) throws DOMException {
|
||||
return element.removeAttributeNode(attr);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить значение аттрибута.
|
||||
* @param name название аттрибута.
|
||||
* @param value значение аттрибута.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void setAttribute(String name, String value) throws DOMException {
|
||||
element.setAttribute(name, value);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить значение аттрибута с заданным пространством имен.
|
||||
* @param namespaceURI
|
||||
* @param qualifiedName
|
||||
* @param value
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void setAttributeNS(String namespaceURI, String qualifiedName, String value)
|
||||
throws DOMException {
|
||||
element.setAttributeNS(namespaceURI, qualifiedName, value);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить нод аттрибута элементу.
|
||||
* @param attr нод аттрибута.
|
||||
* @return установленный нод аттрибута.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public Attr setAttributeNode(Attr attr) throws DOMException {
|
||||
return element.setAttributeNode(attr);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить нод аттрибута элементу, заменив при этом старый нод если он существует.
|
||||
* @param attr нод аттрибута.
|
||||
* @return замененный нод аттрибута если он есть, null если его не было.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
|
||||
return element.setAttributeNodeNS(newAttr);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить id аттрибуту.
|
||||
* @param name название аттрибута.
|
||||
* @param isId см. Element.setIdAttribute documentation.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void setIdAttribute(String name, boolean isId) throws DOMException {
|
||||
element.setIdAttribute(name, isId);
|
||||
}
|
||||
|
||||
/** =
|
||||
* Установить id аттрибуту с заданным пространством имен.
|
||||
* @param namespaceURI пространство имен.
|
||||
* @param localName локальное имя аттрибута.
|
||||
* @param isId см. Element.setIdAttributeNode documentation.
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void setIdAttributeNS(String namespaceURI, String localName, boolean isId)
|
||||
throws DOMException {
|
||||
element.setIdAttributeNS(namespaceURI, localName, isId);
|
||||
}
|
||||
|
||||
/** =
|
||||
* См. Element.setIdAttributeNode documentation.
|
||||
* @param idAttr
|
||||
* @param isId
|
||||
* @throws DOMException
|
||||
*/
|
||||
public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
|
||||
element.setIdAttributeNode(idAttr, isId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ru.delkom07.util.xml;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
/**
|
||||
* Интерфейс представителя JXML.
|
||||
* Имплементирующий класс должен уметь представлять и воостанавливать данные в виде JXML дерева.
|
||||
* @author Komyshev.
|
||||
*/
|
||||
public interface JXMLRepresentable {
|
||||
/**
|
||||
* Получить представление в виде JXML дерева.
|
||||
* @param doc - документ DOM использующийся для создания JElement-ов.
|
||||
* @return - JElement дерево (корневой элемент).
|
||||
*/
|
||||
JElement getJXMLRepresentation(Document doc);
|
||||
|
||||
/**
|
||||
* Восстановить данные из JXML.
|
||||
* @param root - корень JXML дерева.
|
||||
*/
|
||||
void recoveryFromJXML(JElement root);
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package ru.delkom07.util.xml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.TransformerFactoryConfigurationError;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.ErrorHandler;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
/**
|
||||
* Обработчик файлов в формате xml.
|
||||
* Предоставляет доступ к builder-у и реализует простые операции с xml данными.
|
||||
* Library class: v.1.2(c) Changed
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class XMLDOMHandler {
|
||||
/*
|
||||
* Этот парсер является частным случаем парсера DocumentBuilder, но агрегирует его
|
||||
* так как DocumentBuilder производится фабрикой.
|
||||
* Методы DocumentBuilder делегируются. (Делегированы не все методы!)
|
||||
*/
|
||||
/*
|
||||
private static final String JAXP_SCHEMA_LANGUAGE =
|
||||
"http://java.sun.com/xml/jaxp/properties/schemaLanguage";
|
||||
private static final String W3C_XML_SCHEMA =
|
||||
"http://www.w3.org/2001/XMLSchema";
|
||||
*/
|
||||
|
||||
// Фабрика производит DocumentBuilder-ы, которые создают DOM дерево из xml файлов
|
||||
private static DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance() ;
|
||||
private static DocumentBuilder builder;
|
||||
|
||||
// ***Инициализация***
|
||||
static {
|
||||
factory.setNamespaceAware(true); // Производить парсеры, учитывающие пространства имен
|
||||
factory.setValidating(false); // Производить парсеры, не проверяющие структуру док-та
|
||||
|
||||
/*
|
||||
try{ // Установка аттрибутов фабрики
|
||||
factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
|
||||
}catch(IllegalArgumentException ex){
|
||||
ex.printStackTrace();
|
||||
throw new RuntimeException("Unknown feature: " + JAXP_SCHEMA_LANGUAGE);
|
||||
}*/
|
||||
|
||||
try { // Производим парсер DocumentBuilder
|
||||
builder = factory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Can't create XML parser.");
|
||||
}
|
||||
|
||||
// Устанавливаем обработчик ошибок
|
||||
builder.setErrorHandler(new ErrHandler());
|
||||
}
|
||||
|
||||
|
||||
// ***Делегированные методы*** (Делегированы не все методы!)
|
||||
/**
|
||||
* Распарсить файл и вернуть DOM документ.
|
||||
* @param file - xml файл.
|
||||
* @return - документ DOM.
|
||||
* @throws SAXException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Document parse(File file) throws SAXException, IOException{
|
||||
return builder.parse(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Распарсить входной поток и вернуть DOM документ.
|
||||
* @param is - входной поток представляющий xml данные.
|
||||
* @return - документ DOM.
|
||||
* @throws SAXException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Document parse(InputStream is) throws SAXException, IOException {
|
||||
return builder.parse(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать новый DOM документ.
|
||||
* @return - новый DOM документ.
|
||||
*/
|
||||
public static Document newDocument(){
|
||||
return builder.newDocument();
|
||||
}
|
||||
|
||||
|
||||
// ***Дополнительные методы***
|
||||
/**
|
||||
* Возвращает первый child-элемент. Если такого child-элемента не существует - возвращает null.
|
||||
* @param element - родительский элемент.
|
||||
* @return - child-элемент или null.
|
||||
*/
|
||||
public static Element getFirstChildElement(Element element) {
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
if(Node.ELEMENT_NODE==node.getNodeType()){
|
||||
return (Element) node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает первый child-элемент с заданным именем. Если такого child-элемента не существует,
|
||||
* - возвращает null.
|
||||
* @param element - родительский элемент.
|
||||
* @param name - имя child-элемента.
|
||||
* @return - child-элемент или null.
|
||||
*/
|
||||
public static Element getFirstChildElementByTagName(Element element, String name) {
|
||||
NodeList nodeList = element.getChildNodes();
|
||||
|
||||
for(int i=0; i<nodeList.getLength(); i++) {
|
||||
Node node = nodeList.item(i);
|
||||
if(Node.ELEMENT_NODE==node.getNodeType()){
|
||||
if(node.getNodeName().equals(name))
|
||||
return (Element) node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Записать DOM дерево в файл.
|
||||
* @param document - Document DOM дерева.
|
||||
* @param toFile - файл, в который записывается DOM дерево.
|
||||
*/
|
||||
public static void writeTree(Document document, File toFile){
|
||||
Transformer transformer = null;
|
||||
try {
|
||||
transformer = TransformerFactory.newInstance().newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
FileOutputStream fos = new FileOutputStream(toFile);
|
||||
transformer.transform(new DOMSource(document), new StreamResult(fos));
|
||||
fos.close();
|
||||
} catch (TransformerConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("TransformerConfigurationException is occured.");
|
||||
} catch (TransformerFactoryConfigurationError e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("TransformerFactoryConfigurationError is occured.");
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("FileNotFoundException is occured.");
|
||||
} catch (TransformerException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("TransformerException is occured.");
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Записать DOM дерево в поток.
|
||||
* @param doc - Document DOM дерева.
|
||||
* @param outputStream - поток, в который записывается DOM дерево.
|
||||
*/
|
||||
public static void writeTree(Document doc, OutputStream outputStream){
|
||||
Transformer transformer = null;
|
||||
try {
|
||||
transformer = TransformerFactory.newInstance().newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.transform(new DOMSource(doc), new StreamResult(outputStream));
|
||||
} catch (TransformerConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("TransformerConfigurationException is occured.");
|
||||
} catch (TransformerFactoryConfigurationError e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("TransformerFactoryConfigurationError is occured.");
|
||||
} catch (TransformerException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("TransformerException is occured.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ***Getters and Setters***
|
||||
/**
|
||||
* Получить DOM builder сгенерированный фабрикой.
|
||||
* @return - DOM builder сгенерированный фабрикой.
|
||||
*/
|
||||
public static DocumentBuilder getDocumentBuilder() {
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static String getAttributeNN(Element element, String name) {
|
||||
String result;
|
||||
return null==(result=element.getAttribute(name)) ? "":result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Обработчик ошибок парсера DocumentBuilder.
|
||||
* @author Komyshev
|
||||
*/
|
||||
class ErrHandler implements ErrorHandler{
|
||||
|
||||
public void warning(SAXParseException exception) throws SAXException {
|
||||
System.err.println("Warning: " + exception);
|
||||
System.err.println("line = " + exception.getLineNumber() +
|
||||
" col = " + exception.getColumnNumber());
|
||||
}
|
||||
|
||||
public void error(SAXParseException exception) throws SAXException {
|
||||
System.err.println("Error: " + exception);
|
||||
System.err.println("line = " + exception.getLineNumber() +
|
||||
" col = " + exception.getColumnNumber());
|
||||
}
|
||||
|
||||
public void fatalError(SAXParseException exception) throws SAXException {
|
||||
System.err.println("Fatal error: " + exception);
|
||||
System.err.println("line = " + exception.getLineNumber() +
|
||||
" col = " + exception.getColumnNumber());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
package smirnov.colorchecker;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.linear.SingularMatrixException;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.geometry.SpecificGeometry;
|
||||
import ru.delkom07.util.Pair;
|
||||
import smirnov.colormetric.CellColors;
|
||||
import smirnov.colormetric.Color;
|
||||
import smirnov.colormetric.EuclideanLab;
|
||||
import smirnov.colormetric.EuclideanRGB;
|
||||
import smirnov.regression.ColorSpace;
|
||||
import smirnov.regression.RegressionModel;
|
||||
|
||||
public class ColorChecker {
|
||||
private final static List<List<Scalar>> BGR_REFERENCE_COLORS = Arrays.asList(
|
||||
Arrays.asList(new Scalar(171, 191, 99), new Scalar(41, 161, 229), new Scalar(166, 136, 0), new Scalar(50, 50, 50)),
|
||||
Arrays.asList(new Scalar(176, 129, 130), new Scalar(62, 189, 160), new Scalar(150, 84, 188), new Scalar(85, 84, 83)),
|
||||
Arrays.asList(new Scalar(65, 108, 90), new Scalar(105, 59, 91), new Scalar(22, 200, 238), new Scalar(121, 121, 120)),
|
||||
Arrays.asList(new Scalar(157, 123, 93), new Scalar(98, 84, 195), new Scalar(56, 48, 176), new Scalar(161, 161, 160)),
|
||||
Arrays.asList(new Scalar(129, 149, 196), new Scalar(168, 92, 72), new Scalar(72, 149, 71), new Scalar(201, 201, 200)),
|
||||
Arrays.asList(new Scalar(67, 81, 115), new Scalar(45, 123, 220), new Scalar(147, 62, 43), new Scalar(240, 245, 245))
|
||||
);
|
||||
|
||||
private final Mat checkerImage;
|
||||
private final List<List<Point>> centers;
|
||||
private final Integer xScale;
|
||||
private final Integer xColorPatchSize;
|
||||
private final Integer yScale;
|
||||
private final Integer yColorPatchSize;
|
||||
|
||||
private static final Double REAL_WIDTH = 64.0; // millimeters
|
||||
private static final Double REAL_HEIGHT = 108.0; // millimeters
|
||||
private static final List<Integer> TOP_INDEXES = Arrays.asList(0, 1, 2);
|
||||
private static final List<Integer> BOTTOM_INDEXES = Arrays.asList(6, 7, 8);
|
||||
private static final List<Integer> LEFT_INDEXES = Arrays.asList(0, 3, 6);
|
||||
private static final List<Integer> RIGHT_INDEXES = Arrays.asList(2, 5, 8);
|
||||
|
||||
Quad quad = null; // edited
|
||||
|
||||
public ColorChecker(Mat image, Quad quad) { // edited
|
||||
this(image, quad, false, true);
|
||||
//this.quad = quad; // edited
|
||||
}
|
||||
|
||||
/*
|
||||
* withCorrectionByReference - try to correct recognized points on ColorChecker palette
|
||||
* by comparing the colors with the reference
|
||||
* withCorrectionByDeviation - try to correct recognized points by shifting in opposite direction
|
||||
* from the points having the most deviation from the center
|
||||
*/
|
||||
public ColorChecker(Mat image, Quad quad, boolean withCorrectionByReference, boolean withCorrectionByDeviation) {
|
||||
this.quad = quad; // edited
|
||||
|
||||
checkerImage = image;
|
||||
Integer width = image.width();
|
||||
Integer height = image.height();
|
||||
|
||||
xScale = (int) (0.04 * width);
|
||||
xColorPatchSize = xScale / 8;
|
||||
yScale = (int) (0.02 * height);
|
||||
yColorPatchSize = yScale / 8;
|
||||
|
||||
List<Double> xCenters = Arrays.asList(0.143, 0.381, 0.613, 0.862);
|
||||
List<Double> yCenters = Arrays.asList(0.160, 0.305, 0.440, 0.580, 0.717, 0.856);
|
||||
|
||||
centers = new ArrayList<>();
|
||||
for (int row = 0; row < rowCount(); ++row) {
|
||||
List<Point> points = new ArrayList<>();
|
||||
for (int col = 0; col < colCount(); ++col) {
|
||||
Point point = new Point(xCenters.get(col) * width, yCenters.get(row) * height);
|
||||
if (withCorrectionByReference) {
|
||||
point = correctByReference(point, row, col);
|
||||
}
|
||||
if (withCorrectionByDeviation) {
|
||||
point = correctByDeviation(point);
|
||||
}
|
||||
points.add(point);
|
||||
}
|
||||
centers.add(points);
|
||||
}
|
||||
}
|
||||
|
||||
public Double pixelArea() { // Edited
|
||||
return REAL_WIDTH * REAL_HEIGHT / quad.getArea();
|
||||
}
|
||||
|
||||
public Pair<Mat, Double> calibrate(Mat srcImage, RegressionModel model,
|
||||
ColorSpace featuresSpace, ColorSpace targetSpace) throws IllegalStateException {
|
||||
Mat result = srcImage.clone();
|
||||
result.convertTo(result, CvType.CV_64FC3);
|
||||
|
||||
int channels = result.channels();
|
||||
int size = (int) result.total() * channels;
|
||||
double[] temp = new double[size];
|
||||
result.get(0, 0, temp);
|
||||
|
||||
for (int i = 0; i + channels < size; i += channels) {
|
||||
featuresSpace.convertFromBGR(DoubleBuffer.wrap(temp, i, channels), true);
|
||||
}
|
||||
|
||||
List<DoubleBuffer> train = new ArrayList<>();
|
||||
List<DoubleBuffer> answers = new ArrayList<>();
|
||||
|
||||
calculateTrainAndAnswers(featuresSpace, targetSpace, train, answers);
|
||||
|
||||
Double transformationDeviance = 0.0; // Edited
|
||||
try {
|
||||
model.train(train, answers);
|
||||
transformationDeviance = model.getTransformationDeviance(train, answers); // Edited
|
||||
} catch (SingularMatrixException e) {
|
||||
result.release();
|
||||
throw new IllegalStateException("Couldn't calibrate colors given this reference");
|
||||
}
|
||||
|
||||
for (int i = 0; i + channels < size; i += channels) {
|
||||
DoubleBuffer srcColor = DoubleBuffer.wrap(temp, i, channels);
|
||||
model.calibrate(srcColor);
|
||||
}
|
||||
|
||||
for (int i = 0; i + channels < size; i += channels) {
|
||||
targetSpace.convertToBGR(DoubleBuffer.wrap(temp, i, channels));
|
||||
}
|
||||
|
||||
result.put(0, 0, temp);
|
||||
result.convertTo(result, srcImage.type());
|
||||
|
||||
return new Pair<Mat, Double>(result, transformationDeviance); // Edited
|
||||
}
|
||||
|
||||
// a wrapper for the getTransformationDeviation method in AbstractOLS class
|
||||
public double getTransformationDeviation(RegressionModel model, ColorSpace featuresSpace) throws IllegalStateException {
|
||||
List<DoubleBuffer> train = new ArrayList<>();
|
||||
List<DoubleBuffer> answers = new ArrayList<>();
|
||||
|
||||
calculateTrainAndAnswers(featuresSpace, featuresSpace, train, answers);
|
||||
|
||||
double deviation;
|
||||
|
||||
try {
|
||||
deviation = model.getTransformationDeviance(train, answers);
|
||||
} catch (SingularMatrixException e) {
|
||||
throw new IllegalStateException("Couldn't calculate the transformation matrix given this reference");
|
||||
}
|
||||
|
||||
return deviation;
|
||||
}
|
||||
|
||||
private void calculateTrainAndAnswers(ColorSpace featuresSpace, ColorSpace targetSpace,
|
||||
List<DoubleBuffer> train, List<DoubleBuffer> answers) {
|
||||
for (Integer row = 0; row < rowCount(); ++row) {
|
||||
for (Integer col = 0; col < colCount(); ++col) {
|
||||
List<DoubleBuffer> samplePoints = getSamplePoints(row, col);
|
||||
for (DoubleBuffer s : samplePoints) {
|
||||
train.add(featuresSpace.convertFromBGR(s, false));
|
||||
DoubleBuffer referenceColor = DoubleBuffer.wrap(BGR_REFERENCE_COLORS.get(row).get(col).val);
|
||||
answers.add(targetSpace.convertFromBGR(referenceColor, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double labDeviationFromReference() {
|
||||
return getCellColors(checkerImage, false).calculateMetric(new EuclideanLab());
|
||||
}
|
||||
|
||||
public CellColors getCellColors(Mat checkerImage) {
|
||||
return getCellColors(checkerImage, true);
|
||||
}
|
||||
|
||||
public CellColors getCellColors(Mat checkerImage, boolean allPoints) {
|
||||
CellColors cellColors = new CellColors();
|
||||
|
||||
for (Integer row = 0; row < rowCount(); ++row) {
|
||||
for (Integer col = 0; col < colCount(); ++col) {
|
||||
List<DoubleBuffer> actualColors = getSamplePoints(checkerImage, row, col, allPoints);
|
||||
DoubleBuffer referenceColor = DoubleBuffer.wrap(BGR_REFERENCE_COLORS.get(row).get(col).val);
|
||||
for (DoubleBuffer color : actualColors) {
|
||||
cellColors.addColor(new Color(color), new Color(referenceColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cellColors;
|
||||
}
|
||||
|
||||
private List<DoubleBuffer> getSamplePoints(Integer row, Integer col) {
|
||||
return getSamplePoints(checkerImage, row, col, false);
|
||||
}
|
||||
|
||||
private List<DoubleBuffer> getSamplePoints(Mat checkerImage, Integer row, Integer col, boolean allPoints) {
|
||||
final int STEP = 10;
|
||||
final int CHANNELS = 3;
|
||||
|
||||
Point center = centers.get(row).get(col);
|
||||
List<Point> surroundingPoints = getSurroundingPoints(center);
|
||||
|
||||
List<DoubleBuffer> points = new ArrayList<>();
|
||||
double[] result;
|
||||
|
||||
if (allPoints) {
|
||||
int minX = (int) surroundingPoints.get(0).x;
|
||||
int minY = (int) surroundingPoints.get(0).y;
|
||||
int maxX = (int) surroundingPoints.get(8).x;
|
||||
int maxY = (int) surroundingPoints.get(8).y;
|
||||
int xSize = (maxX - minX) / STEP + 1;
|
||||
int ySize = (maxY - minY) / STEP + 1;
|
||||
result = new double[xSize * ySize * CHANNELS];
|
||||
int index = 0;
|
||||
|
||||
for (int y = minY; y <= maxY; y += STEP) {
|
||||
for (int x = minX; x <= maxX; x += STEP) {
|
||||
double[] color = checkerImage.get(y, x);
|
||||
System.arraycopy(color, 0, result, index, CHANNELS);
|
||||
points.add(DoubleBuffer.wrap(result, index, CHANNELS));
|
||||
index += CHANNELS;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = new double[surroundingPoints.size() * CHANNELS];
|
||||
for (int i = 0; i < surroundingPoints.size(); ++i) {
|
||||
Point p = surroundingPoints.get(i);
|
||||
double[] color = checkerImage.get((int) p.y, (int) p.x);
|
||||
System.arraycopy(color, 0, result, i * 3, CHANNELS);
|
||||
points.add(DoubleBuffer.wrap(result, i*CHANNELS, CHANNELS));
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
public Mat drawSamplePoints() {
|
||||
Mat result = checkerImage.clone();
|
||||
Scalar red = new Scalar(0, 0, 255);
|
||||
Scalar blue = new Scalar(255, 0, 0);
|
||||
|
||||
for (Integer row = 0; row < rowCount(); ++row) {
|
||||
for (Integer col = 0; col < colCount(); ++col) {
|
||||
Point center = centers.get(row).get(col);
|
||||
List<Point> points = getSurroundingPoints(center);
|
||||
int i = 0;
|
||||
for (Point p : points) {
|
||||
if (i % 2 == 0) {
|
||||
Imgproc.circle(result, p, 10, red, Core.FILLED);
|
||||
} else {
|
||||
Imgproc.circle(result, p, 10, blue, Core.FILLED);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Point correctByReference(Point center, int row, int col) {
|
||||
final int ITERATIONS = 3;
|
||||
final double STEP_CHANGE = 1.2;
|
||||
final double THRESHOLD = (row <= 1 && col == 3 ? 2.0 : 1.1);
|
||||
final double VARIANCE_THRESHOLD = 100.0;
|
||||
final double DISTANCE_COEFFICIENT = 1.5;
|
||||
|
||||
return correctByReference(center, row, col, ITERATIONS, STEP_CHANGE, THRESHOLD,
|
||||
VARIANCE_THRESHOLD, DISTANCE_COEFFICIENT);
|
||||
}
|
||||
|
||||
private Point correctByReference(Point center, int row, int col, int iterations, double stepChange,
|
||||
double threshold, double varianceThreshold, double distanceCoefficient) {
|
||||
final double INFINITY = 1e9;
|
||||
|
||||
Color referenceColor = new Color(DoubleBuffer.wrap(BGR_REFERENCE_COLORS.get(row).get(col).val));
|
||||
EuclideanLab metric = new EuclideanLab();
|
||||
double xStep = xScale;
|
||||
double yStep = yScale;
|
||||
|
||||
for (int iteration = 0; iteration < iterations; ++iteration) {
|
||||
List<Point> points = getSurroundingPoints(center);
|
||||
int nearestPoint = -1;
|
||||
double nearestDistance = INFINITY;
|
||||
|
||||
for (int i = 0; i < points.size(); ++i) {
|
||||
Point point = points.get(i);
|
||||
int x = (int) (center.x + (point.x - center.x) * distanceCoefficient);
|
||||
int y = (int) (center.y + (point.y - center.y) * distanceCoefficient);
|
||||
if (getValueVariance(x, y) < varianceThreshold) {
|
||||
Color color = getMeanColor(x, y);
|
||||
double distance = metric.calculate(color, referenceColor);
|
||||
if (distance < nearestDistance) {
|
||||
nearestPoint = i;
|
||||
nearestDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestPoint == -1) {
|
||||
return center;
|
||||
}
|
||||
Point point = points.get(8 - nearestPoint);
|
||||
int x = (int) (center.x + (point.x - center.x));
|
||||
int y = (int) (center.y + (point.y - center.y));
|
||||
Color color = getMeanColor(x, y);
|
||||
double oppositeDistance = metric.calculate(color, referenceColor);
|
||||
|
||||
if (oppositeDistance < nearestDistance * threshold) {
|
||||
return center;
|
||||
}
|
||||
|
||||
if (RIGHT_INDEXES.contains(nearestPoint)) {
|
||||
if (checkCorrectness(points, xStep, 0.0)) {
|
||||
center.x += xStep;
|
||||
}
|
||||
xStep /= stepChange;
|
||||
} else if (LEFT_INDEXES.contains(nearestPoint)) {
|
||||
if (checkCorrectness(points, -xStep, 0.0)) {
|
||||
center.x -= xStep;
|
||||
}
|
||||
xStep /= stepChange;
|
||||
}
|
||||
|
||||
if (BOTTOM_INDEXES.contains(nearestPoint)) {
|
||||
if (checkCorrectness(points, 0.0, yStep)) {
|
||||
center.y += yStep;
|
||||
}
|
||||
yStep /= stepChange;
|
||||
} else if (TOP_INDEXES.contains(nearestPoint)) {
|
||||
if (checkCorrectness(points, 0.0, -yStep)) {
|
||||
center.y -= yStep;
|
||||
}
|
||||
yStep /= stepChange;
|
||||
}
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
private Color getMeanColor(int x, int y) {
|
||||
double[] result = {0.0, 0.0, 0.0};
|
||||
|
||||
for (int row = -1; row < 2; ++row) {
|
||||
for (int col = -1; col < 2; ++col) {
|
||||
double[] color = getColor(x + row * xColorPatchSize, y + col * yColorPatchSize);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
result[i] += color[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
result[i] /= 9;
|
||||
}
|
||||
|
||||
return new Color(DoubleBuffer.wrap(result));
|
||||
}
|
||||
|
||||
private double getValueVariance(int x, int y) {
|
||||
double firstMoment = 0.0;
|
||||
double secondMoment = 0.0;
|
||||
|
||||
for (int row = -1; row < 2; ++row) {
|
||||
for (int col = -1; col < 2; ++col) {
|
||||
double[] color = getColor(x + row * xColorPatchSize, y + col * yColorPatchSize);
|
||||
double value = Math.max(color[0], Math.max(color[1], color[2]));
|
||||
firstMoment += value;
|
||||
secondMoment += Math.pow(value, 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
firstMoment /= 9.0;
|
||||
secondMoment /= 9.0;
|
||||
|
||||
return secondMoment - Math.pow(firstMoment, 2.0);
|
||||
}
|
||||
|
||||
private double[] getColor(int x, int y) {
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
} else if (x >= checkerImage.cols()) {
|
||||
x = checkerImage.cols() - 1;
|
||||
}
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
} else if (y >= checkerImage.rows()) {
|
||||
y = checkerImage.rows() - 1;
|
||||
}
|
||||
|
||||
return checkerImage.get(y, x);
|
||||
}
|
||||
|
||||
private Point correctByDeviation(Point center) {
|
||||
final int ITERATIONS = 10;
|
||||
final double THRESHOLD = 1.5;
|
||||
final double STEP_CHANGE = 1.5;
|
||||
|
||||
return correctByDeviation(center, ITERATIONS, THRESHOLD, STEP_CHANGE);
|
||||
}
|
||||
|
||||
private Point correctByDeviation(Point center, int iterations, double threshold, double stepChange) {
|
||||
double xStep = xScale;
|
||||
double yStep = yScale;
|
||||
|
||||
for (int iteration = 0; iteration < iterations; ++iteration) {
|
||||
List<Point> points = getSurroundingPoints(center);
|
||||
double top = deviationSum(points, center, TOP_INDEXES);
|
||||
double bottom = deviationSum(points, center, BOTTOM_INDEXES);
|
||||
double left = deviationSum(points, center, LEFT_INDEXES);
|
||||
double right = deviationSum(points, center, RIGHT_INDEXES);
|
||||
Point newCenter = center.clone();
|
||||
|
||||
if (left >= right * threshold) {
|
||||
if (checkCorrectness(points, xStep, 0.0)) {
|
||||
double newLeft = deviationSum(points, center, LEFT_INDEXES, xStep, 0.0);
|
||||
double newRight = deviationSum(points, center, RIGHT_INDEXES, xStep, 0.0);
|
||||
if (newLeft + newRight < left + right) {
|
||||
newCenter.x += xStep;
|
||||
}
|
||||
}
|
||||
xStep /= stepChange;
|
||||
} else if (right >= left * threshold) {
|
||||
if (checkCorrectness(points, -xStep, 0.0)) {
|
||||
double newLeft = deviationSum(points, center, LEFT_INDEXES, -xStep, 0.0);
|
||||
double newRight = deviationSum(points, center, RIGHT_INDEXES, -xStep, 0.0);
|
||||
if (newLeft + newRight < left + right) {
|
||||
newCenter.x -= xStep;
|
||||
}
|
||||
}
|
||||
xStep /= stepChange;
|
||||
}
|
||||
|
||||
if (top >= bottom * threshold) {
|
||||
if (checkCorrectness(points, 0.0, yStep)) {
|
||||
double newTop = deviationSum(points, center, TOP_INDEXES, 0.0, yStep);
|
||||
double newBottom = deviationSum(points, center, BOTTOM_INDEXES, 0.0, yStep);
|
||||
if (newTop + newBottom < top + bottom) {
|
||||
newCenter.y += yStep;
|
||||
}
|
||||
}
|
||||
yStep /= stepChange;
|
||||
} else if (bottom >= top * threshold) {
|
||||
if (checkCorrectness(points, 0.0, -yStep)) {
|
||||
double newTop = deviationSum(points, center, TOP_INDEXES, 0.0, -yStep);
|
||||
double newBottom = deviationSum(points, center, BOTTOM_INDEXES, 0.0, -yStep);
|
||||
if (newTop + newBottom < top + bottom) {
|
||||
newCenter.y -= yStep;
|
||||
}
|
||||
}
|
||||
yStep /= stepChange;
|
||||
}
|
||||
|
||||
center = newCenter;
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
private double deviationSum(List<Point> points, Point center, List<Integer> indexes) {
|
||||
return deviationSum(points, center, indexes, 0.0, 0.0);
|
||||
}
|
||||
|
||||
private double deviationSum(List<Point> points, Point center, List<Integer> indexes, double xStep, double yStep) {
|
||||
Color centerColor = new Color(DoubleBuffer.wrap(checkerImage.get((int) (center.y + yStep),
|
||||
(int) (center.x + xStep))));
|
||||
EuclideanRGB metric = new EuclideanRGB();
|
||||
|
||||
return indexes.stream().map(x -> {
|
||||
Point point = points.get(x);
|
||||
Color color = new Color(DoubleBuffer.wrap(
|
||||
checkerImage.get((int) (point.y + yStep), (int) (point.x + xStep))));
|
||||
return metric.calculate(centerColor, color);
|
||||
}).reduce(0.0, (x,y) -> x + y);
|
||||
}
|
||||
|
||||
private boolean checkCorrectness(List<Point> points, double xStep, double yStep) {
|
||||
for (Point p : points) {
|
||||
if (p.x + xStep < 0 || p.x + xStep >= checkerImage.cols()
|
||||
|| p.y + yStep < 0 || p.y + yStep >= checkerImage.rows()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<Point> getSurroundingPoints(Point center) {
|
||||
return Arrays.asList(
|
||||
new Point(center.x - xScale, center.y - yScale),
|
||||
new Point(center.x, center.y - yScale),
|
||||
new Point(center.x + xScale, center.y - yScale),
|
||||
new Point(center.x - xScale, center.y),
|
||||
new Point(center.x, center.y),
|
||||
new Point(center.x + xScale, center.y),
|
||||
new Point(center.x - xScale, center.y + yScale),
|
||||
new Point(center.x, center.y + yScale),
|
||||
new Point(center.x + xScale, center.y + yScale)
|
||||
);
|
||||
}
|
||||
|
||||
private int rowCount() {
|
||||
return BGR_REFERENCE_COLORS.size();
|
||||
}
|
||||
|
||||
private int colCount() {
|
||||
return BGR_REFERENCE_COLORS.get(0).size();
|
||||
}
|
||||
|
||||
|
||||
// Edited:
|
||||
public void colorPrint(Mat img) {
|
||||
float persent = 0.3f;
|
||||
|
||||
MatOfPoint matOfPoint = new MatOfPoint(quad.tl(), quad.tr(), quad.br(), quad.bl());
|
||||
|
||||
Point[] tlbr = SpecificGeometry.getTopLeftAndBottomRight(matOfPoint.toList());
|
||||
Point tl = tlbr[0];
|
||||
Point br = tlbr[1];
|
||||
Mat binMark = Mat.zeros((int)(br.y - tl.y), (int)(br.x - tl.x), CvType.CV_8SC1);
|
||||
|
||||
MatOfPoint matOfPoint2 = new MatOfPoint(
|
||||
new Point(quad.tl().x - tl.x, quad.tl().y - tl.y),
|
||||
new Point(quad.tr().x - tl.x, quad.tr().y - tl.y),
|
||||
new Point(quad.br().x - tl.x, quad.br().y - tl.y),
|
||||
new Point(quad.bl().x - tl.x, quad.bl().y - tl.y)
|
||||
);
|
||||
Imgproc.fillConvexPoly(binMark, matOfPoint2, new Scalar(255));
|
||||
|
||||
for(int i=(int)tl.y; i<br.y; i++) {
|
||||
for(int j=(int)tl.x; j<br.x; j++) {
|
||||
if(binMark.get((int)(i-tl.y), (int)(j-tl.x))[0] != 0) {
|
||||
double[] rgb = img.get(i, j);
|
||||
|
||||
img.put(i, j, new double[] {rgb[0], rgb[1]/persent, rgb[2]});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edited:
|
||||
public Quad getQuad() {
|
||||
return quad;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package smirnov.colorchecker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.opencv.calib3d.Calib3d;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.DMatch;
|
||||
import org.opencv.core.KeyPoint;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfByte;
|
||||
import org.opencv.core.MatOfDMatch;
|
||||
import org.opencv.core.MatOfKeyPoint;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Range;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.features2d.DescriptorMatcher;
|
||||
import org.opencv.features2d.Feature2D;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
|
||||
|
||||
public class FindColorChecker {
|
||||
private final Mat referenceImage;
|
||||
private final MatchingModel matchingModel;
|
||||
private final MatOfKeyPoint referenceKeypoints;
|
||||
private final MatOfKeyPoint referenceDescriptors;
|
||||
private final Feature2D detector;
|
||||
private final Feature2D extractor;
|
||||
|
||||
public FindColorChecker(URL referenceFile, MatchingModel matchingModel) throws IOException {
|
||||
//referenceImage = Imgcodecs.imread(referenceFile,
|
||||
// Imgcodecs.CV_LOAD_IMAGE_ANYCOLOR | Imgcodecs.CV_LOAD_IMAGE_ANYDEPTH);
|
||||
|
||||
byte[] temporaryImageInMemory = referenceFile.openStream().readAllBytes();
|
||||
referenceImage = Imgcodecs.imdecode(new MatOfByte(temporaryImageInMemory),
|
||||
Imgcodecs.IMREAD_ANYCOLOR | Imgcodecs.IMREAD_ANYDEPTH);
|
||||
|
||||
|
||||
|
||||
this.matchingModel = matchingModel;
|
||||
|
||||
referenceKeypoints = new MatOfKeyPoint();
|
||||
detector = this.matchingModel.getDetector();
|
||||
detector.detect(referenceImage, referenceKeypoints);
|
||||
|
||||
referenceDescriptors = new MatOfKeyPoint();
|
||||
extractor = this.matchingModel.getExtractor();
|
||||
extractor.compute(referenceImage, referenceKeypoints, referenceDescriptors);
|
||||
}
|
||||
|
||||
public Quad findBestFitColorChecker(Mat image) {
|
||||
Quad bestQuad = findColorChecker(image);
|
||||
Mat extractedColorChecker = bestQuad.getTransformedField(image);
|
||||
ColorChecker colorChecker = new ColorChecker(extractedColorChecker, bestQuad, false, false);
|
||||
double bestMetric = colorChecker.labDeviationFromReference();
|
||||
extractedColorChecker.release();
|
||||
|
||||
for (double scale : Arrays.asList(0.05, 0.1, 0.2)) {
|
||||
Quad quad = findColorChecker(image, scale);
|
||||
extractedColorChecker = quad.getTransformedField(image);
|
||||
colorChecker = new ColorChecker(extractedColorChecker, quad, false, false);
|
||||
double metric = colorChecker.labDeviationFromReference();
|
||||
extractedColorChecker.release();
|
||||
if (metric < bestMetric) {
|
||||
bestMetric = metric;
|
||||
bestQuad = quad;
|
||||
}
|
||||
}
|
||||
|
||||
return bestQuad;
|
||||
}
|
||||
|
||||
public Quad findColorChecker(Mat image) {
|
||||
return getQuad(getHomography(image), 0.0).orElse(fullImageQuad(image));
|
||||
}
|
||||
|
||||
public Quad findColorChecker(Mat image, double scale) {
|
||||
Optional<Quad> quad1 = getQuad(getHomography(image), scale);
|
||||
if (!quad1.isPresent()) {
|
||||
return fullImageQuad(image);
|
||||
}
|
||||
|
||||
image = imageSplice(image, quad1.get());
|
||||
Optional<Quad> quad2 = getQuad(getHomography(image), 0.0);
|
||||
image.release();
|
||||
if (!quad2.isPresent()) {
|
||||
return fullImageQuad(image);
|
||||
}
|
||||
|
||||
return shiftQuad(quad2.get(), quad1.get());
|
||||
}
|
||||
|
||||
private Quad fullImageQuad(Mat image) {
|
||||
return new Quad(
|
||||
new Point(0.0, 0.0),
|
||||
new Point(image.cols() - 1, 0.0),
|
||||
new Point(image.cols() - 1, image.rows() - 1),
|
||||
new Point(0.0, image.rows() - 1)
|
||||
);
|
||||
}
|
||||
|
||||
private Optional<Mat> getHomography(Mat image) {
|
||||
MatOfKeyPoint keypoints = new MatOfKeyPoint();
|
||||
if (image.rows() < 100 || image.cols() < 50) {
|
||||
return Optional.empty();
|
||||
}
|
||||
detector.detect(image, keypoints);
|
||||
if ((int) keypoints.size().width * (int) keypoints.size().height < 2) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
MatOfKeyPoint descriptors = new MatOfKeyPoint();
|
||||
extractor.compute(image, keypoints, descriptors);
|
||||
|
||||
LinkedList<DMatch> goodMatches = getGoodMatches(descriptors);
|
||||
|
||||
if (goodMatches.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return getHomography(keypoints, goodMatches);
|
||||
}
|
||||
|
||||
private LinkedList<DMatch> getGoodMatches(MatOfKeyPoint descriptors) {
|
||||
List<MatOfDMatch> matches = new LinkedList<>();
|
||||
DescriptorMatcher descriptorMatcher = DescriptorMatcher.create(
|
||||
matchingModel.getMatcher());
|
||||
|
||||
synchronized (FindColorChecker.class) {
|
||||
descriptorMatcher.knnMatch(referenceDescriptors, descriptors, matches, 4);
|
||||
}
|
||||
|
||||
|
||||
LinkedList<DMatch> goodMatches = new LinkedList<>();
|
||||
|
||||
for (MatOfDMatch matofDMatch : matches) {
|
||||
DMatch[] dmatcharray = matofDMatch.toArray();
|
||||
DMatch m1 = dmatcharray[0];
|
||||
DMatch m2 = dmatcharray[1];
|
||||
|
||||
if (m1.distance <= m2.distance * matchingModel.getThreshold()) {
|
||||
goodMatches.addLast(m1);
|
||||
}
|
||||
}
|
||||
|
||||
return goodMatches;
|
||||
}
|
||||
|
||||
private Optional<Mat> getHomography(MatOfKeyPoint keypoints, LinkedList<DMatch> goodMatches) {
|
||||
List<KeyPoint> referenceKeypointlist = referenceKeypoints.toList();
|
||||
List<KeyPoint> keypointlist = keypoints.toList();
|
||||
|
||||
LinkedList<Point> referencePoints = new LinkedList<>();
|
||||
LinkedList<Point> points = new LinkedList<>();
|
||||
|
||||
for (DMatch goodMatch : goodMatches) {
|
||||
referencePoints.addLast(referenceKeypointlist.get(goodMatch.queryIdx).pt);
|
||||
points.addLast(keypointlist.get(goodMatch.trainIdx).pt);
|
||||
}
|
||||
|
||||
MatOfPoint2f referenceMatOfPoint2f = new MatOfPoint2f();
|
||||
referenceMatOfPoint2f.fromList(referencePoints);
|
||||
MatOfPoint2f matOfPoint2f = new MatOfPoint2f();
|
||||
matOfPoint2f.fromList(points);
|
||||
|
||||
return Optional.of(Calib3d.findHomography(
|
||||
referenceMatOfPoint2f, matOfPoint2f, Calib3d.RANSAC, 3))
|
||||
.filter(x -> x.cols() > 0 && x.rows() > 0);
|
||||
}
|
||||
|
||||
private Optional<Quad> getQuad(Optional<Mat> homography, double scale) {
|
||||
if (!homography.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Mat corners = new Mat(4, 1, CvType.CV_32FC2);
|
||||
Mat referenceCorners = new Mat(4, 1, CvType.CV_32FC2);
|
||||
|
||||
referenceCorners.put(0, 0, -scale * referenceImage.cols(), -scale * referenceImage.rows());
|
||||
referenceCorners.put(1, 0, (1.0 + scale) * referenceImage.cols(), -scale * referenceImage.rows());
|
||||
referenceCorners.put(2, 0, (1.0 + scale) * referenceImage.cols(), (1.0 + scale) * referenceImage.rows());
|
||||
referenceCorners.put(3, 0, -scale * referenceImage.cols(), (1.0 + scale) * referenceImage.rows());
|
||||
|
||||
Core.perspectiveTransform(referenceCorners, corners, homography.get());
|
||||
|
||||
return Optional.of(new Quad(new Point(corners.get(0, 0)),new Point(corners.get(1, 0)),
|
||||
new Point(corners.get(2, 0)), new Point(corners.get(3, 0))));
|
||||
}
|
||||
|
||||
public void fillColorChecker(Mat image, Quad quad) {
|
||||
MatOfPoint points = new MatOfPoint();
|
||||
|
||||
points.fromArray(quad.getPoints());
|
||||
Imgproc.fillConvexPoly(image, points, getBackgroundColor(image, quad));
|
||||
}
|
||||
|
||||
private Mat imageSplice(Mat image, Quad quad) {
|
||||
Range rows = new Range(clipRow(top(quad), image), clipRow(bottom(quad), image) + 1);
|
||||
Range cols = new Range(clipCol(left(quad), image), clipCol(right(quad), image) + 1);
|
||||
|
||||
return new Mat(image, rows, cols);
|
||||
}
|
||||
|
||||
private int clipRow(int row, Mat image) {
|
||||
return Math.max(0, Math.min(image.rows() - 1, row));
|
||||
}
|
||||
|
||||
private int clipCol(int col, Mat image) {
|
||||
return Math.max(0, Math.min(image.cols() - 1, col));
|
||||
}
|
||||
|
||||
private Quad shiftQuad(Quad quad, Quad shift) {
|
||||
return new Quad(
|
||||
shiftPoint(quad.tl(), shift),
|
||||
shiftPoint(quad.tr(), shift),
|
||||
shiftPoint(quad.br(), shift),
|
||||
shiftPoint(quad.bl(), shift)
|
||||
);
|
||||
}
|
||||
|
||||
private Point shiftPoint(Point point, Quad shift) {
|
||||
return new Point(point.x + left(shift), point.y + top(shift));
|
||||
}
|
||||
|
||||
private int left(Quad quad) {
|
||||
return (int) Math.min(
|
||||
Math.min(quad.tl().x, quad.tr().x),
|
||||
Math.min(quad.bl().x, quad.br().x)
|
||||
);
|
||||
}
|
||||
|
||||
private int right(Quad quad) {
|
||||
return (int) Math.max(
|
||||
Math.max(quad.tl().x, quad.tr().x),
|
||||
Math.max(quad.bl().x, quad.br().x)
|
||||
);
|
||||
}
|
||||
|
||||
private int top(Quad quad) {
|
||||
return (int) Math.min(
|
||||
Math.min(quad.tl().y, quad.tr().y),
|
||||
Math.min(quad.bl().y, quad.br().y)
|
||||
);
|
||||
}
|
||||
|
||||
private int bottom(Quad quad) {
|
||||
return (int) Math.max(
|
||||
Math.max(quad.tl().y, quad.tr().y),
|
||||
Math.max(quad.bl().y, quad.br().y)
|
||||
);
|
||||
}
|
||||
|
||||
private Scalar getBackgroundColor(Mat image, Quad quad) {
|
||||
List<double[]> colors = new ArrayList<>();
|
||||
double sumBlue = 0.0;
|
||||
double sumGreen = 0.0;
|
||||
double sumRed = 0.0;
|
||||
|
||||
while (colors.size() < 1000) {
|
||||
double x = ThreadLocalRandom.current().nextDouble(0.0, image.cols());
|
||||
double y = ThreadLocalRandom.current().nextDouble(0.0, image.rows());
|
||||
|
||||
if (image.get((int) x, (int) y) != null && !quad.isInside(new Point(x, y))) {
|
||||
colors.add(image.get((int) x, (int) y));
|
||||
sumBlue += colors.get(colors.size() - 1)[0];
|
||||
sumGreen += colors.get(colors.size() - 1)[1];
|
||||
sumRed += colors.get(colors.size() - 1)[2];
|
||||
}
|
||||
}
|
||||
double meanBlue = sumBlue / colors.size();
|
||||
double meanGreen = sumGreen / colors.size();
|
||||
double meanRed = sumRed / colors.size();
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
List<double[]> buffer = new ArrayList<>();
|
||||
sumBlue = 0.0;
|
||||
sumGreen = 0.0;
|
||||
sumRed = 0.0;
|
||||
for (double[] c : colors) {
|
||||
if (Math.abs(c[0] - meanBlue) <= 25 &&
|
||||
Math.abs(c[1] - meanGreen) <= 25 &&
|
||||
Math.abs(c[2] - meanRed) <= 25) {
|
||||
sumBlue += c[0];
|
||||
sumGreen += c[1];
|
||||
sumRed += c[2];
|
||||
buffer.add(c);
|
||||
}
|
||||
}
|
||||
colors = buffer;
|
||||
meanBlue = sumBlue / colors.size();
|
||||
meanGreen = sumGreen / colors.size();
|
||||
meanRed = sumRed / colors.size();
|
||||
}
|
||||
|
||||
return new Scalar(meanBlue, meanGreen, meanRed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package smirnov.colorchecker;
|
||||
|
||||
import org.opencv.features2d.Feature2D;
|
||||
|
||||
public class MatchingModel {
|
||||
private final Feature2D detector;
|
||||
private final Feature2D extractor;
|
||||
private final int matcher;
|
||||
private final float threshold;
|
||||
|
||||
public MatchingModel(Feature2D detector, Feature2D extractor,
|
||||
int matcher, float threshold) {
|
||||
this.detector = detector;
|
||||
this.extractor = extractor;
|
||||
this.matcher = matcher;
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
public Feature2D getDetector() {
|
||||
return detector;
|
||||
}
|
||||
|
||||
public Feature2D getExtractor() {
|
||||
return extractor;
|
||||
}
|
||||
|
||||
public int getMatcher() {
|
||||
return matcher;
|
||||
}
|
||||
|
||||
public float getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package smirnov.colormetric;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CellColors {
|
||||
private final List<Color> referenceColors;
|
||||
private final List<Color> actualColors;
|
||||
|
||||
public CellColors() {
|
||||
referenceColors = new ArrayList<>();
|
||||
actualColors = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addColor(Color actualColor, Color referenceColor) {
|
||||
actualColors.add(actualColor);
|
||||
referenceColors.add(referenceColor);
|
||||
}
|
||||
|
||||
public double calculateMetric(ColorMetric metric) {
|
||||
double sum = 0.0;
|
||||
for (int i = 0; i < referenceColors.size(); ++i) {
|
||||
sum += metric.calculate(actualColors.get(i), referenceColors.get(i));
|
||||
}
|
||||
return sum / referenceColors.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package smirnov.colormetric;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
public class Color {
|
||||
private final DoubleBuffer bgr;
|
||||
private DoubleBuffer lab;
|
||||
|
||||
public Color(DoubleBuffer bgr) {
|
||||
this.bgr = bgr;
|
||||
this.lab = null;
|
||||
}
|
||||
|
||||
public static double channel(DoubleBuffer color, int channel) {
|
||||
return color.get(color.position() + channel);
|
||||
}
|
||||
|
||||
public double red() {
|
||||
return channel(bgr, 2);
|
||||
}
|
||||
|
||||
public double green() {
|
||||
return channel(bgr, 1);
|
||||
}
|
||||
|
||||
public double blue() {
|
||||
return channel(bgr, 0);
|
||||
}
|
||||
|
||||
public double lightness() {
|
||||
calculateLab();
|
||||
return channel(lab, 0) / 2.55;
|
||||
}
|
||||
|
||||
public double a() {
|
||||
calculateLab();
|
||||
return channel(lab, 1) - 128.0;
|
||||
}
|
||||
|
||||
public double b() {
|
||||
calculateLab();
|
||||
return channel(lab, 2) - 128.0;
|
||||
}
|
||||
|
||||
private void calculateLab() {
|
||||
if (this.lab == null) {
|
||||
Scalar lab = bgrToLabScalar(new Scalar(channel(bgr, 0), channel(bgr, 1), channel(bgr, 2)));
|
||||
this.lab = DoubleBuffer.wrap(lab.val);
|
||||
}
|
||||
}
|
||||
|
||||
private Scalar bgrToLabScalar(Scalar color) {
|
||||
Mat bgr = new Mat(1, 1, CvType.CV_8UC4, color);
|
||||
Mat lab = new Mat(1, 1, CvType.CV_32FC3);
|
||||
Imgproc.cvtColor(bgr, lab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
return new Scalar(lab.get(0, 0));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package smirnov.colormetric;
|
||||
|
||||
public interface ColorMetric {
|
||||
double calculate(Color c1, Color c2);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package smirnov.colormetric;
|
||||
|
||||
public class EuclideanLab implements ColorMetric {
|
||||
@Override
|
||||
public double calculate(Color c1, Color c2) {
|
||||
return Math.sqrt(
|
||||
Math.pow(c1.lightness() - c2.lightness(), 2.0) +
|
||||
Math.pow(c1.a() - c2.a(), 2.0) +
|
||||
Math.pow(c1.b() - c2.b(), 2.0)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package smirnov.colormetric;
|
||||
|
||||
public class EuclideanRGB implements ColorMetric {
|
||||
@Override
|
||||
public double calculate(Color c1, Color c2) {
|
||||
return Math.sqrt(
|
||||
Math.pow(c1.red() - c2.red(), 2.0) +
|
||||
Math.pow(c1.green() - c2.green(), 2.0) +
|
||||
Math.pow(c1.blue() - c2.blue(), 2.0)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package smirnov.common;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.TermCriteria;
|
||||
|
||||
public class Clusterizer {
|
||||
private final int clusters;
|
||||
|
||||
public Clusterizer(int clusters) {
|
||||
this.clusters = clusters;
|
||||
}
|
||||
|
||||
/* takes:
|
||||
* source image points reshaped into (rows*cols, channels, 1),
|
||||
* cluster labels (rows*cols, 1, 1)
|
||||
*/
|
||||
public double getBackgroundVariance(Mat samples, Mat labels) {
|
||||
List<Map<String,Double>> clusterStatistics = new ArrayList<>();
|
||||
for (int i = 0; i < clusters; ++i) {
|
||||
clusterStatistics.add(Stream.of(
|
||||
new SimpleEntry<>("second_moment", 0.0),
|
||||
new SimpleEntry<>("count", 0.0)
|
||||
).collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue)));
|
||||
for (int col = 0; col < samples.cols(); ++col) {
|
||||
clusterStatistics.get(i).put("sum_channel_" + col, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
for (int row = 0; row < labels.rows(); ++row) {
|
||||
int label = (int)labels.get(row, 0)[0];
|
||||
Map<String,Double> map = clusterStatistics.get(label);
|
||||
|
||||
for (int col = 0; col < samples.cols(); ++col) {
|
||||
map.put("second_moment", map.get("second_moment") + Math.pow(samples.get(row, col)[0], 2));
|
||||
map.put("sum_channel_" + col, map.get("sum_channel_" + col) + samples.get(row, col)[0]);
|
||||
}
|
||||
map.put("count", map.get("count") + 1.0);
|
||||
}
|
||||
|
||||
Map<String,Double> maxCluster = clusterStatistics.get(0);
|
||||
for (Map<String,Double> c : clusterStatistics) {
|
||||
if (c.get("count") > maxCluster.get("count")) {
|
||||
maxCluster = c;
|
||||
}
|
||||
}
|
||||
|
||||
double result = maxCluster.get("second_moment") / maxCluster.get("count");
|
||||
|
||||
for (int col = 0; col < samples.cols(); ++col) {
|
||||
result -= Math.pow(maxCluster.get("sum_channel_" + col) / maxCluster.get("count"), 2);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* takes:
|
||||
* source image,
|
||||
* array {
|
||||
* cluster labels (rows*cols, 1, 1),
|
||||
* cluster centroids (CLUSTERS, channels, 1)
|
||||
* }
|
||||
*/
|
||||
public Mat getBackgroundSegmentation(Mat image, Mat[] clusters) {
|
||||
Mat samples = getClusteringSamples(image);
|
||||
Mat labels = clusters[0];
|
||||
Mat centroids = clusters[1];
|
||||
|
||||
for (int row = 0; row < labels.rows(); ++row) {
|
||||
int label = (int)labels.get(row, 0)[0];
|
||||
Mat samplesRow = samples.row(row);
|
||||
centroids.row(label).copyTo(samplesRow);
|
||||
}
|
||||
labels.release();
|
||||
centroids.release();
|
||||
|
||||
samples = samples.reshape(image.channels(), image.rows());
|
||||
samples.convertTo(samples, image.type());
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
// returns: source image points reshaped into (rows*cols, channels, 1)
|
||||
public Mat getClusteringSamples(Mat image) {
|
||||
Mat samples = image.reshape(1, image.rows() * image.cols());
|
||||
samples.convertTo(samples, CvType.CV_32F);
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
/* takes: source image points reshaped into (rows*cols, channels, 1)
|
||||
* returns: {
|
||||
* cluster labels (rows*cols, 1, 1),
|
||||
* cluster centroids (CLUSTERS, channels, 1)
|
||||
* }
|
||||
*/
|
||||
public Mat[] clusterize(Mat samples) {
|
||||
return clusterize(samples, 3, 5, 1e-5);
|
||||
}
|
||||
|
||||
public Mat[] clusterize(Mat samples, int attempts, int maxIterations, double epsilon) {
|
||||
Mat labels = new Mat(samples.rows(), 1, CvType.CV_8U);
|
||||
Mat centroids = new Mat(clusters, 1, CvType.CV_32F);
|
||||
TermCriteria criteria = new TermCriteria(TermCriteria.EPS | TermCriteria.MAX_ITER,
|
||||
maxIterations, epsilon);
|
||||
|
||||
Core.kmeans(samples, clusters, labels, criteria, attempts, Core.KMEANS_RANDOM_CENTERS, centroids);
|
||||
|
||||
return new Mat[] {labels, centroids};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package smirnov.common;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
public class HSVBinarization {
|
||||
private final List<Pair<Scalar, Scalar>> targetsAndRanges;
|
||||
|
||||
public HSVBinarization(List<Pair<Scalar, Scalar>> targetsAndRanges) {
|
||||
this.targetsAndRanges = targetsAndRanges;
|
||||
}
|
||||
|
||||
|
||||
public Mat apply(Mat input) {
|
||||
int width = input.cols();
|
||||
int height = input.rows();
|
||||
|
||||
Mat hsvImg = new Mat(height, width, CvType.CV_8UC3);
|
||||
Imgproc.cvtColor(input, hsvImg, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat output = new Mat(height, width, CvType.CV_8UC1);
|
||||
for(int i=0; i<hsvImg.rows(); i++) {
|
||||
for(int j=0; j<hsvImg.cols(); j++) {
|
||||
double[] value = hsvImg.get(i, j);
|
||||
|
||||
boolean hitting = false;
|
||||
for(Pair<Scalar, Scalar> targetAndRange : targetsAndRanges) {
|
||||
Scalar target = targetAndRange.getLeft();
|
||||
Scalar range = targetAndRange.getRight();
|
||||
|
||||
if(Math.abs(value[0]-target.val[0])<range.val[0] && Math.abs(value[1]-target.val[1])<range.val[1] && Math.abs(value[2]-target.val[2])<range.val[2])
|
||||
hitting = true;
|
||||
}
|
||||
|
||||
if(hitting) {
|
||||
output.put(i, j, 255);
|
||||
} else {
|
||||
output.put(i, j, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package smirnov.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
public class Helper {
|
||||
public static Mat whiteThreshold(Mat image) {
|
||||
Mat result = image.clone();
|
||||
Imgproc.cvtColor(result, result, Imgproc.COLOR_BGR2GRAY);
|
||||
Imgproc.threshold(result, result, 240.0, 255.0, Imgproc.THRESH_BINARY_INV);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Mat binarizeSeed(Mat image,
|
||||
List<Pair<Scalar, Scalar>> targetsAndRanges) {
|
||||
HSVBinarization hsv = new HSVBinarization(targetsAndRanges);
|
||||
return hsv.apply(image);
|
||||
}
|
||||
|
||||
public static Mat filterByMask(Mat image, Mat mask) {
|
||||
List<Mat> channels = new ArrayList<>();
|
||||
Core.split(image, channels);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
Mat c = channels.get(i);
|
||||
Core.bitwise_and(c, mask, c);
|
||||
channels.set(i, c);
|
||||
}
|
||||
Mat filtered = new Mat(image.rows(), image.cols(), CvType.CV_8UC3);
|
||||
Core.merge(channels, filtered);
|
||||
for (Mat c : channels) {
|
||||
c.release();
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public static List<MatOfPoint> getContours(Mat image) {
|
||||
Mat gray = new Mat();
|
||||
Imgproc.cvtColor(image, gray, Imgproc.COLOR_RGB2GRAY);
|
||||
List<MatOfPoint> contours = new ArrayList<>();
|
||||
Mat hierarchy = new Mat();
|
||||
Imgproc.findContours(gray, contours, hierarchy, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
|
||||
|
||||
gray.release();
|
||||
hierarchy.release();
|
||||
return contours;
|
||||
}
|
||||
}
|
||||
230
seed-counter-desktop/src/main/java/smirnov/common/SeedUtils.java
Normal file
230
seed-counter-desktop/src/main/java/smirnov/common/SeedUtils.java
Normal file
@@ -0,0 +1,230 @@
|
||||
package smirnov.common;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.math3.stat.descriptive.rank.Percentile;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
public class SeedUtils {
|
||||
public SeedUtils() {
|
||||
this(0.5, 150.0, true);
|
||||
}
|
||||
|
||||
public SeedUtils(double threshold, double whiteThreshold, boolean filterByArea) {
|
||||
this.threshold = threshold;
|
||||
this.whiteThreshold = whiteThreshold;
|
||||
this.filterByArea = filterByArea;
|
||||
}
|
||||
|
||||
private final double MIN_AREA = 5.0;
|
||||
private final double MAX_AREA = 30.0;
|
||||
private final double BRIGHTNESS_PERCENTILE = 10.0;
|
||||
|
||||
private final double threshold;
|
||||
private final double whiteThreshold;
|
||||
private final boolean filterByArea;
|
||||
|
||||
private int seedNumber = 0;
|
||||
private int xOffset = 0;
|
||||
private int yOffset = 0;
|
||||
|
||||
public double getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public double getWhiteThreshold() {
|
||||
return whiteThreshold;
|
||||
}
|
||||
|
||||
public boolean getFileterByArea() {
|
||||
return filterByArea;
|
||||
}
|
||||
|
||||
public void setSeedNumber(int seedNumber) {
|
||||
this.seedNumber = seedNumber;
|
||||
}
|
||||
|
||||
public int getSeedNumber() {
|
||||
return seedNumber;
|
||||
}
|
||||
|
||||
public void setXOffset(int xOffset) {
|
||||
this.xOffset = xOffset;
|
||||
}
|
||||
|
||||
public int getXOffset() {
|
||||
return xOffset;
|
||||
}
|
||||
|
||||
public void setYOffset(int yOffset) {
|
||||
this.yOffset = yOffset;
|
||||
}
|
||||
|
||||
public int getYOffset() {
|
||||
return yOffset;
|
||||
}
|
||||
|
||||
// targets and ranges
|
||||
public static final List<Pair<Scalar, Scalar>> SEED_TYPES = Arrays.asList(
|
||||
new Pair<>(new Scalar(4, 97, 108), new Scalar(50, 100, 80)),
|
||||
new Pair<>(new Scalar(17, 67, 232), new Scalar(50, 50, 50))
|
||||
);
|
||||
|
||||
public static Mat getMask(Mat image, Double scale) {
|
||||
Mat mask = Helper.binarizeSeed(image, SEED_TYPES);
|
||||
Mat whiteMask = Helper.whiteThreshold(image);
|
||||
Core.bitwise_and(mask, whiteMask, mask);
|
||||
whiteMask.release();
|
||||
|
||||
int kernelSize = scale == null ? 10 : (int)(1.5 / Math.sqrt(scale));
|
||||
if (kernelSize < 10) {
|
||||
kernelSize = 10;
|
||||
}
|
||||
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(kernelSize, kernelSize));
|
||||
|
||||
Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_CLOSE, kernel);
|
||||
Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_OPEN, kernel);
|
||||
kernel.release();
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
public static Mat filterByMask(Mat image, Mat mask) {
|
||||
return Helper.filterByMask(image, mask);
|
||||
}
|
||||
|
||||
public int printSeeds(Mat image, Mat imageForFilter, PrintWriter writer,
|
||||
Map<String, String> data, Double scale) {
|
||||
List<MatOfPoint> contours = Helper.getContours(imageForFilter);
|
||||
Mat seedBuffer = Mat.zeros(image.rows(), image.cols(), CvType.CV_8UC1);
|
||||
|
||||
for (MatOfPoint contour : contours) {
|
||||
Double area = scale * Imgproc.contourArea(contour);
|
||||
if (!filterByArea || (area < MAX_AREA && area > MIN_AREA)) {
|
||||
List<Map<String,String>> seedData = getSeedData(contour, image, imageForFilter, seedBuffer);
|
||||
if (!seedData.isEmpty()) {
|
||||
data.put("seed_number", String.valueOf(seedNumber++));
|
||||
data.put("area", area.toString());
|
||||
printSeedData(data, seedData, writer);
|
||||
}
|
||||
}
|
||||
contour.release();
|
||||
}
|
||||
|
||||
seedBuffer.release();
|
||||
|
||||
return seedNumber;
|
||||
}
|
||||
|
||||
private List<Map<String,String>> getSeedData(MatOfPoint contour, Mat image, Mat imageForFilter, Mat seedBuffer) {
|
||||
Imgproc.drawContours(seedBuffer, Collections.singletonList(contour), 0,
|
||||
new Scalar(255.0), Core.FILLED);
|
||||
List<Map<String,String>> result = new ArrayList<>();
|
||||
|
||||
int minX = image.cols() - 1;
|
||||
int maxX = 0;
|
||||
int minY = image.rows() - 1;
|
||||
int maxY = 0;
|
||||
|
||||
for (Point point : contour.toList()) {
|
||||
if (point.x < minX) {
|
||||
minX = (int) point.x;
|
||||
}
|
||||
if (point.x > maxX) {
|
||||
maxX = (int) point.x;
|
||||
}
|
||||
if (point.y < minY) {
|
||||
minY = (int) point.y;
|
||||
}
|
||||
if (point.y > maxY) {
|
||||
maxY = (int) point.y;
|
||||
}
|
||||
}
|
||||
|
||||
List<Double> minChannelValues = new ArrayList<>();
|
||||
|
||||
for (int y = minY; y <= maxY; ++y) {
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
if (seedBuffer.get(y, x)[0] > 0.0) {
|
||||
double[] color = image.get(y, x);
|
||||
double[] colorForFilter = imageForFilter.get(y, x);
|
||||
if (color[0] + color[1] + color[2] > 0.0) {
|
||||
Map<String,String> map = new HashMap<>();
|
||||
map.put("x", String.valueOf(x + xOffset));
|
||||
map.put("y", String.valueOf(y + yOffset));
|
||||
map.put("blue", String.valueOf(color[0]));
|
||||
map.put("green", String.valueOf(color[1]));
|
||||
map.put("red", String.valueOf(color[2]));
|
||||
minChannelValues.add(Math.min(colorForFilter[0], Math.min(colorForFilter[1], colorForFilter[2])));
|
||||
result.add(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Imgproc.drawContours(seedBuffer, Collections.singletonList(contour), 0,
|
||||
new Scalar(0.0), Core.FILLED);
|
||||
|
||||
if (result.size() / (maxX - minX + 1.0) / (maxY - minY + 1.0) < threshold) {
|
||||
result.clear();
|
||||
} else {
|
||||
Percentile percentile = new Percentile();
|
||||
percentile.setData(minChannelValues.stream().mapToDouble(x -> x).toArray());
|
||||
|
||||
if (percentile.evaluate(BRIGHTNESS_PERCENTILE) > whiteThreshold) {
|
||||
result.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void printSeedData(Map<String,String> data, List<Map<String,String>> seedData, PrintWriter writer) {
|
||||
for (Map<String,String> map : seedData) {
|
||||
for (String key : map.keySet()) {
|
||||
data.put(key, map.get(key));
|
||||
}
|
||||
printMap(writer, data);
|
||||
}
|
||||
}
|
||||
|
||||
private void printMap(PrintWriter writer, Map<String,String> map) {
|
||||
boolean header = map.containsKey("header");
|
||||
map.remove("header");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (header) {
|
||||
for (String key : sortedKeySet(map)) {
|
||||
builder.append(key);
|
||||
builder.append("\t");
|
||||
}
|
||||
builder.deleteCharAt(builder.length() - 1);
|
||||
builder.append("\n");
|
||||
}
|
||||
for (String key : sortedKeySet(map)) {
|
||||
builder.append(map.get(key));
|
||||
builder.append("\t");
|
||||
}
|
||||
builder.deleteCharAt(builder.length() - 1);
|
||||
writer.println(builder.toString());
|
||||
}
|
||||
|
||||
private List<String> sortedKeySet(Map<String,String> map) {
|
||||
return map.keySet().stream().sorted().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package smirnov.regression;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
|
||||
import org.apache.commons.math3.linear.LUDecomposition;
|
||||
import org.apache.commons.math3.linear.RealMatrix;
|
||||
import org.apache.commons.math3.stat.regression.OLSMultipleLinearRegression;
|
||||
|
||||
import smirnov.colormetric.Color;
|
||||
|
||||
public abstract class AbstractOLS implements RegressionModel {
|
||||
private final boolean intercept;
|
||||
private double[] beta1;
|
||||
private double[] beta2;
|
||||
private double[] beta3;
|
||||
|
||||
AbstractOLS(boolean intercept) {
|
||||
this.intercept = intercept;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void train(List<DoubleBuffer> train, List<DoubleBuffer> answers) {
|
||||
List<Double> answers1 = new ArrayList<>();
|
||||
List<Double> answers2 = new ArrayList<>();
|
||||
List<Double> answers3 = new ArrayList<>();
|
||||
|
||||
for (DoubleBuffer c : answers) {
|
||||
answers1.add(Color.channel(c, 0));
|
||||
answers2.add(Color.channel(c, 1));
|
||||
answers3.add(Color.channel(c, 2));
|
||||
}
|
||||
|
||||
beta1 = trainChannel(train, answers1);
|
||||
beta2 = trainChannel(train, answers2);
|
||||
beta3 = trainChannel(train, answers3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTransformationDeviance(List<DoubleBuffer> source, List<DoubleBuffer> target) {
|
||||
List<List<Double>> targetFeatures = new ArrayList<>();
|
||||
|
||||
int featuresCount = getFeatures(target.get(0)).length;
|
||||
|
||||
for (int i = 0; i < featuresCount; ++i) {
|
||||
targetFeatures.add(new ArrayList<>());
|
||||
}
|
||||
|
||||
for (DoubleBuffer c : target) {
|
||||
double[] features = getFeatures(c);
|
||||
|
||||
for (int i = 0; i < features.length; ++i) {
|
||||
targetFeatures.get(i).add(features[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int dim = featuresCount + (intercept ? 1 : 0);
|
||||
RealMatrix matrix = new Array2DRowRealMatrix(dim, dim);
|
||||
|
||||
if (intercept) {
|
||||
matrix.setEntry(0, 0, 1.0);
|
||||
for (int col = 1; col < dim; ++col) {
|
||||
matrix.setEntry(0, col, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
for (int index = 0; index < targetFeatures.size(); ++index) {
|
||||
List<Double> answers = targetFeatures.get(index);
|
||||
int row = index + (intercept ? 1 : 0);
|
||||
|
||||
matrix.setRow(row, trainChannel(source, answers));
|
||||
}
|
||||
|
||||
return 1.0 - new LUDecomposition(matrix).getDeterminant();
|
||||
}
|
||||
|
||||
private double[] trainChannel(List<DoubleBuffer> trainSet, List<Double> answers) {
|
||||
double[][] trainArray = new double[answers.size()][3];
|
||||
double[] answersArray = new double[answers.size()];
|
||||
|
||||
for (int i = 0; i < answers.size(); ++i) {
|
||||
answersArray[i] = answers.get(i);
|
||||
trainArray[i] = getFeatures(trainSet.get(i));
|
||||
}
|
||||
|
||||
OLSMultipleLinearRegression regressor = new OLSMultipleLinearRegression();
|
||||
regressor.setNoIntercept(!intercept);
|
||||
regressor.newSampleData(answersArray, trainArray);
|
||||
|
||||
return regressor.estimateRegressionParameters();
|
||||
}
|
||||
|
||||
private double getEstimate(double[] features, double[] beta) {
|
||||
double answer = (intercept ? 1 : 0) * beta[0];
|
||||
for (int i = 0; i < features.length; ++i) {
|
||||
answer += features[i] * beta[i + (intercept ? 1 : 0)];
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calibrate(DoubleBuffer color) {
|
||||
double[] features = getFeatures(color);
|
||||
color.put(new double[] {getEstimate(features, beta1),
|
||||
getEstimate(features, beta2), getEstimate(features, beta3)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.getClass().getSimpleName() + (intercept ? "Intercept" : "");
|
||||
}
|
||||
|
||||
abstract protected double[] getFeatures(DoubleBuffer color);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package smirnov.regression;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
|
||||
import smirnov.colormetric.Color;
|
||||
|
||||
public enum ColorSpace {
|
||||
RGB(false, false),
|
||||
RGB_LINEAR(false, true),
|
||||
XYZ(true, false),
|
||||
XYZ_LINEAR(true, true);
|
||||
|
||||
private final boolean isXYZ;
|
||||
private final boolean isLinear;
|
||||
|
||||
ColorSpace(boolean isXYZ, boolean isLinear) {
|
||||
this.isXYZ = isXYZ;
|
||||
this.isLinear = isLinear;
|
||||
}
|
||||
|
||||
private static double linearizeRGB(double channelColor) {
|
||||
channelColor /= 255.0;
|
||||
if (channelColor > 0.04045) {
|
||||
channelColor = Math.pow((channelColor + 0.055) / 1.055, 2.4);
|
||||
} else {
|
||||
channelColor /= 12.92;
|
||||
}
|
||||
|
||||
return channelColor * 100.0;
|
||||
}
|
||||
|
||||
private static double inverseLinearizeRGB(double channelColor) {
|
||||
channelColor /= 100.0;
|
||||
if (channelColor > 0.0031308) {
|
||||
channelColor = 1.055 * Math.pow(channelColor, 1.0 / 2.4) - 0.055;
|
||||
} else {
|
||||
channelColor *= 12.92;
|
||||
}
|
||||
|
||||
return channelColor * 255.0;
|
||||
}
|
||||
|
||||
public DoubleBuffer convertFromBGR(DoubleBuffer color, boolean inplace) {
|
||||
if (!inplace) {
|
||||
color = DoubleBuffer.wrap(new double[] {Color.channel(color, 0),
|
||||
Color.channel(color, 1), Color.channel(color, 2)});
|
||||
}
|
||||
|
||||
double b = Color.channel(color, 0);
|
||||
double g = Color.channel(color, 1);
|
||||
double r = Color.channel(color, 2);
|
||||
|
||||
if (isLinear) {
|
||||
b = linearizeRGB(b);
|
||||
g = linearizeRGB(g);
|
||||
r = linearizeRGB(r);
|
||||
}
|
||||
|
||||
if (isXYZ) {
|
||||
color.put(color.position(), r * 0.4124 + g * 0.3576 + b * 0.1805);
|
||||
color.put(color.position() + 1, r * 0.2126 + g * 0.7152 + b * 0.0722);
|
||||
color.put(color.position() + 2, r * 0.0193 + g * 0.1192 + b * 0.9505);
|
||||
} else {
|
||||
color.put(color.position(), b);
|
||||
color.put(color.position() + 1, g);
|
||||
color.put(color.position() + 2, r);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
public void convertToBGR(DoubleBuffer color) {
|
||||
if (isXYZ) {
|
||||
double x = Color.channel(color, 0);
|
||||
double y = Color.channel(color, 1);
|
||||
double z = Color.channel(color, 2);
|
||||
color.put(color.position(), 0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
|
||||
color.put(color.position() + 1, -0.9692660 * x + 1.8760108 * y + 0.0415560 * z);
|
||||
color.put(color.position() + 2, 3.2404542 * x - 1.5371385 * y - 0.4985314 * z);
|
||||
}
|
||||
if (isLinear) {
|
||||
color.put(color.position(), inverseLinearizeRGB(Color.channel(color, 0)));
|
||||
color.put(color.position() + 1, inverseLinearizeRGB(Color.channel(color, 1)));
|
||||
color.put(color.position() + 2, inverseLinearizeRGB(Color.channel(color, 2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package smirnov.regression;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.util.List;
|
||||
|
||||
public class IdentityModel implements RegressionModel {
|
||||
@Override
|
||||
public void train(List<DoubleBuffer> train, List<DoubleBuffer> answers) {}
|
||||
|
||||
@Override
|
||||
public double getTransformationDeviance(List<DoubleBuffer> source, List<DoubleBuffer> target) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calibrate(DoubleBuffer c) {}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Identity";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package smirnov.regression;
|
||||
|
||||
public class RegressionFactory {
|
||||
public static RegressionModel createModel(Order order) {
|
||||
if (order == Order.IDENTITY) {
|
||||
return new IdentityModel();
|
||||
}
|
||||
switch(order.order) {
|
||||
case 1: return new SimpleOLS(order.intercept);
|
||||
case 2: return new SecondOrderOLS(order.intercept);
|
||||
default: return new ThirdOrderOLS(order.intercept);
|
||||
}
|
||||
}
|
||||
|
||||
public enum Order {
|
||||
IDENTITY(0, false),
|
||||
FIRST(1, false),
|
||||
FIRST_INTERCEPT(1, true),
|
||||
SECOND(2, false),
|
||||
SECOND_INTERCEPT(2, true),
|
||||
THIRD(3, false),
|
||||
THIRD_INTERCEPT(3, true);
|
||||
|
||||
final int order;
|
||||
final boolean intercept;
|
||||
|
||||
Order(int order, boolean intercept) {
|
||||
this.order = order;
|
||||
this.intercept = intercept;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package smirnov.regression;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.util.List;
|
||||
|
||||
public interface RegressionModel {
|
||||
void train(List<DoubleBuffer> train, List<DoubleBuffer> answers);
|
||||
// calculates (1 - det[H]) metric, where H is the transformation matrix from source to target features
|
||||
double getTransformationDeviance(List<DoubleBuffer> source, List<DoubleBuffer> target);
|
||||
void calibrate(DoubleBuffer c);
|
||||
String getName();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package smirnov.regression;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
|
||||
import smirnov.colormetric.Color;
|
||||
|
||||
public class SecondOrderOLS extends AbstractOLS implements RegressionModel {
|
||||
public SecondOrderOLS(boolean intercept) {
|
||||
super(intercept);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double[] getFeatures(DoubleBuffer color) {
|
||||
double channel0 = Color.channel(color, 0);
|
||||
double channel1 = Color.channel(color, 1);
|
||||
double channel2 = Color.channel(color, 2);
|
||||
|
||||
return new double[] {
|
||||
channel0, channel1, channel2,
|
||||
channel0 * channel0, channel0 * channel1, channel0 * channel2,
|
||||
channel1 * channel1, channel1 * channel2, channel2 * channel2
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package smirnov.regression;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
|
||||
import smirnov.colormetric.Color;
|
||||
|
||||
public class SimpleOLS extends AbstractOLS implements RegressionModel {
|
||||
public SimpleOLS(boolean intercept) {
|
||||
super(intercept);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double[] getFeatures(DoubleBuffer color) {
|
||||
double channel0 = Color.channel(color, 0);
|
||||
double channel1 = Color.channel(color, 1);
|
||||
double channel2 = Color.channel(color, 2);
|
||||
|
||||
return new double[] {
|
||||
channel0, channel1, channel2
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package smirnov.regression;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
|
||||
import smirnov.colormetric.Color;
|
||||
|
||||
public class ThirdOrderOLS extends AbstractOLS implements RegressionModel {
|
||||
public ThirdOrderOLS(boolean intercept) {
|
||||
super(intercept);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double[] getFeatures(DoubleBuffer color) {
|
||||
double channel0 = Color.channel(color, 0);
|
||||
double channel1 = Color.channel(color, 1);
|
||||
double channel2 = Color.channel(color, 2);
|
||||
|
||||
return new double[] {
|
||||
channel0, channel1, channel2,
|
||||
channel0 * channel0, channel0 * channel1, channel0 * channel2,
|
||||
channel1 * channel1, channel1 * channel2, channel2 * channel2,
|
||||
channel0 * channel0 * channel0, channel0 * channel0 * channel1,
|
||||
channel0 * channel0 * channel2, channel0 * channel1 * channel1,
|
||||
channel0 * channel1 * channel2, channel0 * channel2 * channel2,
|
||||
channel1 * channel1 * channel1, channel1 * channel1 * channel2,
|
||||
channel1 * channel2 * channel2, channel2 * channel2 * channel2,
|
||||
};
|
||||
}
|
||||
}
|
||||
46
seed-counter-desktop/src/main/resources/context.xml
Normal file
46
seed-counter-desktop/src/main/resources/context.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
|
||||
|
||||
<bean id="config" class="org.wheatdb.seedcounter.desktop.RunConfiguration" />
|
||||
|
||||
<bean id="detectionProcessor" class="org.wheatdb.seedcounter.processor.DetectionProcessor" />
|
||||
<bean id="binaryMaskProcessor" class="org.wheatdb.seedcounter.processor.BinaryMaskProcessor" />
|
||||
|
||||
|
||||
<bean id="imageHandler" class="org.wheatdb.seedcounter.desktop.ihandlers.ClassicImageHandler">
|
||||
<constructor-arg name="config" ref="config" />
|
||||
<constructor-arg name="detectProcessor" ref="detectionProcessor" />
|
||||
<constructor-arg name="resultSaver" ref="baseResultSaver" />
|
||||
</bean>
|
||||
|
||||
|
||||
|
||||
|
||||
<bean id="baseResultSaver" class="org.wheatdb.seedcounter.desktop.ihandlers.BaseResultSaver">
|
||||
<!--<property name="info" ref="simpleAddInfo"/>-->
|
||||
</bean>
|
||||
|
||||
<bean id="colorResultSaver" class="org.wheatdb.seedcounter.desktop.ihandlers.ColorResultSaver" >
|
||||
<constructor-arg name="baseSaver" ref="baseResultSaver" />
|
||||
</bean>
|
||||
|
||||
<bean id="textureResultSaver" class="org.wheatdb.seedcounter.desktop.ihandlers.TextureResultSaver" >
|
||||
<constructor-arg name="baseSaver" ref="baseResultSaver" />
|
||||
</bean>
|
||||
|
||||
<bean id="colorTextureResultSaver" class="org.wheatdb.seedcounter.desktop.ihandlers.TextureResultSaver" >
|
||||
<constructor-arg name="baseSaver" ref="colorResultSaver" />
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="fileStructureAddInfo" class="org.wheatdb.seedcounter.desktop.ihandlers.FileStructureAddInfo" />
|
||||
<bean id="simpleAddInfo" class="org.wheatdb.seedcounter.desktop.ihandlers.SimpleAddInfo" />
|
||||
|
||||
|
||||
|
||||
|
||||
</beans>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.3 MiB |
188
seed-counter-desktop/src/test/java/ru/delkom07/AppTest.java
Normal file
188
seed-counter-desktop/src/test/java/ru/delkom07/AppTest.java
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* This Java source file was generated by the Gradle 'init' task.
|
||||
*/
|
||||
package ru.delkom07;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AppTest {
|
||||
@Test void appHasAGreeting() {
|
||||
//App classUnderTest = new App();
|
||||
//assertNotNull(classUnderTest.getGreeting(), "app should have a greeting");
|
||||
}
|
||||
|
||||
|
||||
@Test void generalTest() {
|
||||
String[] args = new String[]{"../../../workspace/data/itmi_2023/input", "--out", "../../../workspace/data/itmi_2023/output", "-cc", "-ws", "-dr", "-cd"};
|
||||
|
||||
// SpringApplication.run(DesktopMainWithColorDescriptorsOneThread.class, args);
|
||||
// ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
|
||||
// Bean bean = (Bean) context.getBean("bean");
|
||||
// System.out.println(bean.getName());
|
||||
//
|
||||
//
|
||||
//
|
||||
// System.out.println("Start");
|
||||
// System.out.println("Length: " + args.length);
|
||||
// System.out.println(Arrays.toString(args));
|
||||
//
|
||||
// OpenCV.loadLocally();
|
||||
//
|
||||
// long time = System.currentTimeMillis();
|
||||
//
|
||||
// try {
|
||||
// // processing of command line
|
||||
// List<ArgOption> optMap = new ArrayList<ArgOption>(2);
|
||||
// optMap.add(new ArgOption("--help", "-h", true)); // print help
|
||||
// optMap.add(new ArgOption("--out", "-o", false)); // set output directory
|
||||
//
|
||||
// optMap.add(new ArgOption("--colorchecker", "-cc", true)); // image contains ColorChecker
|
||||
// optMap.add(new ArgOption("--calibrate", "-cl", true)); // do color calibration with ColorChecker
|
||||
// optMap.add(new ArgOption("--withoutsheet", "-ws", true)); // background is solid. standard sheet of paper is not used for scale definition.
|
||||
//
|
||||
// optMap.add(new ArgOption("--colordescriptors", "-cd", true)); // calculate color descriptors
|
||||
// optMap.add(new ArgOption("--draw", "-dr", true)); // draw additional output images
|
||||
// optMap.add(new ArgOption("--markdescriptor", "-md", false)); // mark HSV color descriptor on output image
|
||||
//
|
||||
// optMap.add(new ArgOption("--potatoes", "-pt", true)); //
|
||||
//
|
||||
// CommandRepresentation cmd = new CommandRepresentation(args, optMap, optMap.size(), 0, 2000, 0);
|
||||
//
|
||||
// File outputDir = handleOptions(cmd);
|
||||
//
|
||||
// // preparing input files
|
||||
// File inputDir = new File(cmd.getAcceptedArgs()[0]);
|
||||
//
|
||||
// String base = "output";
|
||||
// String extention = "tsv";
|
||||
// File outputDataFile = new File(outputDir, base + "." + extention);
|
||||
//
|
||||
//
|
||||
// PrintWriter writer = new PrintWriter(outputDataFile, "UTF-8");
|
||||
// writeFileHead(writer);
|
||||
//
|
||||
// ImageWorkspace workspace = new ImageWorkspace(inputDir, outputDir, true);
|
||||
// List<OpUnit> opUnits = workspace.getRawOpUnits();
|
||||
//
|
||||
// for(OpUnit pair : opUnits) {
|
||||
// File inFile = pair.getIn();
|
||||
// File outFile = pair.getOut();
|
||||
//
|
||||
// AddInfo info = AddInfo.fromFilesStructure(inFile);
|
||||
//
|
||||
// System.out.println(inFile.getCanonicalPath());
|
||||
//
|
||||
// try {
|
||||
//
|
||||
// Mat srcImg = Imgcodecs.imread(inFile.getCanonicalPath(), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
|
||||
//
|
||||
// if(srcImg.empty())
|
||||
// throw new IncorrectCmdException("Input file is not image.");
|
||||
//
|
||||
// Mat region = srcImg;
|
||||
//
|
||||
// if(!withoutsheet) {
|
||||
// Quad quad = detectProcessor.detectQuad(srcImg);
|
||||
//
|
||||
// if(null!=quad) {
|
||||
// region = detectProcessor.getTransformedField(srcImg, quad);
|
||||
// } else {
|
||||
// System.out.print("Input file:" + inFile.getName()+". ");
|
||||
// System.out.println("Sheet isn't found. Skipping image.");
|
||||
//
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Imgcodecs.imwrite(outFile.getCanonicalPath().replace(".png", "_transformed.png"), region);
|
||||
//
|
||||
// List<PlanarObject> containers;
|
||||
//
|
||||
// // if is colorchecker
|
||||
// Double transformationDeviance = 0.0;
|
||||
// if(colorchecker) {
|
||||
// MyColorChecker myColorChecker = new MyColorChecker(region);
|
||||
//
|
||||
// // fill ColorChecker
|
||||
// myColorChecker.fillColorChecker();
|
||||
//
|
||||
//
|
||||
// containers = detectProcessor.detectSeedContours(region, myColorChecker.getPixPerMM());
|
||||
//
|
||||
// if(calibrate) {
|
||||
// Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
//
|
||||
// //if(!region.empty()) { // STUB: need to release not calibrated region
|
||||
// // region.release();
|
||||
// //}
|
||||
//
|
||||
// region = calibratedPair.getLeft();
|
||||
// transformationDeviance = calibratedPair.getRight();
|
||||
//
|
||||
// //System.out.println("# Transformation deviance: " + transformationDeviance.toString());
|
||||
// }
|
||||
// } else {
|
||||
// containers = detectProcessor.detectSeedContours(region.clone());
|
||||
// }
|
||||
//
|
||||
// //System.out.println("Seed count: " + containers.size());
|
||||
//
|
||||
// saveResults(writer, region, containers, inFile.getName(), info, transformationDeviance);
|
||||
// JSONSaver.saveInJSON(region, containers, inFile, outFile);
|
||||
//
|
||||
// if(draw) {
|
||||
// if(markdescriptor) {
|
||||
// drawContoursWithRectAndNumAndMarkGCH(region, containers, colorSpace, markGCHDimension, markGCHBinNumber);
|
||||
// } else {
|
||||
// drawContoursWithRectAndNum(region, containers);
|
||||
// }
|
||||
//
|
||||
// Imgcodecs.imwrite(outFile.getCanonicalPath(), region);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // Releases
|
||||
// for(PlanarObject container : containers) {
|
||||
// container.getContour().release();
|
||||
// }
|
||||
// containers.clear();
|
||||
//
|
||||
// if(!srcImg.empty()) {
|
||||
// srcImg.release();
|
||||
// }
|
||||
//
|
||||
// if(!region.empty()) {
|
||||
// region.release();
|
||||
// }
|
||||
//
|
||||
// pair.setProcessed();
|
||||
// System.out.println(inFile.getParent() + "/" + inFile.getName() + " done.");
|
||||
//
|
||||
// } catch(IncorrectCmdException ex) {
|
||||
// System.exit(-1);
|
||||
// } catch (SeedCounterProcessorException e) {
|
||||
// e.printStackTrace();
|
||||
// continue;
|
||||
// } catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// writer.close();
|
||||
//
|
||||
// System.out.println("Computation time: " + Integer.toString((int)(System.currentTimeMillis() - time)/1000));
|
||||
//
|
||||
// System.exit(0); // STATUS - NORM
|
||||
//
|
||||
// } catch (IncorrectCmdException e) {
|
||||
// System.err.println("Error: "+e.getMessage());
|
||||
// usage(e.getMessage());
|
||||
// } catch (IOException e) {
|
||||
// System.err.println("Error: "+e.getMessage());
|
||||
// System.out.println("IO error. The program will be terminated");
|
||||
// System.exit(1);
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* This Java source file was generated by the Gradle 'init' task.
|
||||
*/
|
||||
package ru.delkom07;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import ru.delkom07.fileutils.FileUtilities;
|
||||
import ru.delkom07.workspaces.SimpleWorkspace;
|
||||
|
||||
class WorkspaceTest {
|
||||
@Test void simpleWorkspace() throws IOException {
|
||||
File inputDir = new File("src/test/resources/workspace/input");
|
||||
File outputDir = new File("src/test/resources/workspace/output");
|
||||
|
||||
assertTrue(inputDir.exists());
|
||||
assertTrue(outputDir.exists());
|
||||
|
||||
SimpleWorkspace simpleWorkspace = new SimpleWorkspace(inputDir, outputDir, true, FileUtilities.imageFilter, null);
|
||||
List<SimpleWorkspace.OpUnit> units = simpleWorkspace.getOpUnits();
|
||||
|
||||
assertEquals(units.size(), 2, "There should be 2 file in resource test input dir (all recurcively searched).");
|
||||
assertEquals(units.get(0).getIn().getName(), "input.jpg");
|
||||
assertEquals(units.get(1).getIn().getName(), "subInput.jpg");
|
||||
|
||||
|
||||
System.out.println(units.get(0).getOut().getCanonicalPath());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test void generalTest() {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user