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

9
.gitattributes vendored Normal file
View File

@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf

45
.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
.gradle
**/build/
!src/**/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
#!gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties
# Cache of project
.gradletasknamecache
# Eclipse Gradle plugin generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# My .gitignore:
# Eclipse data:
bin/
.settings/
### Java ###
*.class
# Package Files #
*.jar
*.war
*.ear
# Log file
*.log
!gradle-wrapper.jar

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

240
gradlew vendored Normal file
View File

@@ -0,0 +1,240 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

91
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,91 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,49 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java library 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 java-library plugin for API and implementation separation.
id 'java-library'
}
compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Use JUnit Jupiter for testing.
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
// This dependency is exported to consumers, that is to say found on their compile classpath.
implementation 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
//implementation 'com.google.guava:guava:31.0.1-jre'
// https://mvnrepository.com/artifact/org.knowm.xchart/xchart
implementation 'org.knowm.xchart:xchart:3.5.4'
//testImplementation("org.openpnp:opencv:3.4.2-0")
implementation("org.openpnp:opencv:3.4.2-0")
//testImplementation("org.opencv:opencv:4.9.0")
//implementation("org.opencv:opencv:4.9.0")
implementation project(":utils")
}
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

View File

@@ -0,0 +1,10 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package ru.delkom07;
public class Library {
public boolean someLibraryMethod() {
return true;
}
}

View File

@@ -0,0 +1,214 @@
package ru.delkom07.improc.color;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.knowm.xchart.BitmapEncoder;
import org.knowm.xchart.BitmapEncoder.BitmapFormat;
import org.knowm.xchart.CategoryChart;
import org.knowm.xchart.CategoryChartBuilder;
import org.knowm.xchart.PieChart;
import org.knowm.xchart.PieChartBuilder;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.internal.chartpart.Chart;
import org.knowm.xchart.style.Styler.LegendPosition;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import ru.delkom07.improc.color.descriptors.ColorLayoutDescriptor;
import ru.delkom07.improc.color.descriptors.DominantColor;
import ru.delkom07.improc.color.descriptors.DominantColorDescriptor;
import ru.delkom07.improc.color.descriptors.GlobalColorHistogram;
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
import ru.delkom07.improc.color.descriptors.SimpleHistograms;
import ru.delkom07.util.Pair;
public class ColorDescriptorsDemo {
public static void main(String[] args) throws IOException {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
if(0 == args.length) {
printHelp();
System.exit(-1);
}
File imgFile = new File(args[0]);
Mat img = Imgcodecs.imread(imgFile.getCanonicalPath());
ColorDescriptorsDemo cd = new ColorDescriptorsDemo();
Chart mcChart = cd.calculateMeanColorDescriptor(img, false);
Chart gchChart = cd.calculateGCH(img, false);
Chart histChart = cd.calculateHistogram(img, false);
Chart dcdChart = cd.calculateDCD(img, false);
Chart cldChart = cd.calculateCLD(img, false);
File outputDir = new File(imgFile.getParent(), "output");
if(!outputDir.exists()) {
outputDir.mkdirs();
}
BitmapEncoder.saveBitmapWithDPI(mcChart, outputDir.getCanonicalPath() + "/mc", BitmapFormat.PNG, 300);
BitmapEncoder.saveBitmapWithDPI(gchChart, outputDir.getCanonicalPath() + "/gch", BitmapFormat.PNG, 300);
BitmapEncoder.saveBitmapWithDPI(histChart, outputDir.getCanonicalPath() + "/hist", BitmapFormat.PNG, 300);
BitmapEncoder.saveBitmapWithDPI(dcdChart, outputDir.getCanonicalPath() + "/dcd", BitmapFormat.PNG, 300);
BitmapEncoder.saveBitmapWithDPI(cldChart, outputDir.getCanonicalPath() + "/cld", BitmapFormat.PNG, 300);
}
public Chart calculateMeanColorDescriptor(Mat img, boolean show) {
int rejectionSigma = 3;
MeanColorDescriptor meanColorDescriptor = new MeanColorDescriptor(rejectionSigma).calculate(img);
double[] meanColor = meanColorDescriptor.getMeanColorWithoutSigma();
// Create Chart
Color[] sliceColors = new Color[1];
sliceColors[0] = new Color((int)meanColor[2], (int)meanColor[1], (int)meanColor[0]);
// Create Chart
PieChart chart = new PieChartBuilder().width(800).height(600).title("Mean color descriptor").build();
// Customize Chart
chart.getStyler().setSeriesColors(sliceColors);
// Series
chart.addSeries(Integer.toString(1), 100);
if(show) {
new SwingWrapper<PieChart>(chart).displayChart();
}
return chart;
}
public Chart calculateGCH(Mat img, boolean show) {
int dimension = 3;
GlobalColorHistogram gch = new GlobalColorHistogram(dimension).calculate(img);
double[] flattenGCH = gch.getFlattenGCH();
// Create Chart
CategoryChart chart = new CategoryChartBuilder().width(800).height(600).title("Global Color Histogram").xAxisTitle("Score").yAxisTitle("Number").build();
// Customize Chart
chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
//chart.getStyler().setHasAnnotations(true);
double[] labels = new double[dimension*dimension*dimension];
for(int i=0; i<dimension*dimension*dimension; i++) {
labels[i] = i;
}
// Series
chart.addSeries("Values", labels, flattenGCH);
if(show) {
new SwingWrapper<CategoryChart>(chart).displayChart();
}
return chart;
}
public Chart calculateCLD(Mat img, boolean show) {
ColorLayoutDescriptor cld = new ColorLayoutDescriptor(12).calculate(img, null, null);
double[] descriptorB = cld.getBlueDescriptor();
double[] descriptorG = cld.getGreenDescriptor();
double[] descriptorR = cld.getRedDescriptor();
// Create Chart
CategoryChart chart = new CategoryChartBuilder().width(800).height(600).title("Color Layout Descriptor").xAxisTitle("Score").yAxisTitle("Number").build();
// Customize Chart
chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
//chart.getStyler().setHasAnnotations(true);
double[] labels = new double[] {1,2,3,4,5,6,7,8,9,10,11,12};
// Series
chart.addSeries("B", labels, descriptorB);
chart.addSeries("G", labels, descriptorG);
chart.addSeries("R", labels, descriptorR);
if(show) {
new SwingWrapper<CategoryChart>(chart).displayChart();
}
return chart;
}
public Chart calculateDCD(Mat img, boolean show) {
int colorsAmount = 3;
DominantColorDescriptor dcd = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(img, null, null);
List<DominantColor> dominantColors = dcd.getDominantColors();
System.out.println(dcd.getSparialCoherency());
Color[] sliceColors = new Color[colorsAmount];
for(int i=0; i<colorsAmount; i++) {
double[] color = dominantColors.get(i).color;
sliceColors[i] = new Color((int)color[2], (int)color[1], (int)color[0]);
}
// Create Chart
PieChart chart = new PieChartBuilder().width(800).height(600).title("Dominant Colors Descriptor").build();
// Customize Chart
chart.getStyler().setSeriesColors(sliceColors);
// Series
for(int i=0; i<dominantColors.size(); i++) {
chart.addSeries(Integer.toString(i+1), dominantColors.get(i).persent*100);
}
if(show) {
new SwingWrapper<PieChart>(chart).displayChart();
}
return chart;
}
public Chart calculateHistogram(Mat img, boolean show) {
SimpleHistograms hists = new SimpleHistograms(new Pair<Integer, Integer>(0, 256), 64, SimpleHistograms.ImgType.COLOR).calculate(img, null, null);
double[] dataB = hists.getBlueHistogram();
double[] dataG = hists.getGreenHistogram();
double[] dataR = hists.getRedHistogram();
double[] labels = new double[dataB.length];
for(int i=0; i<dataB.length; i++) {
labels[i] = i;
}
// Create Chart
CategoryChart chart = new CategoryChartBuilder().width(800).height(600).title("Simple Histograms").xAxisTitle("Score").yAxisTitle("Number").build();
// Customize Chart
chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
//chart.getStyler().setHasAnnotations(true);
// Series
chart.addSeries("B", labels, dataB);
chart.addSeries("G", labels, dataG);
chart.addSeries("R", labels, dataR);
if(show) {
new SwingWrapper<CategoryChart>(chart).displayChart();
}
return chart;
}
public static void printHelp() {
System.out.println("Usage: java -jar program.jar inputImage.png");
}
}

View File

@@ -0,0 +1,10 @@
package ru.delkom07.improc.color;
public class GCH_ColorSpacies_Demo {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,83 @@
package ru.delkom07.improc.color;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
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.GlobalColorHistogram;
public class GCH_Demo {
private static final int rectHeight = 50;
private static final int rectWidth = 250;
private static final int gap = 20;
private static final int startX0 = 160;
private static final int startY0 = 30;
public static void main(String[] args) throws IOException {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
File outputDir = new File("../data/berners_seeds");
int dimension = 8;
GlobalColorHistogram gch = new GlobalColorHistogram(dimension);
PrintWriter writer = new PrintWriter(new File(outputDir, "gch8.tsv"), "UTF-8");
int imgHeight = 2*startY0 + (gap+rectHeight)*dimension*dimension*dimension;
Mat gchImg = Mat.zeros(new Size(500, imgHeight), CvType.CV_8UC3);
for(int i=0; i<dimension*dimension*dimension; i++) {
double[] currentColor = gch.getBinColor(i);
String hex = String.format("#%02x%02x%02x", (int)currentColor[2], (int)currentColor[1], (int)currentColor[0]);
writer.print("GCH8_" + (i+1) + " " + currentColor[0] + " " + currentColor[1] + " " + currentColor[2] + " " + hex);
writer.println();
int startX1 = startX0+rectWidth;
int startY1 = startY0+rectHeight;
int step = rectHeight + gap;
Imgproc.putText(gchImg, "GCH8_" + (i+1), new Point(startX0-100, startY0+rectHeight/2 + i*step), 1, 1, new Scalar(255, 255, 255));
Imgproc.rectangle(gchImg, new Point(startX0, startY0 + i*step), new Point(startX1, startY1 + i*step),
new Scalar(currentColor[0], currentColor[1], currentColor[2]), -1);
}
writer.close();
Mat bgr = new Mat();
Imgproc.cvtColor(gchImg, bgr, Imgproc.COLOR_RGB2BGR);
Imgcodecs.imwrite(new File(outputDir, "RGB_gch"+dimension+".png").getCanonicalPath(), bgr);
Mat hsv = new Mat();
Imgproc.cvtColor(gchImg, hsv, Imgproc.COLOR_HSV2BGR);
Imgcodecs.imwrite(new File(outputDir, "HSV_gch"+dimension+".png").getCanonicalPath(), hsv);
Mat Lab = new Mat();
Imgproc.cvtColor(gchImg, Lab, Imgproc.COLOR_Lab2BGR);
Imgcodecs.imwrite(new File(outputDir, "Lab_gch"+dimension+".png").getCanonicalPath(), Lab);
Mat YCbCr = new Mat();
Imgproc.cvtColor(gchImg, YCbCr, Imgproc.COLOR_YCrCb2BGR);
Imgcodecs.imwrite(new File(outputDir, "YCbCr_gch"+dimension+".png").getCanonicalPath(), YCbCr);
}
}

View File

@@ -0,0 +1,204 @@
package ru.delkom07.improc.color.descriptors;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Rect;
import org.opencv.core.Size;
/**
* Дескриптор цветового распределения.
* Исходное изображение (в MPEG-7 этот дескриптор вычисляется в цветовом пространстве Y/Cb/Cr) разбивается на блоки (8*8=64 блока).
* Для каждого блока вычисляется средний цвет и записывается в соответствующий пиксель матрицы 8*8.
* Далее производится дискретное косинусное преобразование и в результирующий массив записываются самые левые верхние цвета этой матрицы в зигзагообразном порядке.
* @author KomyshevEG
*
*/
public class ColorLayoutDescriptor {
private static final int[] zigzagScanInds = new int[] {0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56};
private static final int MAX_DESCRIPTOR_SIZE = zigzagScanInds.length;
private static final int GRID_DIMENSION = 8;
private Mat blocksColorsR = new Mat(new Size(8, 8), CvType.CV_8UC1);
private Mat blocksColorsG = new Mat(new Size(8, 8), CvType.CV_8UC1);
private Mat blocksColorsB = new Mat(new Size(8, 8), CvType.CV_8UC1);
private double[] descriptorR;
private double[] descriptorG;
private double[] descriptorB;
private int descriptorSize;
private boolean calculated = false;
public ColorLayoutDescriptor(int descriptorSize) {
if(descriptorSize > MAX_DESCRIPTOR_SIZE) {
descriptorSize = MAX_DESCRIPTOR_SIZE;
}
this.descriptorSize = descriptorSize;
descriptorR = new double[descriptorSize];
descriptorG = new double[descriptorSize];
descriptorB = new double[descriptorSize];
}
public ColorLayoutDescriptor calculate(Mat img, Mat mask, Rect roi) {
if(null == roi) {
roi = new Rect(0, 0, img.cols(), img.rows());
}
int xBlockSize = roi.width/GRID_DIMENSION;
int yBlockSize = roi.height/GRID_DIMENSION;
for(int xBlock=0; xBlock<GRID_DIMENSION; xBlock++) {
for(int yBlock=0; yBlock<GRID_DIMENSION; yBlock++) {
Rect rect = new Rect(roi.x + xBlock*xBlockSize, roi.y + yBlock*yBlockSize, xBlockSize, yBlockSize);
double[] color = (null != mask) ? getMiddleColor(img, mask, rect) : getMiddleColor(img, rect);
blocksColorsR.put(yBlock, xBlock, color[0]);
blocksColorsG.put(yBlock, xBlock, color[1]);
blocksColorsB.put(yBlock, xBlock, color[2]);
}
}
Mat convertedR = new Mat();
Mat convertedG = new Mat();
Mat convertedB = new Mat();
blocksColorsR.convertTo(convertedR, CvType.CV_32FC3);
blocksColorsG.convertTo(convertedG, CvType.CV_32FC3);
blocksColorsB.convertTo(convertedB, CvType.CV_32FC3);
Mat transformedR = new Mat(convertedR.size(), convertedR.type());
Mat transformedG = new Mat(convertedG.size(), convertedG.type());
Mat transformedB = new Mat(convertedB.size(), convertedB.type());
Core.dct(convertedR, transformedR);
Core.dct(convertedG, transformedG);
Core.dct(convertedB, transformedB);
for(int i=0; i<descriptorSize; i++) {
int x = zigzagScanInds[i]%GRID_DIMENSION;
int y = (int)zigzagScanInds[i]/GRID_DIMENSION;
descriptorR[i] = blocksColorsR.get(y, x)[0];
descriptorG[i] = blocksColorsG.get(y, x)[0];
descriptorB[i] = blocksColorsB.get(y, x)[0];
}
calculated = true;
return this;
}
public double[] getBlueDescriptor() {
if(!calculated) {
throw new RuntimeException("Descriptor was not calculated");
}
return descriptorB;
}
public double[] getGreenDescriptor() {
if(!calculated) {
throw new RuntimeException("Descriptor was not calculated");
}
return descriptorG;
}
public double[] getRedDescriptor() {
if(!calculated) {
throw new RuntimeException("Descriptor was not calculated");
}
return descriptorR;
}
private double[] getMiddleColor(Mat img, Mat mask, Rect rect) {
double[] middleColor = new double[3];
int count = 0;
for(int i=rect.y; i<rect.y+rect.height; i++) {
for(int j=rect.x; j<rect.x+rect.width; j++) {
if(mask.get(i, j)[0] == 255) {
double[] color = img.get(i, j);
middleColor[0] += color[0];
middleColor[1] += color[1];
middleColor[2] += color[2];
count++;
}
}
}
middleColor[0] = middleColor[0] / ((double)count);
middleColor[1] = middleColor[1] / ((double)count);
middleColor[2] = middleColor[2] / ((double)count);
return middleColor;
}
private double[] getMiddleColor(Mat img, Rect rect) {
double[] middleColor = new double[3];
int count = 0;
for(int i=rect.y; i<rect.y+rect.height; i++) {
for(int j=rect.x; j<rect.x+rect.width; j++) {
double[] color = img.get(i, j);
middleColor[0] += color[0];
middleColor[1] += color[1];
middleColor[2] += color[2];
count++;
}
}
middleColor[0] = middleColor[0] / ((double)count);
middleColor[1] = middleColor[1] / ((double)count);
middleColor[2] = middleColor[2] / ((double)count);
return middleColor;
}
@Override
public String toString() {
String result = "";
for(int i=0; i<descriptorSize; i++) {
if(i<descriptorR.length-1) {
result += "[" + descriptorR[i] + ", " + descriptorG[i] + ", " + descriptorB[i] + "]";
}
}
return result;
}
public String toTsvCsvString(String sep) {
String result = "";
for(int i=0; i<descriptorSize; i++) {
if(i<descriptorR.length-1) {
result += descriptorR[i] + sep + descriptorG[i] + sep + descriptorB[i] + sep;
} else {
result += descriptorR[i] + sep + descriptorG[i] + sep + descriptorB[i];
}
}
return result;
}
}

View File

@@ -0,0 +1,18 @@
package ru.delkom07.improc.color.descriptors;
import org.apache.commons.math3.ml.clustering.Clusterable;
public class ColorPixelContainer implements Clusterable {
private double[] pixel;
public ColorPixelContainer(double[] pixel) {
this.pixel = pixel;
}
@Override
public double[] getPoint() {
return pixel;
}
}

View File

@@ -0,0 +1,26 @@
package ru.delkom07.improc.color.descriptors;
public class DominantColor {
public double[] color;
public double persent;
public double variance;
public DominantColor(double[] color, double persent, double variance) {
this.color = color;
this.persent = persent;
this.variance = variance;
}
@Override
public String toString() {
return color[0] + ", " + color[1] + ", " + color[2] + ", " + persent + ", " + variance;
}
public String toTsvCsvString(String sep) {
return color[0] + sep + color[1] + sep + color[2] + sep + persent + sep + variance;
}
}

View File

@@ -0,0 +1,266 @@
package ru.delkom07.improc.color.descriptors;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.math3.ml.clustering.CentroidCluster;
import org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer;
import org.apache.commons.math3.ml.clustering.MultiKMeansPlusPlusClusterer;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import ru.delkom07.improc.geometry.GeometricFunctions;
public class DominantColorDescriptor {
private List<DominantColor> dominantColors = new ArrayList<DominantColor>();
private double spatialCoherency;
private int descriptorSize;
private int xStepSize;
private int yStepSize;
private boolean calculated = false;
public static final short SORT_BY_PERSENT = 0;
public static final short SORT_BY_BRIGHTNESS = 1;
private short sortBy = 0;
public DominantColorDescriptor(int descriptorSize) {
this.descriptorSize = descriptorSize;
this.xStepSize = 1;
this.yStepSize = 1;
}
public DominantColorDescriptor(int descriptorSize, int xStepSize, int yStepSize, short sortBy) {
this.descriptorSize = descriptorSize;
this.xStepSize = xStepSize > 0 ? xStepSize : 1;
this.yStepSize = yStepSize > 0 ? yStepSize : 1;
this.sortBy = sortBy;
}
public DominantColorDescriptor calculate(Mat img, Mat mask, Rect roi) {
if(null == roi) {
roi = new Rect(0, 0, img.cols(), img.rows());
}
List<CentroidCluster<ColorPixelContainer>> clusters = null != mask ? clusterizeColors(img, mask, roi, descriptorSize) : clusterizeColors(img, roi, descriptorSize);
int clustersSumSize = 0;
for(CentroidCluster<ColorPixelContainer> cluster : clusters) {
clustersSumSize += cluster.getPoints().size();
}
for(CentroidCluster<ColorPixelContainer> cluster : clusters) {
List<ColorPixelContainer> clusterColors = cluster.getPoints();
double[] clusterCenterColor = cluster.getCenter().getPoint();
int clusterSize = cluster.getPoints().size();
double variance = calculateColorVariance(clusterColors, clusterCenterColor);
dominantColors.add(new DominantColor(clusterCenterColor, clusterSize/(double)clustersSumSize, variance));
}
switch(sortBy) {
case 1: {
dominantColors.sort(new Comparator<DominantColor>() {
@Override
public int compare(DominantColor o1, DominantColor o2) {
double val1 = o1.color[0]+o1.color[1]+o1.color[2];
double val2 = o2.color[0]+o2.color[1]+o2.color[2];
if(val1<val2) {
return -1;
}
if(val1>val2) {
return 1;
}
return 0;
}
});
break;
}
default: {
dominantColors.sort(new Comparator<DominantColor>() {
@Override
public int compare(DominantColor o1, DominantColor o2) {
if(o1.persent<o2.persent) {
return -1;
}
if(o1.persent>o2.persent) {
return 1;
}
return 0;
}
});
}
}
spatialCoherency = calculatespatialCoherency(img.size(), clusters, clustersSumSize);
calculated = true;
return this;
}
public List<DominantColor> getDominantColors() {
if(!calculated) {
throw new RuntimeException("Descriptor was not calculated");
}
return dominantColors;
}
public double getSparialCoherency() {
if(!calculated) {
throw new RuntimeException("Descriptor was not calculated");
}
return spatialCoherency;
}
private double calculateColorVariance(List<ColorPixelContainer> colors, double[] clusterCenterColor) {
double sumSqrDeviance = 0;
for(ColorPixelContainer container : colors) {
sumSqrDeviance += GeometricFunctions.euclideanDistance(container.getPoint(), clusterCenterColor);
}
return sumSqrDeviance/colors.size();
}
private double calculatespatialCoherency(Size size, List<CentroidCluster<ColorPixelContainer>> clusters, int clustersSumSize) {
double coherency = 0;
for(CentroidCluster<ColorPixelContainer> cluster : clusters) {
List<ColorPixelContainer> clusterColors = cluster.getPoints();
if(0 != clusterColors.size()) {
double percentage = clusterColors.size() / clustersSumSize;
Mat clusterImprint = Mat.zeros(size, CvType.CV_8UC1);
for(ColorPixelContainer container : clusterColors) {
double[] point = container.getPoint();
clusterImprint.put((int)point[1], (int)point[0], 255);
}
int neighbors = 0;
for(int i=0; i<clusterImprint.rows(); i++) {
for(int j=0; j<clusterImprint.cols(); j++) {
double pixel = clusterImprint.get(i, j)[0];
if(0 != pixel) {
if(i-1 >= 0 ? (0 != clusterImprint.get(i-1, j)[0]) : false) {
neighbors++;
continue;
}
if(j+1 < clusterImprint.cols() ? (0 != clusterImprint.get(i, j+1)[0]) : false) {
neighbors++;
continue;
}
if(i+1 < clusterImprint.rows() ? (0 != clusterImprint.get(i+1, j)[0]) : false) {
neighbors++;
continue;
}
if(j-1 >= 0 ? (0 != clusterImprint.get(i, j-1)[0]) : false) {
neighbors++;
continue;
}
}
}
}
coherency += (neighbors / (double) clusterColors.size()) * percentage;
clusterImprint.release();
}
}
return coherency;
}
private List<CentroidCluster<ColorPixelContainer>> clusterizeColors(Mat img, Mat mask, Rect roi, int clusterNumber) {
KMeansPlusPlusClusterer<ColorPixelContainer> kmeansClusterer = new KMeansPlusPlusClusterer<ColorPixelContainer>(clusterNumber);
MultiKMeansPlusPlusClusterer<ColorPixelContainer> mkmeansClusterer = new MultiKMeansPlusPlusClusterer<>(kmeansClusterer, 1);
List<ColorPixelContainer> points = new LinkedList<ColorPixelContainer>();
for(int i=roi.y; i<roi.y+roi.height; i+= yStepSize) {
for(int j=roi.x; j<roi.x+roi.width; j+= xStepSize) {
if(mask.get(i, j)[0] == 255) {
double[] pixel = img.get(i, j);
points.add(new ColorPixelContainer(new double[] {pixel[0], pixel[1], pixel[2]}));
}
}
}
return mkmeansClusterer.cluster(points);
}
private List<CentroidCluster<ColorPixelContainer>> clusterizeColors(Mat img, Rect roi, int clusterNumber) {
KMeansPlusPlusClusterer<ColorPixelContainer> kmeansClusterer = new KMeansPlusPlusClusterer<ColorPixelContainer>(clusterNumber);
MultiKMeansPlusPlusClusterer<ColorPixelContainer> mkmeansClusterer = new MultiKMeansPlusPlusClusterer<>(kmeansClusterer, 1);
List<ColorPixelContainer> points = new LinkedList<ColorPixelContainer>();
for(int i=roi.y; i<roi.y+roi.height; i+= yStepSize) {
for(int j=roi.x; j<roi.x+roi.width; j+= xStepSize) {
double[] pixel = img.get(i, j);
points.add(new ColorPixelContainer(new double[] {pixel[0], pixel[1], pixel[2]}));
}
}
return mkmeansClusterer.cluster(points);
}
@Override
public String toString() {
String result = "[";
for(DominantColor dColors : dominantColors) {
result += dColors.toString() + ", ";
}
result += spatialCoherency + "]";
return result;
}
public String toTsvCsvString(String sep) {
String result = "";
for(DominantColor dColors : dominantColors) {
result += dColors.toTsvCsvString(sep) + sep;
}
result += spatialCoherency;
return result;
}
}

View File

@@ -0,0 +1,206 @@
package ru.delkom07.improc.color.descriptors;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
/**
* Глобальная цветовая гистограмма.
* Цветовое пространство разбивается на трехмерный куб, размера D*D*D (D - dimension).
* Размер бина равен 256/D.
* В каждый бин записывается доля пикселей, попавших в него из изображения (n/count).
* @author KomyshevEG
*
*/
public class GlobalColorHistogram {
private double[][][] gch; // GlobalColorHistogram
private int ch1RangeSize = 256; // Default channel 1 range size
private int ch2RangeSize = 256; // Default channel 2 range size
private int ch3RangeSize = 256; // Default channel 3 range size
private int dimension;
private float ch1BinSize; // Channel 1 bin size
private float ch2BinSize; // Channel 2 bin size
private float ch3BinSize; // Channel 3 bin size
private boolean calculated = false;
public GlobalColorHistogram(int dimension) {
this.dimension = dimension;
ch1BinSize = ch1RangeSize/(float)dimension;
ch2BinSize = ch2RangeSize/(float)dimension;
ch3BinSize = ch3RangeSize/(float)dimension;
gch = new double[dimension][dimension][dimension];
}
public GlobalColorHistogram(int dimension, int channel1RangeSize, int channel2RangeSize, int channel3RangeSize) {
this.dimension = dimension;
this.ch1RangeSize = channel1RangeSize;
this.ch2RangeSize = channel2RangeSize;
this.ch3RangeSize = channel3RangeSize;
ch1BinSize = channel1RangeSize/(float)dimension;
ch2BinSize = channel2RangeSize/(float)dimension;
ch3BinSize = channel3RangeSize/(float)dimension;
gch = new double[dimension][dimension][dimension];
}
public GlobalColorHistogram calculate(Mat img) {
long pixelsAmount = 0;
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double[] pix = img.get(i, j);
gch[(int)(pix[0]/ch1BinSize)][(int)(pix[1]/ch2BinSize)][(int)(pix[2]/ch3BinSize)]++;
pixelsAmount++;
}
}
for(int i=0; i<dimension; i++) {
for(int j=0; j<dimension; j++) {
for(int k=0; k<dimension; k++) {
gch[i][j][k] = gch[i][j][k] / ((double)pixelsAmount);
}
}
}
calculated = true;
return this;
}
public GlobalColorHistogram calculate(Mat img, Mat mask) {
long pixelsAmount = 0;
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double[] pix = img.get(i, j);
if((int)pix[0]/ch1BinSize >= 8) {
System.out.println("FFF");
}
if(mask.get(i, j)[0] != 0) {
gch[(int)(pix[0]/ch1BinSize)][(int)(pix[1]/ch2BinSize)][(int)(pix[2]/ch3BinSize)]++;
pixelsAmount++;
}
}
}
for(int i=0; i<dimension; i++) {
for(int j=0; j<dimension; j++) {
for(int k=0; k<dimension; k++) {
gch[i][j][k] = gch[i][j][k] / ((double)pixelsAmount);
}
}
}
calculated = true;
return this;
}
public double[][][] getGCH() {
if(!calculated) {
throw new RuntimeException("Descriptor was not calculated");
}
return gch;
}
public double[] getFlattenGCH() {
if(!calculated) {
throw new RuntimeException("Descriptor was not calculated");
}
double[] flatten = new double[dimension*dimension*dimension];
for(int i=0; i<dimension; i++) {
for(int j=0; j<dimension; j++) {
for(int k=0; k<dimension; k++) {
flatten[i + j*dimension + k*dimension*dimension] = gch[i][j][k]; // i - R; j - G; k - B
}
}
}
return flatten;
}
public String toTsvCsvString(String sep) {
double[] flattenGCH = getFlattenGCH();
String result = "";
for(int i=0; i<flattenGCH.length; i++) {
double val = flattenGCH[i];
if(i != flattenGCH.length-1) {
result = result + Double.toString(val) + sep;
} else {
result = result + Double.toString(val);
}
}
return result;
}
public double[] getBinColor(int i, int j, int k) {
double toMiddleShiftCh1 = (ch1RangeSize/dimension)/2;
double toMiddleShiftCh2 = (ch2RangeSize/dimension)/2;
double toMiddleShiftCh3 = (ch3RangeSize/dimension)/2;
return new double[] {(ch1RangeSize/dimension)*i + toMiddleShiftCh1, (ch2RangeSize/dimension)*j + toMiddleShiftCh2, (ch3RangeSize/dimension)*k + toMiddleShiftCh3};
}
public double[] getBinColor(int n) {
// n = i + j*dimension + k*dimension*dimension
int k = (int)(n / (dimension*dimension));
int j = (int)(n - k*dimension*dimension) / dimension;
int i = n - k*dimension*dimension - j*dimension;
return getBinColor(i, j, k);
}
public Mat markPixels(Mat img, Mat mask, int binNumber) {
Mat markMask = Mat.zeros(mask.size(), CvType.CV_8UC1);
double ch1HalfOfBinSize = ch1BinSize/2.0;
double ch2HalfOfBinSize = ch2BinSize/2.0;
double ch3HalfOfBinSize = ch3BinSize/2.0;
double[] binColor = getBinColor(binNumber);
double[] from = new double[] {binColor[0]-ch1HalfOfBinSize, binColor[1]-ch2HalfOfBinSize, binColor[2]-ch3HalfOfBinSize};
double[] to = new double[] {binColor[0]+ch1HalfOfBinSize, binColor[1]+ch2HalfOfBinSize, binColor[2]+ch3HalfOfBinSize};
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double maskPix = mask.get(i, j)[0];
if(0 != maskPix) {
double[] pix = img.get(i, j);
if((pix[0]>from[0] && pix[0]<to[0]) && (pix[1]>from[1] && pix[1]<to[1]) && (pix[2]>from[2] && pix[2]<to[2])) {
markMask.put(i, j, new double[] {255});
}
}
}
}
return markMask;
}
}

View File

@@ -0,0 +1,337 @@
package ru.delkom07.improc.color.descriptors;
import org.opencv.core.Mat;
import org.opencv.core.Rect;
public class MeanColorDescriptor {
private double rejectionSigma;
private double[] meanColor;
private double sigma;
private double[] meanColorExceptDevPixels;
public MeanColorDescriptor(double rejectionSigma) {
this.rejectionSigma = rejectionSigma;
}
/**
* Calculation of mean color except pixels with greater deviation then sigma * rejectionSigma.
* @param img - source image.
* @param mask - mask.
* @return - mean color.
*/
public MeanColorDescriptor calculate(Mat img, Mat mask) {
meanColor = calculateMeanColor(img, mask);
sigma = calculateSigma(img, mask, meanColor);
meanColorExceptDevPixels = calculateMeanColorExceptDevPixels(img, mask, meanColor, sigma, rejectionSigma);
return this;
}
public MeanColorDescriptor calculate(Mat img, Rect rect) {
meanColor = calculateMeanColor(img, rect);
sigma = calculateSigma(img, rect, meanColor);
meanColorExceptDevPixels = calculateMeanColorExceptDevPixels(img, rect, meanColor, sigma, rejectionSigma);
return this;
}
/**
* Calculation of mean color except pixels with greater deviation then sigma * rejectionSigma.
* Without mask.
* @param img - source image.
* @param mask - mask.
* @return - mean color.
*/
public MeanColorDescriptor calculate(Mat img) {
meanColor = calculateMeanColor(img);
sigma = calculateSigma(img, meanColor);
meanColorExceptDevPixels = calculateMeanColorExceptDevPixels(img, meanColor, sigma, rejectionSigma);
return this;
}
// Calculation
private double[] calculateMeanColor(Mat img, Mat mask) {
double[] meanColor = new double[3];
int counter = 0;
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double[] maskPix = mask.get(i, j);
if(0 != maskPix[0]) {
double[] imgPix = img.get(i, j);
meanColor[0] += imgPix[0];
meanColor[1] += imgPix[1];
meanColor[2] += imgPix[2];
counter++;
}
}
}
meanColor[0] = meanColor[0] / ((double)counter);
meanColor[1] = meanColor[1] / ((double)counter);
meanColor[2] = meanColor[2] / ((double)counter);
return meanColor;
}
private double[] calculateMeanColor(Mat img, Rect rect) {
double[] meanColor = new double[3];
int counter = 0;
for(int i=rect.y; i<rect.y+rect.height; i++) {
for(int j=rect.x; j<rect.x+rect.width; j++) {
double[] imgPix = img.get(i, j);
meanColor[0] += imgPix[0];
meanColor[1] += imgPix[1];
meanColor[2] += imgPix[2];
counter++;
}
}
meanColor[0] = meanColor[0] / ((double)counter);
meanColor[1] = meanColor[1] / ((double)counter);
meanColor[2] = meanColor[2] / ((double)counter);
return meanColor;
}
// without mask
private double[] calculateMeanColor(Mat img) {
double[] meanColor = new double[3];
int counter = 0;
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double[] imgPix = img.get(i, j);
meanColor[0] += imgPix[0];
meanColor[1] += imgPix[1];
meanColor[2] += imgPix[2];
counter++;
}
}
meanColor[0] = meanColor[0] / ((double)counter);
meanColor[1] = meanColor[1] / ((double)counter);
meanColor[2] = meanColor[2] / ((double)counter);
return meanColor;
}
private double calculateSigma(Mat img, Mat mask, double[] meanColor) {
double meanDeviationSquare = 0;
int counter = 0;
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double[] maskPix = mask.get(i, j);
if(0 != maskPix[0]) {
double[] imgPix = img.get(i, j);
meanDeviationSquare += deviationSquare(meanColor, imgPix);
counter++;
}
}
}
meanDeviationSquare = meanDeviationSquare / ((double)counter);
return Math.sqrt(meanDeviationSquare);
}
private double calculateSigma(Mat img, Rect rect, double[] meanColor) {
double meanDeviationSquare = 0;
int counter = 0;
for(int i=rect.y; i<rect.y+rect.height; i++) {
for(int j=rect.x; j<rect.x+rect.width; j++) {
double[] imgPix = img.get(i, j);
meanDeviationSquare += deviationSquare(meanColor, imgPix);
counter++;
}
}
meanDeviationSquare = meanDeviationSquare / ((double)counter);
return Math.sqrt(meanDeviationSquare);
}
// without mask
private double calculateSigma(Mat img, double[] meanColor) {
double meanDeviationSquare = 0;
int counter = 0;
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double[] imgPix = img.get(i, j);
meanDeviationSquare += deviationSquare(meanColor, imgPix);
counter++;
}
}
meanDeviationSquare = meanDeviationSquare / ((double)counter);
return Math.sqrt(meanDeviationSquare);
}
private double[] calculateMeanColorExceptDevPixels(Mat img, Mat mask, double[] meanColor, double sigma, double rejectionSigma) {
double[] meanColorExceptDevPixels = new double[3];
int counter = 0;
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double[] maskPix = mask.get(i, j);
if(0 != maskPix[0]) {
double[] imgPix = img.get(i, j);
double deviation = colorDistance(meanColor, imgPix);
if(deviation < sigma * rejectionSigma) {
meanColorExceptDevPixels[0] += imgPix[0];
meanColorExceptDevPixels[1] += imgPix[1];
meanColorExceptDevPixels[2] += imgPix[2];
counter++;
}
}
}
}
meanColorExceptDevPixels[0] = meanColorExceptDevPixels[0] / ((double)counter);
meanColorExceptDevPixels[1] = meanColorExceptDevPixels[1] / ((double)counter);
meanColorExceptDevPixels[2] = meanColorExceptDevPixels[2] / ((double)counter);
return meanColorExceptDevPixels;
}
private double[] calculateMeanColorExceptDevPixels(Mat img, Rect rect, double[] meanColor, double sigma, double rejectionSigma) {
double[] meanColorExceptDevPixels = new double[3];
int counter = 0;
for(int i=rect.y; i<rect.y + rect.height; i++) {
for(int j=rect.x; j<rect.x+rect.width; j++) {
double[] imgPix = img.get(i, j);
double deviation = colorDistance(meanColor, imgPix);
if(deviation < sigma * rejectionSigma) {
meanColorExceptDevPixels[0] += imgPix[0];
meanColorExceptDevPixels[1] += imgPix[1];
meanColorExceptDevPixels[2] += imgPix[2];
counter++;
}
}
}
meanColorExceptDevPixels[0] = meanColorExceptDevPixels[0] / ((double)counter);
meanColorExceptDevPixels[1] = meanColorExceptDevPixels[1] / ((double)counter);
meanColorExceptDevPixels[2] = meanColorExceptDevPixels[2] / ((double)counter);
return meanColorExceptDevPixels;
}
// without mask
private double[] calculateMeanColorExceptDevPixels(Mat img, double[] meanColor, double sigma, double rejectionSigma) {
double[] meanColorExceptDevPixels = new double[3];
int counter = 0;
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double[] imgPix = img.get(i, j);
double deviation = colorDistance(meanColor, imgPix);
if(deviation < sigma * rejectionSigma) {
meanColorExceptDevPixels[0] += imgPix[0];
meanColorExceptDevPixels[1] += imgPix[1];
meanColorExceptDevPixels[2] += imgPix[2];
counter++;
}
}
}
meanColorExceptDevPixels[0] = meanColorExceptDevPixels[0] / ((double)counter);
meanColorExceptDevPixels[1] = meanColorExceptDevPixels[1] / ((double)counter);
meanColorExceptDevPixels[2] = meanColorExceptDevPixels[2] / ((double)counter);
return meanColorExceptDevPixels;
}
private double deviationSquare(double[] color1, double[] color2) {
return ((color1[0]-color2[0])*(color1[0]-color2[0]) +
(color1[1]-color2[1])*(color1[1]-color2[1]) +
(color1[2]-color2[2])*(color1[2]-color2[2]));
}
private double colorDistance(double[] color1, double[] color2) {
return Math.sqrt((color1[0]-color2[0])*(color1[0]-color2[0]) +
(color1[1]-color2[1])*(color1[1]-color2[1]) +
(color1[2]-color2[2])*(color1[2]-color2[2]));
}
// Getters
public double[] getMeanColorWithoutSigma() {
return meanColorExceptDevPixels;
}
public double getSigma() {
return sigma;
}
public String toTsvCsvString(String sep) {
return meanColorExceptDevPixels[0] + sep + meanColorExceptDevPixels[1] + sep + meanColorExceptDevPixels[2];
}
public double[] toArray() {
return new double[] {meanColorExceptDevPixels[0], meanColorExceptDevPixels[1], meanColorExceptDevPixels[2]};
}
@Deprecated
public static void markPixels(Mat img, Mat mask, int channel, double from, double to, double[] color) {
for(int i=0; i<img.rows(); i++) {
for(int j=0; j<img.cols(); j++) {
double maskPix = mask.get(i, j)[0];
if(0 != maskPix) {
double[] pix = img.get(i, j);
if(pix[channel] > from && pix[channel] < to) {
img.put(i, j, color);
}
}
}
}
}
}

View File

@@ -0,0 +1,177 @@
package ru.delkom07.improc.color.descriptors;
import java.util.Arrays;
import org.opencv.core.Mat;
import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt;
import org.opencv.core.Rect;
import org.opencv.imgproc.Imgproc;
import ru.delkom07.util.Pair;
public class SimpleHistograms {
private Pair<Integer, Integer> range;
private int histogramSize;
private Mat histGray = new Mat();
private Mat histB = new Mat();
private Mat histG = new Mat();
private Mat histR = new Mat();
private ImgType imgType;
private boolean calculated = false;
public enum ImgType {
GRAY, COLOR
}
public SimpleHistograms(Pair<Integer, Integer> range, int histSize, ImgType imgType) {
this.range = range;
this.histogramSize = histSize;
this.imgType = imgType;
}
public SimpleHistograms calculate(Mat img, Mat mask, Rect roi) {
if(null == mask) {
mask = new Mat();
}
if(null != roi) {
img = img.submat(roi);
mask = mask.submat(roi);
}
MatOfFloat ranges = new MatOfFloat(range.getLeft(), range.getRight());
MatOfInt histSize = new MatOfInt(histogramSize);
if(imgType == ImgType.COLOR) {
Imgproc.calcHist(Arrays.asList(img), new MatOfInt(0), mask, histB, histSize, ranges);
Imgproc.calcHist(Arrays.asList(img), new MatOfInt(1), mask, histG, histSize, ranges);
Imgproc.calcHist(Arrays.asList(img), new MatOfInt(2), mask, histR, histSize, ranges);
} else {
Imgproc.calcHist(Arrays.asList(img), new MatOfInt(), new Mat(), histGray, histSize, ranges);
}
calculated = true;
return this;
}
public Mat getGrayHistogramMat() {
if(imgType != ImgType.GRAY || !calculated) {
throw new RuntimeException("Gray histogram mat isn't calculated");
}
return histGray;
}
public Mat getBlueColorHistogramMat() {
if(imgType != ImgType.COLOR || !calculated) {
throw new RuntimeException("Color histogram mats isn't calculated");
}
return histB;
}
public Mat getGreenColorHistogramMat() {
if(imgType != ImgType.COLOR || !calculated) {
throw new RuntimeException("Color histogram mats isn't calculated");
}
return histG;
}
public Mat getRedColorHistogramMat() {
if(imgType != ImgType.COLOR || !calculated) {
throw new RuntimeException("Color histogram mats isn't calculated");
}
return histR;
}
public double[] getGrayHistogram() {
if(imgType != ImgType.GRAY || !calculated) {
throw new RuntimeException("Gray histogram mat isn't calculated");
}
double[] dataGray = new double[histGray.cols()*histGray.rows()];
int k = 0;
for(int i=0; i<histGray.rows(); i++) {
for(int j=0; j<histGray.cols(); j++) {
dataGray[k] = histGray.get(i, j)[0];
k++;
}
}
return dataGray;
}
public double[] getBlueHistogram() {
if(imgType != ImgType.COLOR || !calculated) {
throw new RuntimeException("Color histogram mats isn't calculated");
}
double[] dataB = new double[histB.cols()*histB.rows()];
int k = 0;
for(int i=0; i<histB.rows(); i++) {
for(int j=0; j<histB.cols(); j++) {
dataB[k] = histB.get(i, j)[0];
k++;
}
}
return dataB;
}
public double[] getGreenHistogram() {
if(imgType != ImgType.COLOR || !calculated) {
throw new RuntimeException("Color histogram mats isn't calculated");
}
double[] dataG = new double[histG.cols()*histG.rows()];
int k = 0;
for(int i=0; i<histG.rows(); i++) {
for(int j=0; j<histG.cols(); j++) {
dataG[k] = histG.get(i, j)[0];
k++;
}
}
return dataG;
}
public double[] getRedHistogram() {
if(imgType != ImgType.COLOR || !calculated) {
throw new RuntimeException("Color histogram mats isn't calculated");
}
double[] dataR = new double[histR.cols()*histR.rows()];
int k = 0;
for(int i=0; i<histR.rows(); i++) {
for(int j=0; j<histR.cols(); j++) {
dataR[k] = histR.get(i, j)[0];
k++;
}
}
return dataR;
}
}

View File

@@ -0,0 +1,473 @@
package ru.delkom07.improc.geometry;
import java.util.List;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import ru.delkom07.util.Pair;
/**
*
* @author Komyshev.
* v.0.1
*/
public class GeometricFunctions {
public static double euclideanDistance(double[] p1, double[] p2) {
double sum = 0;
for(int i=0; i<p1.length || i<p2.length; i++) {
sum += Math.pow(p1[i]-p2[i], 2);
}
return Math.sqrt(sum);
}
public static double distance(Point p1, Point p2) {
double dx = Math.abs(p1.x-p2.x);
double dy = Math.abs(p1.y-p2.y);
return Math.sqrt(dx*dx+dy*dy);
}
public static double length(Vector vector) {
double dx = Math.abs(vector.x1()-vector.x2());
double dy = Math.abs(vector.y1()-vector.y2());
return Math.sqrt(dx*dx+dy*dy);
}
public static Point getMiddlePoint(Point p1, Point p2) {
return new Point((p1.x+p2.x)/2, (p1.y+p2.y)/2);
}
public static double[] getMassCenter(MatOfPoint contour) {
// mass center
double[] massCenter = new double[2];
massCenter[0] = 0.0;
massCenter[1] = 0.0;
int count = 0;
for(int i=0; i<contour.rows(); i++) {
for(int j=0; j<contour.cols(); j++) {
double[] point = contour.get(i, j);
massCenter[0] += point[0];
massCenter[1] += point[1];
count++;
}
}
massCenter[0] = massCenter[0]/(double)count;
massCenter[1] = massCenter[1]/(double)count;
return massCenter;
}
public static Vector[] getMainComponents(MatOfPoint contour) {
double[] massCenter = getMassCenter(contour);
Vector minAxis = new Vector(massCenter[0], massCenter[1], contour.get(0, 0)[0], contour.get(0, 0)[1]);
double[] minInertionMoment = new double[2];
minInertionMoment[0] = Double.MAX_VALUE;
minInertionMoment[1] = Double.MAX_VALUE;
for(int i=0; i<contour.rows(); i++) {
for(int j=0; j<contour.cols(); j++) {
Vector potentialAxis = new Vector(massCenter[0], massCenter[1], contour.get(i, j)[0], contour.get(i, j)[1]);
double A = potentialAxis.y1()-potentialAxis.y2();
double B = potentialAxis.x2()-potentialAxis.x1();
double C = potentialAxis.x1()*potentialAxis.y2() - potentialAxis.x2()*potentialAxis.y1();
double[] inertionMoment = new double[2];
for(int ii=0; ii<contour.rows(); ii++) {
for(int jj=0; jj<contour.cols(); jj++) {
double[] point = contour.get(ii, jj);
double[] pointOnAxis = new double[2];
pointOnAxis[0] = ( B*(B*point[0]-A*point[1] )-A*C) / (A*A+B*B);
pointOnAxis[1] = ( A*(-1*B*point[0]+A*point[1] )-B*C) / (A*A+B*B);
double d = Math.abs(A*point[0] + B*point[1] + C) / Math.sqrt(A*A + B*B);
inertionMoment[0] += d*d*pointOnAxis[0];
inertionMoment[1] += d*d*pointOnAxis[1];
}
}
if(Math.sqrt(inertionMoment[0]*inertionMoment[0]+inertionMoment[1]*inertionMoment[1])
< Math.sqrt(minInertionMoment[0]*minInertionMoment[0]+minInertionMoment[1]*minInertionMoment[1])) {
minAxis = potentialAxis;
minInertionMoment[0] = inertionMoment[0];
minInertionMoment[1] = inertionMoment[1];
}
}
}
return new Vector[]{minAxis, new Vector(minAxis.x1(), minAxis.y1(), minAxis.x1()+minAxis.y(), minAxis.y1()-minAxis.x())};
}
public static Point[] findMaxDistantPoints(Point[] pointArr) {
Point maxP1 = pointArr[0];
Point maxP2 = pointArr[0];
double maxDistance = 0;
for(Point p1: pointArr) {
for(Point p2: pointArr) {
if(GeometricFunctions.distance(p1, p2) > maxDistance) {
maxP1 = p1;
maxP2 = p2;
maxDistance = GeometricFunctions.distance(p1, p2);
}
}
}
return new Point[]{maxP1, maxP2};
}
/**
* Вернить две точки лежащие наиболее близко к данной линии и нормали справа и слева от нормали соответственно.
* Функция сначала находит список точек претендентов: наиболее близкие к линии точки по pointBufferSize справа и слева.
* После этого для каждой стороны выбирается наиболее близкая точка к нормали.
* @param listOfPoints - списко точек.
* @param line - данная линия.
* @param normal - нормаль.
* @return две точки лежащие наиболее близко к данной линии и нормали справа и слева от нормали соответственно.
*/
/*
public static Point[] getTwoPointThatLayOnGivenLine(List<Point> listOfPoints, Vector line, Vector normal) {
short pointBufferSize = 5;
// наборы правых и левых точек контура относительно normal, лежащие близко к line
List<Pair<Point, Double>> leftPoints = new LinkedList<Pair<Point, Double>>();
List<Pair<Point, Double>> rightPoints = new LinkedList<Pair<Point, Double>>();
for(Point point : listOfPoints) {
double xDiff = (line.x2() - line.x1());
double yDiff = (line.y2() - line.y1());
xDiff = xDiff == 0 ? 0.0001 : xDiff; // STUB
yDiff = yDiff == 0 ? 0.0001 : yDiff; // STUB
// расстояние от точки до прямой line
double closenessToLine = Math.abs((point.x - line.x1())/xDiff - (point.y - line.y1())/yDiff);
//double closenessToLine = Math.abs((point.x - line.x1())/(line.x2() - line.x1()) - (point.y - line.y1())/(line.y2()-line.y1()));
// position relative to the normal
// (y1-y2)*x+(x2-x1)*y+(x1*y2-x2*y1) > or < 0
double position = (normal.y1()-normal.y2())*point.x + (normal.x2()-normal.x1()) * point.y + (normal.x1()*normal.y2()-normal.x2()*normal.y1());
// компаратор для пары Точка-Значение. сравнивает пары по значению.
Comparator<Pair<Point, Double>> comparator = new Comparator<Pair<Point, Double>>() {
@Override
public int compare(Pair<Point, Double> arg0, Pair<Point, Double> arg1) {
if(arg0.getValue() > arg1.getValue()) {
return 1;
}
if(arg0.getValue() < arg1.getValue()) {
return -1;
}
return 0;
}
};
if(position <= 0) { // right position
if(rightPoints.size() < pointBufferSize && !Double.isNaN(closenessToLine) && Double.isFinite(closenessToLine)) {
rightPoints.add(new Pair<Point, Double>(point, closenessToLine));
if(rightPoints.size() == pointBufferSize) {
rightPoints.sort(comparator);
}
} else {
for(int j=0; j<rightPoints.size(); j++) {
Pair<Point, Double> rightPoint = rightPoints.get(j);
if(closenessToLine < rightPoint.getValue()) {
rightPoints.add(rightPoints.indexOf(rightPoint), new Pair<Point, Double>(point, closenessToLine));
rightPoints.remove(rightPoints.size()-1);
break;
}
}
}
} else { // left position
if(leftPoints.size() < pointBufferSize && !Double.isNaN(closenessToLine) && Double.isFinite(closenessToLine)) {
leftPoints.add(new Pair<Point, Double>(point, closenessToLine));
if(leftPoints.size() == pointBufferSize) {
leftPoints.sort(comparator);
}
} else {
for(int j=0; j<leftPoints.size(); j++) {
Pair<Point, Double> leftPoint = leftPoints.get(j);
if(closenessToLine < leftPoint.getValue()) {
leftPoints.add(leftPoints.indexOf(leftPoint), new Pair<Point, Double>(point, closenessToLine));
leftPoints.remove(leftPoints.size()-1);
break;
}
}
}
}
}
Point rightPoint = null;
double rightBestDist = Double.MAX_VALUE;
for(Pair<Point, Double> rightPointPair : rightPoints) {
Point point = rightPointPair.getKey();
double xDiff = (normal.x2() - normal.x1());
double yDiff = (normal.y2() - normal.y1());
xDiff = xDiff == 0 ? 0.0001 : xDiff;
yDiff = yDiff == 0 ? 0.0001 : yDiff;
double closenessToNormal = Math.abs((point.x - normal.x1())/xDiff - (point.y - normal.y1())/yDiff);
//double closenessToNormal = Math.abs((point.x - normal.x1())/(normal.x2() - normal.x1()) - (point.y - normal.y1())/(normal.y2()-normal.y1()));
if(closenessToNormal < rightBestDist) {
rightPoint = point;
rightBestDist = closenessToNormal;
}
}
Point leftPoint = null;
double leftBestDist = Double.MAX_VALUE;
for(Pair<Point, Double> leftPointPair : leftPoints) {
Point point = leftPointPair.getKey();
double xDiff = (normal.x2() - normal.x1());
double yDiff = (normal.y2()- normal.y1());
xDiff = xDiff == 0 ? 0.0001 : xDiff;
yDiff = yDiff == 0 ? 0.0001 : yDiff;
double closenessToNormal = Math.abs((point.x - normal.x1())/xDiff - (point.y - normal.y1())/yDiff);
//double closenessToNormal = Math.abs((point.x - normal.x1())/(normal.x2() - normal.x1()) - (point.y - normal.y1())/(normal.y2()-normal.y1()));
if(closenessToNormal < leftBestDist) {
leftPoint = point;
leftBestDist = closenessToNormal;
}
}
return new Point[]{leftPoint, rightPoint};
}
*/
public static Point[] getTwoPointThatLayOnGivenLine(List<Point> listOfPoints, Vector line, Vector normal) {
// правые и левые точки контура относительно normal, лежащие близко к line
Pair<Point, Double> rightPoint = null;
Pair<Point, Double> leftPoint = null;
for(Point point : listOfPoints) {
double xDiff = (line.x2() - line.x1());
double yDiff = (line.y2() - line.y1());
//xDiff = xDiff == 0 ? 0.0001 : xDiff; // STUB
//yDiff = yDiff == 0 ? 0.0001 : yDiff; // STUB
// расстояние от точки до прямой line
//double closenessToLine = Math.abs((point.x - line.x1())/xDiff - (point.y - line.y1())/yDiff);
double closenessToLineX = xDiff != 0 ? (point.x - line.x1())/xDiff : 0;
double closenessToLineY = yDiff != 0 ? (point.y - line.y1())/yDiff : 0;
double closenessToLine = Math.abs(closenessToLineX - closenessToLineY);
//double closenessToLine = Math.abs((point.x - line.x1())/(line.x2() - line.x1()) - (point.y - line.y1())/(line.y2()-line.y1()));
// position relative to the normal
// (y1-y2)*x+(x2-x1)*y+(x1*y2-x2*y1) > or < 0
double position = (normal.y1()-normal.y2())*point.x + (normal.x2()-normal.x1()) * point.y + (normal.x1()*normal.y2()-normal.x2()*normal.y1());
if(Double.isNaN(closenessToLine) || !Double.isFinite(closenessToLine)) {
continue;
} else {
if(position <= 0) { // right position
if(null == rightPoint) {
rightPoint = new Pair<Point, Double>(point, closenessToLine);
} else {
if(closenessToLine < rightPoint.getRight()) {
rightPoint = new Pair<Point, Double>(point, closenessToLine);
}
}
} else {
if(null == leftPoint) {
leftPoint = new Pair<Point, Double>(point, closenessToLine);
} else {
if(closenessToLine < leftPoint.getRight()) {
leftPoint = new Pair<Point, Double>(point, closenessToLine);
}
}
}
}
}
return new Point[]{leftPoint != null ? leftPoint.getLeft() : null, rightPoint != null ? rightPoint.getLeft() : null};
}
public static Vector normalize(Vector vector) {
double length = length(vector);
double newX = vector.x()/length;
double newY = vector.y()/length;
return new Vector(vector.x1(), vector.y1(), vector.x1()+newX, vector.y1()+newY);
}
/**
* Calculate of angle between given lines in degrees.
* To calculate radians: *pi/180.
* @param vect1 - first vector.
* @param vect2 - second vector.
* @return angle between given lines.
*/
public static double angle(Vector vect1, Vector vect2) {
return Math.acos((vect1.x()*vect2.x()+vect1.y()*vect2.y())/(vect1.length()*vect2.length())) * 180 / Math.PI;
}
/**
* Angle between two lines (without mean directions).
* @param vect1 - first line.
* @param vect2 - second line.
* @return Angle between two lines.
*/
public static double absLineAngle(Vector vect1, Vector vect2) {
double val = Math.abs(Math.acos((vect1.x()*vect2.x()+vect1.y()*vect2.y())/(vect1.length()*vect2.length())) * 180 / Math.PI);
return val < 90 ? val : 180 - val;
}
/**
* Calculate of angle between given lines in degrees.
* To calculate radians: *pi/180.
* @param line1 - first line.
* @param line2 - second line.
* @return angle between given lines.
*/
public static double angle(double[] line1, double[] line2) {
double vect1X = Math.abs(line1[0]-line1[2]); // |x1 - x2|
double vect1Y = Math.abs(line1[1]-line1[3]); // |y1 - y2|
double length1 = vectLenght(vect1X, vect1Y);
double vect2X = Math.abs(line2[0]-line2[2]); // |x1 - x2|
double vect2Y = Math.abs(line2[1]-line2[3]); // |y1 - y2|
double length2 = vectLenght(vect2X, vect2Y);
return Math.acos((vect1X*vect2X+vect1Y*vect2Y)/(length1*length2)) * 180 / Math.PI;
}
/**
* Calculate of angle between given vector in degrees.
* To calculate radians: *pi/180.
* @param vect1 - first vector.
* @param vect2 - second vector.
* @return angle between given lines.
*/
public static double vectorAngle(double[] vect1, double[] vect2) {
double vect1X = vect1[2]-vect1[0]; // x2 - x1
double vect1Y = vect1[3]-vect1[1]; // y2 - y1
double length1 = vectLenght(vect1X, vect1Y);
double vect2X = vect2[2]-vect2[0]; // x2 - x1
double vect2Y = vect2[3]-vect2[1]; // y2 - y1
double length2 = vectLenght(vect2X, vect2Y);
return Math.acos((vect1X*vect2X+vect1Y*vect2Y)/(length1*length2)) * 180 / Math.PI;
}
public static double vectLenght(double x, double y) {
return Math.sqrt(x*x+y*y);
}
public static Point[] getTopLeftAndBottomRight(List<Point> points) {
double minX = Integer.MAX_VALUE;
double minY = Integer.MAX_VALUE;
double maxX = -1;
double maxY = -1;
for(Point point : points) {
if(point.x < minX) {
minX = point.x;
}
if(point.y < minY) {
minY = point.y;
}
if(point.x > maxX) {
maxX = point.x;
}
if(point.y > maxY) {
maxY = point.y;
}
}
return new Point[] {new Point(minX, minY), new Point(maxX, maxY)};
}
}
/*
*
* public static Point[] getTwoPointThatLayOnGivenLine_(List<Point> listOfPoints, Vector line) {
Point point1 = null;
Point point2 = null;
Point prevPoint = listOfPoints.get(0);
boolean rele = false;
double minEstimation = Double.MAX_VALUE;
for(Point point : listOfPoints) {
double estimation = Math.abs((point.x - line.x1())/(line.x2() - line.x1()) - (point.y - line.y1())/(line.y2()-line.y1()));
if(estimation < minEstimation && distance(prevPoint, point)>10) { // ?????? distance it is not good decision
minEstimation= estimation;
if(rele) {
point1 = point;
} else {
point2 = point;
}
rele = !rele;
prevPoint = point;
}
}
return new Point[]{point1, point2};
}
*/

View File

@@ -0,0 +1,103 @@
package ru.delkom07.improc.geometry;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
public class Vector {
private Double x;
private Double y;
private Double x1;
private Double y1;
private Double x2;
private Double y2;
boolean coordinateAssigned = false;
public Vector(double x, double y) {
this.x = x;
this.y = y;
coordinateAssigned = false;
}
public Vector(double x1, double y1, double x2, double y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.x = x2-x1;
this.y = y2-y1;
coordinateAssigned = true;
}
public Double x() {
return x;
}
public Double y() {
return y;
}
public Double x1() {
if(!coordinateAssigned)
throw new RuntimeException("Coordinate not assigned");
return x1;
}
public Double y1() {
if(!coordinateAssigned)
throw new RuntimeException("Coordinate not assigned");
return y1;
}
public Double x2() {
if(!coordinateAssigned)
throw new RuntimeException("Coordinate not assigned");
return x2;
}
public Double y2() {
if(!coordinateAssigned)
throw new RuntimeException("Coordinate not assigned");
return y2;
}
public void setPoint1(Point p1) {
coordinateAssigned = true;
this.x1 = p1.x;
this.y1 = p1.y;
this.x = x2-x1;
this.y = y2-y1;
}
public void setPoint2(Point p2) {
coordinateAssigned = true;
this.x2 = p2.x;
this.y2 = p2.y;
this.x = x2-x1;
this.y = y2-y1;
}
public Double length() {
return Math.sqrt(x*x+y*y);
}
public void draw(Mat img, Scalar color) {
Imgproc.line(img, new Point(x1(), y1()), new Point(x2(), y2()), color, 1);
//Imgproc.circle(img, new Point(x2(), y2()), 10, color, -1);
}
}

View File

@@ -0,0 +1,369 @@
package ru.delkom07.improc.texture;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.opencv.core.Mat;
/**
* Realization of Gray level co-occurrence matrix.
*
* В текущей версии вычисляется матрица для всех основных принципиальных направлений (в определении матрицы GLCM это параметр, как и d).
*
* @author Komyshev
*/
public class GLCM {
int Nx; // ширина изображения (параметр матрицы GLCM)
int Ny; // высота изображения (параметр матрицы GLCM)
int Ng = 256; // квантификация уровней серого (параметр матрицы GLCM)
int d = 1; // расстояние (параметр матрицы GLCM)
int[][] P; // ненормализованная матрица GLCM
double[][] p; // нормализованная матрица GLCM
boolean calculated = false;
// вычисление
// все вычисляемые направления (параметр матрицы GLCM)
static final int directions[][] = {
{0, 1}, {0, -1}, // by vertical
{1, 0}, {-1, 0}, // by horizontal
{1, 1}, {-1, -1}, // 1 diagonal
{1, -1}, {-1, 1}, // 2 diagonal
};
public GLCM() {}
public GLCM(int d) {
this.d = d;
}
public GLCM(int Ng, int d) {
this.Ng = Ng;
this.d = d;
}
public GLCM calculate(Mat gray) {
P = new int[Ng][Ng];
p = new double[Ng][Ng];
Nx = gray.cols();
Ny = gray.rows();
initP(gray);
// (3*) нормализация
int C = 2*Nx*(Ny - 1) + 2*Ny*(Nx - 1) + 4*(Nx - 1)*(Ny - 1);
normalizeP(C);
calculated = true;
return this;
}
/**
* (1*) Инициализировать матрицу P (ненормализованную) - посчитать число соотвутствующих пар пикселей.
* @param gray
*/
private void initP(Mat gray) {
for(int x=0; x<Nx; x++) {
for(int y=0; y<Ny; y++) {
int iPix = (int) gray.get(y, x)[0];
// loop for directions
for(int directInd=0; directInd<directions.length; directInd++) {
int nextX = x + d*directions[directInd][0];
int nextY = y + d*directions[directInd][1];
if(nextX >= 0 && nextX < Nx && nextY >= 0 && nextY < Ny) {
int jPix = (int) gray.get(nextY, nextX)[0];
P[iPix][jPix]++;
}
}
}
}
}
/*
* (2*) Вычислить нормализованную GLCM матрицу.
*/
void normalizeP(int C) {
for(int i=0; i<Ng; i++) {
for(int j=0; j<Ng; j++) {
p[i][j] = P[i][j] / (double) C;
}
}
}
/*
* Получить не нормализованную GLCM матрицу (числа сооветствующих пар пикселей).
*/
public int[][] getNonNormilizedP() {
initCheck();
return P;
}
/*
* Получить нормализованную GLCM матрицу.
*/
public double[][] getNormilizedP() {
initCheck();
return p;
}
void initCheck() {
if(!calculated) {
throw new RuntimeException("The descriptor is not initialized");
}
}
///////////////////////////////////////////////////////////
// Texture characteristics based on GLCM.
///////////////////////////////////////////////////////////
/**
* (4*) Mean.
* @return
*/
public double mean() {
initCheck();
double mu = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
mu += i*p[i-1][j-1];
}
}
return mu;
}
/**
* (5*) Variance.
* @return
*/
public double variance() {
initCheck();
double sigma2 = 0;
double mu = mean();
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
sigma2 += Math.pow(i-mu, 2) * p[i-1][j-1];
}
}
return sigma2;
}
/**
* (6*) Uniformity.
* @return
*/
public double uniformity() {
initCheck();
double res = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
res += p[i-1][j-1] * p[i-1][j-1];
}
}
return res;
}
/**
* (7*) Entropy.
* @return
*/
public double entropy() {
initCheck();
double res = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
if(0 != p[i-1][j-1]) // *quantification check
res -= p[i-1][j-1] * Math.log(p[i-1][j-1]);
}
}
return res;
}
/**
* (8*) Maximum probability.
* @return
*/
public double maxProbability() {
initCheck();
double max = p[0][0];
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
if(p[i-1][j-1] > max) {
max = p[i-1][j-1];
}
}
}
return max;
}
/**
* (9*) Correlation.
* @return
*/
public double correlation() {
initCheck();
double res = 0;
double mu = mean();
double sigma2 = variance();
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
res += ( ( (i-mu)*(j-mu) )/sigma2 ) * p[i-1][j-1];
}
}
return res;
}
/**
* (10*) Homogeneity.
* @return
*/
public double homogeneity() {
initCheck();
double res = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
res += ( 1 / (1 + Math.pow(i-j, 2) ) ) * p[i-1][j-1];
}
}
return res;
}
/**
* (11*) Inertia.
* @return
*/
public double inertia() {
initCheck();
double res = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
res += Math.pow(i-j, 2) * p[i-1][j-1];
}
}
return res;
}
/**
* (12*) Cluster shade.
* @return
*/
public double clusterShade() {
initCheck();
double res = 0;
double mu = mean();
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
res += Math.pow(i + j - 2*mu, 3) * p[i-1][j-1];
}
}
return res;
}
/**
* (13*) Cluster prominance.
* @return
*/
public double clusterProminance() {
initCheck();
double res = 0;
double mu = mean();
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Ng; j++) {
res += Math.pow(i + j - 2*mu, 4) * p[i-1][j-1];
}
}
return res;
}
// == Вывод ==
public static String[] ChNames() {
return new String[] {
"mean", "variance",
"uniformity", "entropy", "maxProbability", "correlation",
"homogeneity", "inertia", "clusterShade", "clusterProminance"
};
}
/**
* Вывести дескриптор как массив характеристик.
* @return массив характеристик дескриптора.
*/
public double[] toArray() {
return new double[] {
mean(), variance(),
uniformity(), entropy(), maxProbability(), correlation(),
homogeneity(), inertia(), clusterShade(), clusterProminance()
};
}
/**
* Вывести дескриптор как строку.
* @return строковое представление дескриптора.
*/
public String toTsvCsvString(String sep) {
List<Double> list = Arrays.stream(toArray()).boxed().collect(Collectors.toList());
String joined = list.stream()
.map(d -> d.toString())
.collect(Collectors.joining(sep));
return joined;
}
}

View File

@@ -0,0 +1,419 @@
package ru.delkom07.improc.texture;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.opencv.core.Mat;
/**
* Realization of Gray level run length matrix.
*
* @author Komyshev
*/
public class GLRM {
int Ng = 256; // квантификация уровней серого
int Nr = 4; // максимальная длина серий уровней серого, определяемая в данной матрице
int[][] q; // матрица GLRM
int R; // сумма матрицы GLRM
boolean calculated = false; // флаг инициализации дескриптора для проверки перед запросом характеристик или матриц
/**
* Основной конструктор.
* @param maxLength - максимальная учитываемая длина серии.
*/
public GLRM(int maxLength) {
this.Nr = maxLength;
}
/**
* Конструктор с указанием квантификации уровня серого.
* @param Ng - квантификация уровней серого.
* @param maxLength - максимальная учитываемая длина серии.
*/
public GLRM(int Ng, int maxLength) {
this.Ng = Ng;
this.Nr = maxLength;
}
public GLRM calculate(Mat gray) {
q = new int[Ng][Nr];
countRunsOn_0(gray);
countRunsOn_90(gray);
countRunsOn_135(gray);
countRunsOn_45(gray);
R = calculateR();
calculated = true;
return this;
}
/**
* Посчитать сериии по горизонтали (слева на право).
* @param gray - изображение в градациях серого.
*/
private void countRunsOn_0(Mat gray) {
for(int y=0; y<gray.rows(); y++) { // for rows
int curLvl = (int) gray.get(y, 0)[0]; // base point
int curLen = 1;
for(int x=1;; x++) { // for cols
if(x >= gray.cols()) {
count(curLvl, curLen);
break;
}
int nextPoint = (int) gray.get(y, x)[0];
if(curLvl != nextPoint) {
count(curLvl, curLen);
// flash
curLvl = nextPoint;
curLen = 1;
} else {
curLen++;
}
} // end for cols
} // end for rows
}
/**
* Посчитать серии по вертикали (сверху вниз).
* @param gray - изображение в градациях серого.
*/
private void countRunsOn_90(Mat gray) {
for(int x=0; x<gray.cols(); x++) { // for cols
int curLvl = (int) gray.get(0, x)[0]; // base point
int curLen = 1;
for(int y=1;; y++) { // for rows
if(y>=gray.rows()) {
count(curLvl, curLen);
break;
}
int nextPoint = (int) gray.get(y, x)[0];
if(curLvl != nextPoint) {
count(curLvl, curLen);
// flash
curLvl = nextPoint;
curLen = 1;
} else {
curLen++;
}
} // end for rows
} // end for cols
}
/**
* По диагонали слева-сверху -> вправо-вниз
* @param gray
*/
private void countRunsOn_135(Mat gray) {
for(int c=0; c<gray.cols(); c++) { // for cols
int curX = c;
int curY = 0;
countDiag(gray, curX, curY, 1, 1);
}
for(int r=1; r<gray.rows(); r++) { // for rows
int curX = 0;
int curY = r;
countDiag(gray, curX, curY, 1, 1);
}
}
/**
* По диагонали слева-снизу -> вправо-вверх
* @param gray
*/
private void countRunsOn_45(Mat gray) {
for(int r=0; r<gray.rows(); r++) { // for rows
int curX = 0;
int curY = r;
countDiag(gray, curX, curY, 1, -1);
}
for(int c=1; c<gray.cols(); c++) { // for cols
int curX = c;
int curY = gray.rows()-1;
countDiag(gray, curX, curY, 1, -1);
}
}
/**
* Посчитать серии по диагонали.
* @param gray - изображение в градациях серого.
* @param curX - стартовый пиксель (X).
* @param curY - стартовый пиксель (Y).
* @param dx - смещение по X.
* @param dy - смещение по Y.
*/
private void countDiag(Mat gray, int curX, int curY, int dx, int dy) {
int curLvl = (int) gray.get(curY, curX)[0]; // base point
int curLen = 1;
for(;;) { // for diag
int nextX = curX + dx*curLen;
int nextY = curY + dy*curLen;
// выход за пределы изображения
if(nextX < 0 || nextX >= gray.cols() || nextY < 0 || nextY >= gray.rows()) {
count(curLvl, curLen);
break;
}
int nextLvl = (int) gray.get(nextY, nextX)[0];
if(curLvl != nextLvl) { // уровень серого изменился
count(curLvl, curLen);
// flash
curLvl = nextLvl;
curLen = 1;
curX = nextX;
curY = nextY;
} else { // новый уровень серого
curLen++;
}
}
}
void count(int lvl, int len) {
int lenPos = len <= Nr ? len-1 : Nr-1;
q[lvl][lenPos]++;
}
/**
* Вычислить делитель нормализации.
* По сути просто суммирует матрицу q.
* (20*)
* @return делитель нормализации.
*/
int calculateR() {
int res = 0;
for(int i=0; i<Ng; i++) {
for(int j=0; j<Nr; j++) {
res += q[i][j];
}
}
return res;
}
/**
* Получить матрицу GLRM.
* @return матрица GLRM
*/
public int[][] getQ() {
initCheck();
return q;
}
/**
* Триггер на обращение к методам неинициализированного класса.
*/
private void initCheck() {
if(!calculated) {
throw new RuntimeException("The descriptor is not initialized");
}
}
///////////////////////////////////////////////////////////
// Texture characteristics based on GLRM.
///////////////////////////////////////////////////////////
/**
* (14*)
* @return
*/
public double shortRun() {
initCheck();
double res = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Nr; j++) {
res += q[i-1][j-1]/(j*j);
}
}
return res / (double) R;
}
/**
* (15*)
* @return
*/
public double longRun() {
initCheck();
double res = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Nr; j++) {
res += (j*j) * q[i-1][j-1];
}
}
return res / (double) R;
}
/**
* (16*)
* @return
*/
public double grayLevelNonUniformity() {
initCheck();
double sumI = 0;
for(int i=1; i<=Ng; i++) {
double sumJ = 0;
for(int j=1; j<=Nr; j++) {
sumJ += q[i-1][j-1];
}
sumI += sumJ*sumJ;
}
return sumI / (double) R;
}
/**
* (17*)
* @return
*/
public double runLengthNonUniformity() {
initCheck();
double sumJ = 0;
for(int j=1; j<=Nr; j++) {
double sumI = 0;
for(int i=1; i<=Ng; i++) {
sumI += q[i-1][j-1];
}
sumJ += sumI*sumI;
}
return sumJ / (double) R;
}
/**
* (18*)
* @return
*/
public double runRatio() {
initCheck();
double res = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Nr; j++) {
res += j*q[i-1][j-1];
}
}
return (double) R / res;
}
/**
* (19*)
* @return
*/
public double entropy() {
initCheck();
double res = 0;
for(int i=1; i<=Ng; i++) {
for(int j=1; j<=Nr; j++) {
if(0 != q[i-1][j-1]) // *quantification check
res += q[i-1][j-1] * Math.log(q[i-1][j-1]);
}
}
return res / (double) R;
}
// == Вывод ==
public static String[] ChNames() {
return new String[] {
"shortRun", "longRun", "grayLevelNonUniformity", "runLengthNonUniformity", "runRatio", "entropy"
};
}
/**
* Вывести дескриптор как массив характеристик.
* @return массив характеристик дескриптора.
*/
public double[] toArray() {
return new double[] {
shortRun(), longRun(), grayLevelNonUniformity(), runLengthNonUniformity(), runRatio(), entropy()
};
}
/**
* Вывести дескриптор как строку.
* @return строковое представление дескриптора.
*/
public String toTsvCsvString(String sep) {
List<Double> list = Arrays.stream(toArray()).boxed().collect(Collectors.toList());
String joined = list.stream()
.map(d -> d.toString())
.collect(Collectors.joining(sep));
return joined;
}
}

View File

@@ -0,0 +1,83 @@
package ru.delkom07.improc.texture;
import org.opencv.core.Mat;
/**
* Realization of Gray level co-occurrence matrix with masks.
* При вычислении учитываются только пиксели соотвествующие не нулевым пикселя маски.
* При встрече нулевого пикселя маски рассматривается такое же поведение, как выход за границы изображения.
*
* В текущей версии вычисляется матрица для всех основных принципиальных направлений (в определении матрицы GLCM это параметр, как и d).
*
* @author Komyshev
*/
public class MaskedGLCM extends GLCM {
Mat mask;
public MaskedGLCM() {
super();
}
public MaskedGLCM(int d) {
super(d);
}
public MaskedGLCM(int Ng, int d) {
super(Ng, d);
}
public MaskedGLCM calculate(Mat gray, Mat mask) {
P = new int[Ng][Ng];
p = new double[Ng][Ng];
Nx = gray.cols();
Ny = gray.rows();
initP(gray, mask);
// (3*) нормализация
int C = 2*Nx*(Ny - 1) + 2*Ny*(Nx - 1) + 4*(Nx - 1)*(Ny - 1);
normalizeP(C);
calculated = true;
return this;
}
/**
* (1*) Инициализировать матрицу P (ненормализованную) - посчитать число соотвутствующих пар пикселей.
* @param gray
*/
void initP(Mat gray, Mat mask) {
for(int x=0; x<Nx; x++) {
for(int y=0; y<Ny; y++) {
if(0 != mask.get(y, x)[0]) { // only mask pixels
int iPix = (int) gray.get(y, x)[0];
// loop for directions
for(int directInd=0; directInd<directions.length; directInd++) {
int nextX = x + d*directions[directInd][0];
int nextY = y + d*directions[directInd][1];
if(nextX >= 0 && nextX < Nx && nextY >= 0 && nextY < Ny) {
if(0 != mask.get(nextY, nextX)[0]) { // only mask pixels
int jPix = (int) gray.get(nextY, nextX)[0];
P[iPix][jPix]++;
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,207 @@
package ru.delkom07.improc.texture;
import org.opencv.core.Mat;
/**
* Realization of Gray level run length matrix with masks.
* При вычислении учитываются только пиксели соотвествующие не нулевым пикселя маски.
* При встрече нулевого пикселя маски рассматривается такое же поведение, как выход за границы изображения.
* @author Komyshev
*/
public class MaskedGLRM extends GLRM {
/**
* Основной конструктор.
* @param maxLength - максимальная учитываемая длина серии.
*/
public MaskedGLRM(int maxLength) {
super(maxLength);
}
/**
* Конструктор с указанием квантификации уровня серого.
* @param Ng - квантификация уровней серого.
* @param maxLength - максимальная учитываемая длина серии.
*/
public MaskedGLRM(int Ng, int maxLength) {
super(Ng, maxLength);
}
public MaskedGLRM calculate(Mat gray, Mat mask) {
q = new int[Ng][Nr];
countRunsOn_0(gray, mask);
countRunsOn_90(gray, mask);
countRunsOn_135(gray, mask);
countRunsOn_45(gray, mask);
R = calculateR();
calculated = true;
return this;
}
/**
* Посчитать сериии по горизонтали (слева на право).
* @param gray - изображение в градациях серого.
*/
private void countRunsOn_0(Mat gray, Mat mask) {
for(int y=0; y<gray.rows(); y++) { // for rows
if(0 == mask.get(y, 0)[0]) { continue; } // only mask pixels
int curLvl = (int) gray.get(y, 0)[0]; // base point
int curLen = 1;
for(int x=1;; x++) { // for cols
if(x >= gray.cols()) {
count(curLvl, curLen);
break;
}
int nextPoint = (int) gray.get(y, x)[0];
int nextMaskPoint = (int) mask.get(y, x)[0]; // only mask pixels
if(0 == nextMaskPoint || curLvl != nextPoint) { // only mask pixels
count(curLvl, curLen);
// flash
curLvl = nextPoint;
curLen = 1;
} else {
curLen++;
}
} // end for cols
} // end for rows
}
/**
* Посчитать серии по вертикали (сверху вниз).
* @param gray - изображение в градациях серого.
*/
private void countRunsOn_90(Mat gray, Mat mask) {
for(int x=0; x<gray.cols(); x++) { // for cols
if(0 == mask.get(0, x)[0]) { continue; } // only mask pixels
int curLvl = (int) gray.get(0, x)[0]; // base point
int curLen = 1;
for(int y=1;; y++) { // for rows
if(y>=gray.rows()) {
count(curLvl, curLen);
break;
}
int nextPoint = (int) gray.get(y, x)[0];
int nextMaskPoint = (int) mask.get(y, x)[0]; // only mask pixels
if(0 == nextMaskPoint || curLvl != nextPoint) { // only mask pixels
count(curLvl, curLen);
// flash
curLvl = nextPoint;
curLen = 1;
} else {
curLen++;
}
} // end for rows
} // end for cols
}
/**
* По диагонали слева-сверху -> вправо-вниз
* @param gray
*/
private void countRunsOn_135(Mat gray, Mat mask) {
for(int c=0; c<gray.cols(); c++) { // for cols
int curX = c;
int curY = 0;
countDiag(gray, mask, curX, curY, 1, 1);
}
for(int r=1; r<gray.rows(); r++) { // for rows
int curX = 0;
int curY = r;
countDiag(gray, mask, curX, curY, 1, 1);
}
}
/**
* По диагонали слева-снизу -> вправо-вверх
* @param gray
*/
private void countRunsOn_45(Mat gray, Mat mask) {
for(int r=0; r<gray.rows(); r++) { // for rows
int curX = 0;
int curY = r;
countDiag(gray, mask, curX, curY, 1, -1);
}
for(int c=1; c<gray.cols(); c++) { // for cols
int curX = c;
int curY = gray.rows()-1;
countDiag(gray, mask, curX, curY, 1, -1);
}
}
/**
* Посчитать серии по диагонали.
* @param gray - изображение в градациях серого.
* @param mask - маска изображения.
* @param curX - стартовый пиксель (X).
* @param curY - стартовый пиксель (Y).
* @param dx - смещение по X.
* @param dy - смещение по Y.
*/
private void countDiag(Mat gray, Mat mask, int curX, int curY, int dx, int dy) {
if(0 == mask.get(curY, curX)[0]) return; // only mask pixels
int curLvl = (int) gray.get(curY, curX)[0]; // base point
int curLen = 1;
for(;;) { // for diag
int nextX = curX + dx*curLen;
int nextY = curY + dy*curLen;
// выход за пределы изображения
if(nextX < 0 || nextX >= gray.cols() || nextY < 0 || nextY >= gray.rows()) {
count(curLvl, curLen);
break;
}
int nextLvl = (int) gray.get(nextY, nextX)[0];
int nextMaskLvl = (int) mask.get(nextY, nextX)[0];
if(0 == nextMaskLvl || curLvl != nextLvl) { // уровень серого изменился
count(curLvl, curLen);
// flash
curLvl = nextLvl;
curLen = 1;
curX = nextX;
curY = nextY;
} else { // новый уровень серого
curLen++;
}
}
}
}

View File

@@ -0,0 +1,83 @@
package ru.delkom07.pendent;
import org.opencv.core.Mat;
import org.opencv.core.Rect;
public class DiscreteCosineTransform_direct_wrong_realization {
private Mat transformed;
private static final double oneDivSqrt2 = 1.0/Math.sqrt(2.0);
public void transform(Mat img, Mat transformed, boolean inv) {
this.transformed = transformed;
int blockSize = 8;
int xBlocks = img.cols()/blockSize;
int yBlocks = img.rows()/blockSize;
for(int xBlock=0; xBlock<xBlocks; xBlock++) {
for(int yBlock=0; yBlock<yBlocks; yBlock++) {
Rect rect = new Rect(xBlock*blockSize, yBlock*blockSize, blockSize, blockSize);
for(int i=rect.y; i<rect.y+rect.height; i++) {
for(int j=rect.x; j<rect.x+rect.width; j++) {
if(!inv) {
transformed.put(i, j, direct(i, j, rect, img));
} else {
transformed.put(i, j, inverse(i, j, rect, img));
}
}
}
}
}
}
public Mat getTransformedImg() {
return transformed;
}
private double direct(double i, double j, Rect rect, Mat img) {
double val = 0;
double N = rect.height;
double M = rect.width;
for(int y=rect.y; y<rect.y+rect.height; y++) {
for(int x=rect.x; x<rect.x+rect.width; x++) {
val += Math.cos( Math.PI*j*(2*y + 1) / (2*N) ) * Math.cos( Math.PI*i*(2*x + 1) / (2*M)) * img.get(y, x)[0];
}
}
return Math.sqrt(2.0/M) * Math.sqrt(2.0/N) * lamda(i) * lamda(j) * val;
}
private double inverse(double x, double y, Rect rect, Mat img) {
double val = 0;
double N = rect.height;
double M = rect.width;
for(int i=rect.y; i<rect.y+rect.height; i++) {
for(int j=rect.x; j<rect.x+rect.width; j++) {
val += lamda(i) * lamda(j) * Math.cos( Math.PI*j*(2*y + 1.0) / (2*N) ) * Math.cos( Math.PI*i*(2*x + 1.0) / (2*M)) * img.get(i, j)[0];
}
}
return Math.sqrt(2.0/M) * Math.sqrt(2.0/N) * val;
}
private double lamda(double e) {
if(e == 0) {
return oneDivSqrt2;
} else {
return 1.0;
}
}
}

View File

@@ -0,0 +1,14 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package ru.delkom07;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class LibraryTest {
@Test void someLibraryMethodReturnsTrue() {
Library classUnderTest = new Library();
assertTrue(classUnderTest.someLibraryMethod(), "someLibraryMethod should return 'true'");
}
}

View File

@@ -0,0 +1,9 @@
package ru.delkom07;
public class Test {
public static void main(String[] args) {
}
}

View File

@@ -0,0 +1,56 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package ru.delkom07.improc.texture;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Arrays;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import nu.pattern.OpenCV;
/**
* Тестовый класс для Gray level co-occurrence matrix.
* @author Komyshev
*/
class GLCMTest {
@BeforeAll
static void initOpenCV() {
OpenCV.loadLocally();
}
@Test void shouldReturnCorrectMatrix() {
Mat gray = new Mat(new Size(4, 4), CvType.CV_8UC1);
byte[] bytes = new byte[] {
0, 0, 3, 1,
0, 1, 1, 1,
2, 2, 3, 3,
2, 2, 3, 1,
};
gray.put(0, 0, bytes);
GLCM glcm = new GLCM(4, 1).calculate(gray);
int[][] P = glcm.getNonNormilizedP();
int[][] expectedP = {
{6, 4, 2, 1},
{4, 8, 3, 12},
{2, 3, 12, 4},
{1, 12, 4, 6}
};
assertEquals( Arrays.deepToString(P), Arrays.deepToString(expectedP));
}
@AfterAll
static void destroy() {}
}

View File

@@ -0,0 +1,58 @@
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package ru.delkom07.improc.texture;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Arrays;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import nu.pattern.OpenCV;
/**
* Тестовый класс для Gray level run-length matrix.
* @author Komyshev
*/
class GLRMTest {
@BeforeAll
static void initOpenCV() {
OpenCV.loadLocally();
}
@Test void shouldReturnCorrectMatrix() {
Mat gray = new Mat(new Size(4, 4), CvType.CV_8UC1);
byte[] bytes = new byte[] {
0, 0, 3, 1,
0, 1, 1, 1,
2, 2, 3, 3,
2, 2, 3, 1,
};
gray.put(0, 0, bytes);
GLRM glrm = new GLRM(4, 4).calculate(gray);
int[][] q = glrm.getQ();
int[][] expectedQ = {
{6, 3, 0, 0},
{13, 2, 1, 0},
{4, 6, 0, 0},
{10, 3, 0, 0}
};
assertEquals( Arrays.deepToString(expectedQ), Arrays.deepToString(q));
}
@AfterAll
static void destroy() {}
}

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

Some files were not shown because too many files have changed in this diff Show More