This commit is contained in:
Evgeniy
2024-05-16 15:41:59 +07:00
commit 2dfee5edbe
151 changed files with 17349 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
TODO:
Закончить рефакторинг с введением PlanarObject, WritableData и ImageArea интерфейсов.
Удалить или пересмотреть различные Main-классы запуска SeedCounter-а.

View 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()
}

View File

@@ -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();
}
}

View File

@@ -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;
}
*/

View File

@@ -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)
}
};
*/

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,5 @@
package org.wheatdb.seedcounter.desktop;
public enum ColorSpace {
RGB, HSV, Lab, YCrCb;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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...]";
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
//}

View File

@@ -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);
}
}

View File

@@ -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;
// }
}

View File

@@ -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();
}

View File

@@ -0,0 +1,6 @@
package org.wheatdb.seedcounter.processor;
import org.wheatdb.seedcounter.WritableData;
public interface PlanarObject extends WritableData, ImageArea {
}

View File

@@ -0,0 +1,5 @@
package org.wheatdb.seedcounter.processor;
public interface Processor {
}

View File

@@ -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);
}
}

View File

@@ -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);
//}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
package org.wheatdb.seedcounter.processor.filters;
import org.opencv.core.Mat;
public interface IFilter {
Mat apply(Mat img);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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]);
}
}
}
*/

View File

@@ -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;
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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");
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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--);
}
}
}

View File

@@ -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/");
}

View File

@@ -0,0 +1,7 @@
package ru.delkom07.recognition.potatoes;
public enum PotatoType {
WHITE, DARK;
}

View File

@@ -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;
// }
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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));
}
}

View File

@@ -0,0 +1,5 @@
package smirnov.colormetric;
public interface ColorMetric {
double calculate(Color c1, Color c2);
}

View File

@@ -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)
);
}
}

View File

@@ -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)
);
}
}

View File

@@ -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};
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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());
}
}

View File

@@ -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);
}

View File

@@ -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)));
}
}
}

View File

@@ -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";
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}

View File

@@ -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
};
}
}

View File

@@ -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
};
}
}

View File

@@ -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,
};
}
}

View 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

View 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);
// }
}
}

View File

@@ -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() {
}
}