Base
This commit is contained in:
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal 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
45
.gitignore
vendored
Normal 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
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
240
gradlew
vendored
Normal 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
91
gradlew.bat
vendored
Normal 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
|
||||
49
mask-color-descriptors/build.gradle
Normal file
49
mask-color-descriptors/build.gradle
Normal 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()
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
*/
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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]++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
@@ -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'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.delkom07;
|
||||
|
||||
public class Test {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
|
||||
3
seed-counter-desktop/README.txt
Normal file
3
seed-counter-desktop/README.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
TODO:
|
||||
Закончить рефакторинг с введением PlanarObject, WritableData и ImageArea интерфейсов.
|
||||
Удалить или пересмотреть различные Main-классы запуска SeedCounter-а.
|
||||
50
seed-counter-desktop/build.gradle
Normal file
50
seed-counter-desktop/build.gradle
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*
|
||||
* This generated file contains a sample Java application project to get you started.
|
||||
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
|
||||
* User Manual available at https://docs.gradle.org/7.5/userguide/building_java_projects.html
|
||||
*/
|
||||
|
||||
plugins {
|
||||
// Apply the application plugin to add support for building a CLI application in Java.
|
||||
id 'application'
|
||||
id 'org.springframework.boot' version '2.7.2'
|
||||
id 'io.spring.dependency-management' version '1.0.12.RELEASE'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
compileJava.options.encoding = 'UTF-8'
|
||||
compileTestJava.options.encoding = 'UTF-8'
|
||||
|
||||
|
||||
repositories {
|
||||
// Use Maven Central for resolving dependencies.
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
|
||||
|
||||
implementation 'commons-io:commons-io:2.6'
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'com.googlecode.json-simple:json-simple:1.1.1'
|
||||
|
||||
implementation("org.openpnp:opencv:3.4.2-0")
|
||||
|
||||
implementation project(":mask-color-descriptors")
|
||||
implementation project(":utils")
|
||||
}
|
||||
|
||||
application {
|
||||
// Define the main class for the application.
|
||||
mainClass = 'org.wheatdb.seedcounter.desktop.DesktopMainWithColorDescriptorsOneThread'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
// Use JUnit Platform for unit tests.
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package org.wheatdb.imgproc.colorchecker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.fileutils.FileUtilities;
|
||||
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
|
||||
import ru.delkom07.workspaces.SimpleWorkspace.OpUnit;
|
||||
import ru.delkom07.workspaces.StatusWorkspace;
|
||||
|
||||
public class ColorCheckerCDCalculation {
|
||||
private static final String SEPARATOR = ";";
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
File inputDir = new File(args[0]);
|
||||
File outputDir = new File(args[1]);
|
||||
|
||||
StatusWorkspace workspace = new StatusWorkspace(inputDir, outputDir, true, FileUtilities.jpgImageFilter, null);
|
||||
List<OpUnit> inputAndOutputs = workspace.getRawOpUnits();
|
||||
|
||||
File ccDataFile = new File(outputDir, "colorcheckersign.csv");
|
||||
File wsDataFile = new File(outputDir, "whitesquaresign.csv");
|
||||
|
||||
|
||||
try (
|
||||
PrintWriter ccWriter = new PrintWriter(ccDataFile);
|
||||
PrintWriter wsWriter = new PrintWriter(wsDataFile);
|
||||
) {
|
||||
ccWriter.print("File name" + SEPARATOR);
|
||||
wsWriter.println("File name" + SEPARATOR);
|
||||
|
||||
printHeader(ccWriter, SEPARATOR);
|
||||
|
||||
System.out.println("Total imgs amount: " + inputAndOutputs.size() + "\n");
|
||||
|
||||
int count = 1;
|
||||
for(OpUnit inputAndOutput : inputAndOutputs) {
|
||||
File inputFile = inputAndOutput.getIn();
|
||||
File outputFile = inputAndOutput.getOut();
|
||||
|
||||
Mat inputImg = null;
|
||||
try {
|
||||
inputImg = Imgcodecs.imread(inputFile.getCanonicalPath());
|
||||
|
||||
System.out.println(count++ + " " + inputFile.getCanonicalPath() + "... ");
|
||||
|
||||
MyColorChecker myColorChecker = new MyColorChecker(inputImg);
|
||||
|
||||
ColorCheckerSign sign = myColorChecker.getSignFrom(inputImg);
|
||||
sign.printColorDescriptors(outputFile, inputFile.getName(), ccWriter, SEPARATOR);
|
||||
|
||||
Quad ccQuad = myColorChecker.getColorChecherQuad();
|
||||
|
||||
Rect whiteSquareRect = new Rect((int)ccQuad.tr().x+100, (int)ccQuad.tr().y-100, 300, 300);
|
||||
printAddWhiteBackgroundSquare(inputFile.getName(), inputImg, whiteSquareRect, wsWriter, SEPARATOR);
|
||||
|
||||
Mat extractedCCImg = sign.getColorCheckerImg();
|
||||
|
||||
sign.drawRects(extractedCCImg);
|
||||
Mat whiteSquare = inputImg.submat(whiteSquareRect);
|
||||
|
||||
|
||||
Mat submat = extractedCCImg.submat(new Rect(0, 0, whiteSquareRect.width, whiteSquareRect.height));
|
||||
whiteSquare.copyTo(submat);
|
||||
|
||||
Imgcodecs.imwrite(outputFile.getCanonicalPath(), inputImg);
|
||||
Imgcodecs.imwrite(outputFile.getCanonicalPath().replace(".jpg", "_cc.png"), extractedCCImg);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("error. File was skipped.");
|
||||
} finally {
|
||||
inputImg.release();
|
||||
}
|
||||
|
||||
ccWriter.flush();
|
||||
wsWriter.flush();
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
fnfe.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static void printAddWhiteBackgroundSquare(String fileName, Mat img, Rect rect, PrintWriter writer, String separator) {
|
||||
ColorCheckerSample addWhiteSquareSample = new ColorCheckerSample(img, rect);
|
||||
|
||||
for(int i=0; i<ColorCheckerSample.PARTS_AMOUNT; i++) {
|
||||
Mat samplePartBGR = addWhiteSquareSample.getSampleParts()[i];
|
||||
|
||||
Mat subMatRGB = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatRGB, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Mat subMatHSV = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatHSV, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat subMatLab = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatLab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
Mat subMatYCrCb = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatYCrCb, Imgproc.COLOR_BGR2YCrCb);
|
||||
|
||||
|
||||
// Mean color descriptor:
|
||||
int rejectionSigma = 3;
|
||||
MeanColorDescriptor meanColorRGB = new MeanColorDescriptor(rejectionSigma).calculate(subMatRGB);
|
||||
MeanColorDescriptor meanColorHSV = new MeanColorDescriptor(rejectionSigma).calculate(subMatHSV);
|
||||
MeanColorDescriptor meanColorLab = new MeanColorDescriptor(rejectionSigma).calculate(subMatLab);
|
||||
MeanColorDescriptor meanColorYCrCb = new MeanColorDescriptor(rejectionSigma).calculate(subMatYCrCb);
|
||||
|
||||
writer.print(fileName + separator);
|
||||
writer.print(meanColorRGB.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorHSV.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorLab.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorYCrCb.toTsvCsvString(separator) + separator);
|
||||
writer.println();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void printHeader(PrintWriter writer, String separator) {
|
||||
|
||||
for(int i=0; i<ColorCheckerSign.PALITRA_HEIGHT*ColorCheckerSign.PALITRA_WIDTH; i++) {
|
||||
int number = i+1;
|
||||
writer.print("RGB_mR_" + number + separator);
|
||||
writer.print("RGB_mG_" + number + separator);
|
||||
writer.print("RGB_mB_" + number + separator);
|
||||
writer.print("RGB_v_" + number + separator);
|
||||
}
|
||||
|
||||
for(int i=0; i<ColorCheckerSign.PALITRA_HEIGHT*ColorCheckerSign.PALITRA_WIDTH; i++) {
|
||||
int number = i+1;
|
||||
writer.print("HSV_mH_" + number + separator);
|
||||
writer.print("HSV_mS_" + number + separator);
|
||||
writer.print("HSV_mV_" + number + separator);
|
||||
writer.print("HSV_v_" + number + separator);
|
||||
}
|
||||
|
||||
for(int i=0; i<ColorCheckerSign.PALITRA_HEIGHT*ColorCheckerSign.PALITRA_WIDTH; i++) {
|
||||
int number = i+1;
|
||||
writer.print("Lab_mL_" + number + separator);
|
||||
writer.print("Lab_ma_" + number + separator);
|
||||
writer.print("Lab_mb_" + number + separator);
|
||||
writer.print("Lab_v_" + number + separator);
|
||||
}
|
||||
|
||||
for(int i=0; i<ColorCheckerSign.PALITRA_HEIGHT*ColorCheckerSign.PALITRA_WIDTH; i++) {
|
||||
int number = i+1;
|
||||
writer.print("YCrCb_mY_" + number + separator);
|
||||
writer.print("YCrCb_mCr_" + number + separator);
|
||||
writer.print("YCrCb_mCb_" + number + separator);
|
||||
writer.print("YCrCb_v_" + number + separator);
|
||||
}
|
||||
|
||||
writer.println();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.wheatdb.imgproc.colorchecker;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Size;
|
||||
|
||||
class ColorCheckerSample {
|
||||
private static final int SPLIT = 4;
|
||||
public static final int PARTS_AMOUNT = SPLIT * SPLIT;
|
||||
|
||||
private Rect[] samplePartsRects = new Rect[PARTS_AMOUNT];
|
||||
private Mat[] sampleParts = new Mat[PARTS_AMOUNT];
|
||||
|
||||
ColorCheckerSample(Mat img, Rect rect) {
|
||||
for(int i=0; i<SPLIT; i++) {
|
||||
for(int j=0; j<SPLIT; j++) {
|
||||
int xStep = rect.width / SPLIT;
|
||||
int yStep = rect.height / SPLIT;
|
||||
|
||||
int num = i*SPLIT + j;
|
||||
samplePartsRects[num] = new Rect(new Point(rect.tl().x + xStep*j, rect.tl().y + yStep*i), new Size(xStep, yStep));
|
||||
|
||||
Rect roi = samplePartsRects[num];
|
||||
if(roi.x + roi.width > img.cols() || roi.y + roi.height > img.rows()) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
|
||||
sampleParts[num] = img.submat(samplePartsRects[num]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect[] getRects() {
|
||||
return samplePartsRects;
|
||||
}
|
||||
|
||||
Mat[] getSampleParts() {
|
||||
return sampleParts;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
//colors[(i*SPLIT) + j] = getMiddleColor(img, new Point(tl.x + xStep*j, tl.y + yStep*i),
|
||||
// new Point(tl.x + xStep*(j+1), tl.y + yStep*(i+1)));
|
||||
|
||||
private double[] getMiddleColor(Mat img, Point tl, Point br) {
|
||||
double[] middle = new double[] {0, 0, 0};
|
||||
|
||||
int count = 0;
|
||||
for(int x = (int)tl.x; x<br.x; x++) {
|
||||
for(int y = (int)tl.y; y<br.y; y++) {
|
||||
double[] pix = img.get(x, y);
|
||||
middle[0] += pix[0];
|
||||
middle[1] += pix[1];
|
||||
middle[2] += pix[2];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
middle[0] = middle[0] / (double) count;
|
||||
middle[1] = middle[1] / (double) count;
|
||||
middle[2] = middle[2] / (double) count;
|
||||
|
||||
return middle;
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.wheatdb.imgproc.colorchecker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
|
||||
|
||||
/**
|
||||
* ColorCheckerSign -
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class ColorCheckerSign {
|
||||
public static final int PALITRA_WIDTH = 4;
|
||||
public static final int PALITRA_HEIGHT = 6;
|
||||
|
||||
private static final int SAMPLE_WIDTH = 220;
|
||||
private static final int SAMPLE_HEIGHT = 220;
|
||||
|
||||
ColorCheckerSample[] samples = new ColorCheckerSample[PALITRA_WIDTH*PALITRA_HEIGHT]; // 24
|
||||
|
||||
private static Point baseTl = new Point(120, 295);
|
||||
|
||||
private static final Point[][] tlSamplePoints = new Point[][]{
|
||||
new Point[]{
|
||||
new Point(0, 0), new Point(393, 0), new Point(783, 0), new Point(1171, 0)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 396), new Point(393, 396), new Point(783, 396), new Point(1171, 396)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 782), new Point(393, 782), new Point(783, 782), new Point(1171, 782)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 1172), new Point(393, 1172), new Point(783, 1172), new Point(1171, 1172)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 1558), new Point(393, 1558), new Point(783, 1558), new Point(1171, 1558)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(0, 1946), new Point(393, 1946), new Point(783, 1946), new Point(1171, 1946)
|
||||
}
|
||||
};
|
||||
|
||||
// point position correction by baseTl
|
||||
static {
|
||||
for(int i=0; i<PALITRA_HEIGHT; i++) {
|
||||
for(int j=0; j<PALITRA_WIDTH; j++) {
|
||||
tlSamplePoints[i][j] = new Point(tlSamplePoints[i][j].x + baseTl.x, tlSamplePoints[i][j].y + baseTl.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mat colorCheckerImg = null;
|
||||
|
||||
|
||||
public ColorCheckerSign(Mat colorCheckerImg) {
|
||||
this.colorCheckerImg = colorCheckerImg;
|
||||
|
||||
for(int i=0; i<PALITRA_HEIGHT; i++) {
|
||||
for(int j=0; j<PALITRA_WIDTH; j++) {
|
||||
Point tl = tlSamplePoints[i][j];
|
||||
samples[i*PALITRA_WIDTH + j] = new ColorCheckerSample(colorCheckerImg, new Rect(tl, new Point(tl.x+SAMPLE_WIDTH, tl.y+SAMPLE_HEIGHT)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawRects(Mat img) {
|
||||
for(int i=0; i<samples.length; i++) {
|
||||
ColorCheckerSample sample = samples[i];
|
||||
|
||||
Rect[] rects = sample.getRects();
|
||||
for(Rect rect : rects) {
|
||||
Imgproc.rectangle(img,
|
||||
rect.tl(), rect.br(),
|
||||
//new Point(from.x + rect.tl().x, from.y + rect.tl().y),
|
||||
//new Point(from.x + rect.br().x, from.y + rect.br().y),
|
||||
new Scalar(255, 0, 0), 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void printColorDescriptors(File file, String fileName, PrintWriter writer, String separator) {
|
||||
Mat debugRGB = new Mat(new Size(3000, 3500), CvType.CV_8UC3);
|
||||
|
||||
for(int i=0; i<ColorCheckerSample.PARTS_AMOUNT; i++) {
|
||||
|
||||
writer.print(fileName + separator);
|
||||
|
||||
for(ColorCheckerSample sample : samples) {
|
||||
Mat samplePartBGR = sample.getSampleParts()[i];
|
||||
|
||||
Mat subMatRGB = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatRGB, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Mat subMatHSV = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatHSV, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat subMatLab = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatLab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
Mat subMatYCrCb = new Mat();
|
||||
Imgproc.cvtColor(samplePartBGR, subMatYCrCb, Imgproc.COLOR_BGR2YCrCb);
|
||||
|
||||
|
||||
// Mean color descriptor:
|
||||
int rejectionSigma = 3;
|
||||
MeanColorDescriptor meanColorRGB = new MeanColorDescriptor(rejectionSigma).calculate(subMatRGB);
|
||||
MeanColorDescriptor meanColorHSV = new MeanColorDescriptor(rejectionSigma).calculate(subMatHSV);
|
||||
MeanColorDescriptor meanColorLab = new MeanColorDescriptor(rejectionSigma).calculate(subMatLab);
|
||||
MeanColorDescriptor meanColorYCrCb = new MeanColorDescriptor(rejectionSigma).calculate(subMatYCrCb);
|
||||
|
||||
writer.print(meanColorRGB.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorHSV.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorLab.toTsvCsvString(separator) + separator);
|
||||
writer.print(meanColorYCrCb.toTsvCsvString(separator) + separator);
|
||||
|
||||
double[] color = meanColorRGB.getMeanColorWithoutSigma();
|
||||
Rect rect = sample.getRects()[i];
|
||||
Imgproc.rectangle(debugRGB, rect.tl(), rect.br(), new Scalar(color[0], color[1], color[2]), -1);
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
|
||||
Mat debugBGR = new Mat();
|
||||
Imgproc.cvtColor(debugRGB, debugBGR, Imgproc.COLOR_RGB2BGR);
|
||||
try {
|
||||
Imgcodecs.imwrite(file.getCanonicalPath().replace(".jpg", "_mc.png"), debugBGR);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Mat getColorCheckerImg() {
|
||||
return colorCheckerImg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
private final Point[][] tlSamplePoints = new Point[][]{
|
||||
new Point[]{
|
||||
new Point(82, 276), new Point(474, 276), new Point(864, 276), new Point(1253, 276)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 672), new Point(474, 672), new Point(864, 672), new Point(1253, 672)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 1058), new Point(474, 1058), new Point(864, 1058), new Point(1253, 1058)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 1448), new Point(474, 1448), new Point(864, 1448), new Point(1253, 1448)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 1834), new Point(474, 1834), new Point(864, 1834), new Point(1253, 1834)
|
||||
},
|
||||
new Point[]{
|
||||
new Point(82, 2222), new Point(474, 2222), new Point(864, 2222), new Point(1253, 2222)
|
||||
}
|
||||
};
|
||||
*/
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.wheatdb.imgproc.colorchecker;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.features2d.AKAZE;
|
||||
import org.opencv.features2d.DescriptorMatcher;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
import smirnov.colorchecker.ColorChecker;
|
||||
import smirnov.colorchecker.FindColorChecker;
|
||||
import smirnov.colorchecker.MatchingModel;
|
||||
import smirnov.regression.ColorSpace;
|
||||
import smirnov.regression.RegressionFactory;
|
||||
import smirnov.regression.RegressionFactory.Order;
|
||||
import smirnov.regression.RegressionModel;
|
||||
|
||||
public class MyColorChecker {
|
||||
//private static final String REFERENCE_FILE = "src/main/resources/templates/color_checker/reference.png";
|
||||
private static final URL REFERENCE_FILE = ClassLoader.getSystemClassLoader().getResource("templates/color_checker/reference.png");
|
||||
|
||||
private static MatchingModel matchingModel = new MatchingModel(
|
||||
AKAZE.create(), AKAZE.create(),
|
||||
DescriptorMatcher.BRUTEFORCE_HAMMING, 0.80f
|
||||
);
|
||||
|
||||
private static RegressionModel model = RegressionFactory.createModel(Order.THIRD);
|
||||
|
||||
private Mat img = null;
|
||||
private Quad ccQuad = null;
|
||||
private ColorChecker colorChecker = null;
|
||||
private Mat extractedColorChecker = null;
|
||||
|
||||
public MyColorChecker(Mat img) throws IOException {
|
||||
this.img = img;
|
||||
FindColorChecker findColorChecker = new FindColorChecker(REFERENCE_FILE, matchingModel);
|
||||
ccQuad = findColorChecker.findBestFitColorChecker(img);
|
||||
|
||||
extractedColorChecker = ccQuad.getTransformedField(img);
|
||||
colorChecker = new ColorChecker(extractedColorChecker, ccQuad, false, true);
|
||||
}
|
||||
|
||||
|
||||
public Quad getColorChecherQuad() {
|
||||
return ccQuad;
|
||||
}
|
||||
|
||||
|
||||
public void fillColorChecker() {
|
||||
int borderSize = (int) (ccQuad.getBigSideSize()*0.1);
|
||||
MatOfPoint matOfPoint = new MatOfPoint(
|
||||
new Point(ccQuad.tl().x+borderSize, ccQuad.tl().y+borderSize),
|
||||
new Point(ccQuad.tr().x-borderSize, ccQuad.tr().y+borderSize),
|
||||
new Point(ccQuad.br().x-borderSize, ccQuad.br().y-borderSize),
|
||||
new Point(ccQuad.bl().x+borderSize, ccQuad.bl().y-borderSize)
|
||||
);
|
||||
Imgproc.fillConvexPoly(img, matOfPoint, new Scalar(255, 255, 255));
|
||||
}
|
||||
|
||||
public Pair<Mat, Double> calibrateImg() {
|
||||
return colorChecker.calibrate(img, model, ColorSpace.RGB, ColorSpace.RGB);
|
||||
}
|
||||
|
||||
public double getPixelArea() {
|
||||
return colorChecker.pixelArea();
|
||||
}
|
||||
|
||||
public double getPixPerMM() {
|
||||
return 1/Math.sqrt(colorChecker.pixelArea());
|
||||
}
|
||||
|
||||
public ColorCheckerSign getSignFrom(Mat img) {
|
||||
Mat extractedColorChecker = getTransformedField(img, ccQuad);
|
||||
|
||||
return new ColorCheckerSign(extractedColorChecker);
|
||||
}
|
||||
|
||||
|
||||
public Mat getTransformedField(Mat image, Quad ccQuad) {
|
||||
// Define the destination image
|
||||
Mat transformed = new Mat(2791, 1645, image.type());
|
||||
|
||||
// Corners of the destination image
|
||||
Point[] quad_pts = new Point[4];
|
||||
quad_pts[0] = new Point(0, 0);
|
||||
quad_pts[1] = new Point(transformed.cols(), 0);
|
||||
quad_pts[2] = new Point(transformed.cols(), transformed.rows());
|
||||
quad_pts[3] = new Point(0, transformed.rows());
|
||||
|
||||
// Get transformation matrix
|
||||
Mat transmtx = Imgproc.getPerspectiveTransform(new MatOfPoint2f(ccQuad.getPoints()),
|
||||
new MatOfPoint2f(quad_pts));
|
||||
|
||||
// Apply perspective transformation
|
||||
Imgproc.warpPerspective(image, transformed, transmtx, transformed.size());
|
||||
|
||||
return transformed;
|
||||
}
|
||||
|
||||
public Mat getExtractedColorChecker() {
|
||||
return extractedColorChecker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public interface ISaverLoader {
|
||||
void setData(Object obj);
|
||||
|
||||
void save(File toFile) throws IOException;
|
||||
|
||||
Object load(File fromFile) throws IOException, SAXException;
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import ru.delkom07.util.xml.JElement;
|
||||
import ru.delkom07.util.xml.JXMLRepresentable;
|
||||
|
||||
/**
|
||||
* Замер.
|
||||
* Класс представляющий единичный замер зерен с листа бумаги.
|
||||
* @author Komyshev.
|
||||
* v.0.3 (C)
|
||||
*/
|
||||
public class Measurement {
|
||||
private String name = ""; // Название серии измерения (опционально)
|
||||
private Date date; // Дата и время измерения
|
||||
private List<SeedDataContainer> listOfSeedData = new LinkedList<SeedDataContainer>(); // Список полученных данных
|
||||
private File fromSavedImageFile; // измерения получены из сохраненного изображения
|
||||
private SheetFormat sheetFormat; // формат использованного листа бумаги
|
||||
private boolean selected = false; // UI selection
|
||||
private Status status = Status.NONE;
|
||||
|
||||
private int numberOfTrash = -1;
|
||||
private int numberOfSeeds = -1;
|
||||
private int numberOfStuckTogether = -1;
|
||||
private int totalNumberOfStuckTogetherEst = -1;
|
||||
private int totalNumberOfSeedsEst = -1;
|
||||
|
||||
private boolean objsClassified = false;
|
||||
|
||||
|
||||
public enum Status {
|
||||
NONE, CALCULATED, DELAYED, FAILURE;
|
||||
|
||||
public static Status fromString(String str) {
|
||||
if(null == str)
|
||||
return null;
|
||||
|
||||
if(str.equals("NONE"))
|
||||
return NONE;
|
||||
|
||||
if(str.equals("CALCULATED"))
|
||||
return CALCULATED;
|
||||
|
||||
if(str.equals("DELAYED"))
|
||||
return DELAYED;
|
||||
|
||||
if(str.equals("FAILURE"))
|
||||
return FAILURE;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Measurement(){}
|
||||
|
||||
public Measurement(String name, Date date, File fromSavedImageFile, SheetFormat sheetFormat, Status status) {
|
||||
this.name = name;
|
||||
this.date = date;
|
||||
this.fromSavedImageFile = fromSavedImageFile;
|
||||
this.sheetFormat = sheetFormat;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Measurement(Date date, SeedDataContainer dataItem) {
|
||||
this.date = date;
|
||||
listOfSeedData.add(dataItem);
|
||||
}
|
||||
|
||||
public Measurement(String name, Date date, SeedDataContainer dataItem) {
|
||||
this(date, dataItem);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Measurement(Date date, List<SeedDataContainer> listOfData) {
|
||||
this.date = date;
|
||||
this.listOfSeedData = listOfData;
|
||||
}
|
||||
|
||||
public Measurement(String name, Date date, List<SeedDataContainer> listOfData) {
|
||||
this(date, listOfData);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получить название измерения.
|
||||
* @return название измерения.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Задать название измерения.
|
||||
* @param name - новое название измерения.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получить дату измерения.
|
||||
* @return - дата измерения.
|
||||
*/
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получить список данных о зернах.
|
||||
* @return - список данных о зернах.
|
||||
*/
|
||||
public List<SeedDataContainer> getListOfSeedData() {
|
||||
return listOfSeedData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получить список данных о зернах.
|
||||
* @return - список данных о зернах.
|
||||
*/
|
||||
public void setListOfSeedData(List<SeedDataContainer> listOfSeedData) {
|
||||
this.listOfSeedData = listOfSeedData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Добавить в список данных данные о зерне.
|
||||
* @param dataItem - данные о зерне.
|
||||
*/
|
||||
public void addSeedData(SeedDataContainer dataItem) {
|
||||
listOfSeedData.add(dataItem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set selection from UI.
|
||||
* @param selected
|
||||
*/
|
||||
public void setSelected(boolean selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this measurement was selected in UI.
|
||||
* @return
|
||||
*/
|
||||
public boolean isSelected() {
|
||||
return selected;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set delayed flag to measurement. Such measurement will be calculated later.
|
||||
* @param
|
||||
*/
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns is delayed flag enabled.
|
||||
* @return - true if delayed is turn on or false otherwise.
|
||||
*/
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set saved local image file name which is used to obtain this measurement.
|
||||
* It is optional parameter which sets in case if measures was produced in background mode.
|
||||
* @param file - file object.
|
||||
*/
|
||||
public void setFromSavedImageFile(File file) {
|
||||
this.fromSavedImageFile = file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get name of saved local image file which is used to obtain this measurement (can be null).
|
||||
* @return - file object.
|
||||
*/
|
||||
public File getFromSavedImageFile() {
|
||||
return fromSavedImageFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get format of sheet used for measuring seeds.
|
||||
* @return instance of SheetFormat class.
|
||||
*/
|
||||
public SheetFormat getSheetFormat() {
|
||||
return sheetFormat;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set format of sheet used for measuring seeds.
|
||||
* @param sheetFormat - instance of SheetFormat class.
|
||||
*/
|
||||
public void setSheetFormat(SheetFormat sheetFormat) {
|
||||
this.sheetFormat = sheetFormat;
|
||||
}
|
||||
|
||||
|
||||
public int getNumberOfTrash() {
|
||||
return numberOfTrash;
|
||||
}
|
||||
|
||||
public int getNumberOfSeeds() {
|
||||
return numberOfSeeds;
|
||||
}
|
||||
|
||||
public int getNumberOfStuckTogether() {
|
||||
return numberOfStuckTogether;
|
||||
}
|
||||
|
||||
public int getTotalNumberOfStuckTogetherEst() {
|
||||
return totalNumberOfStuckTogetherEst;
|
||||
}
|
||||
|
||||
public int getTotalNumberOfSeedsEst() {
|
||||
return totalNumberOfSeedsEst;
|
||||
}
|
||||
|
||||
public boolean isObjsClassified() {
|
||||
return objsClassified;
|
||||
}
|
||||
|
||||
public void setNumberOfTrash(int numberOfTrash) {
|
||||
this.numberOfTrash = numberOfTrash;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNumberOfSeeds(int numberOfSeeds) {
|
||||
this.numberOfSeeds = numberOfSeeds;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNumberOfStuckTogether(int numberOfStuckTogether) {
|
||||
this.numberOfStuckTogether = numberOfStuckTogether;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTotalNumberOfStuckTogetherEst(int totalNumberOfStuckTogetherObjs) {
|
||||
this.totalNumberOfStuckTogetherEst = totalNumberOfStuckTogetherObjs;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTotalNumberOfSeedsEst(int totalNumberOfSeedsEst) {
|
||||
this.totalNumberOfSeedsEst = totalNumberOfSeedsEst;
|
||||
|
||||
if(numberOfTrash!=-1 && numberOfSeeds!=-1 && numberOfStuckTogether != -1 && totalNumberOfStuckTogetherEst != -1 && totalNumberOfSeedsEst != -1) {
|
||||
objsClassified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Представитель JXML используется для экспорта измерений в XML файлы, а также для передачи в базу данных.
|
||||
* Поля fromSavedImageFile и SheetFormat не экспортируются, т.к. являются метаданными приложения.
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class Representablator implements JXMLRepresentable {
|
||||
@Override
|
||||
public JElement getJXMLRepresentation(Document doc) {
|
||||
JElement root = new JElement(doc, Names.JELEMENT_NAME);
|
||||
root.setAttribute(Names.NAME_NAME, name);
|
||||
root.setAttribute(Names.DATE_NAME, new SimpleDateFormat("MMMM d, yyyy HH:mm:ss", Locale.ENGLISH).format(date));
|
||||
|
||||
for(SeedData seedData : listOfSeedData) {
|
||||
SeedData.Representablator res = seedData.new Representablator();
|
||||
JElement seedDataJEl = res.getJXMLRepresentation(doc);
|
||||
root.appendChild(seedDataJEl);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public void recoveryFromJXML(JElement root) {
|
||||
listOfSeedData = new LinkedList<SeedDataContainer>();
|
||||
|
||||
name = root.getAttribute("name");
|
||||
try {
|
||||
date = new SimpleDateFormat("MMMM d, yyyy HH:mm:ss", Locale.ENGLISH).parse(root.getAttribute("date"));
|
||||
} catch (ParseException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
List<JElement> seedDataJEls = root.getAll(SeedData.Representablator.Names.JELEMENT_NAME);
|
||||
for(JElement seedDataEl : seedDataJEls) {
|
||||
SeedDataContainer seedData = new SeedDataContainer();
|
||||
SeedDataContainer.Representablator repr = seedData.new Representablator();
|
||||
repr.recoveryFromJXML(seedDataEl);
|
||||
|
||||
listOfSeedData.add(seedData);
|
||||
}
|
||||
}
|
||||
|
||||
public class Names {
|
||||
public static final String JELEMENT_NAME = "measurement";
|
||||
private static final String NAME_NAME = "name";
|
||||
private static final String DATE_NAME = "date";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Представитель в TSV используется для экспорта данных в TSV файлы.
|
||||
* Поля fromSavedImageFile и SheetFormat не экспортируются, т.к. являются метаданными приложения.
|
||||
* @author Komyshev.
|
||||
*/
|
||||
public class TVSRepresentablator {
|
||||
public String toTSV() {
|
||||
String caption = "Measurement\t" + name + "\t" + date + "\r\n";
|
||||
String result = caption;
|
||||
|
||||
boolean withHeader = true;
|
||||
for(SeedData seedData : listOfSeedData) {
|
||||
result = result.concat((seedData.new TVSRepresentablator()).toTSV(withHeader));
|
||||
withHeader = false;
|
||||
}
|
||||
|
||||
return result.concat("\r\n\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
|
||||
Measurement other = (Measurement) obj;
|
||||
if (!name.equals(other.name))
|
||||
return false;
|
||||
|
||||
if (!date.equals(other.date))
|
||||
return false;
|
||||
|
||||
if (!listOfSeedData.equals(other.listOfSeedData))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
public class SeedCounterException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SeedCounterException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SeedCounterException(String mes) {
|
||||
super(mes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import ru.delkom07.util.xml.JElement;
|
||||
import ru.delkom07.util.xml.JXMLRepresentable;
|
||||
|
||||
/**
|
||||
* Единица полученных данных об одном зерне в ходе измерения.
|
||||
* @author Komyshev.
|
||||
* v.0.6 (C)
|
||||
*/
|
||||
public class SeedData implements WritableData {
|
||||
private double length;
|
||||
private double width;
|
||||
private double height; // (optional)
|
||||
private double area;
|
||||
private double[] lengthWidthIntersectPoint = new double[2];
|
||||
private double[] massCenter = new double[2];
|
||||
private double DFMCTWLIP; //distance from mass center to width length intersect point
|
||||
|
||||
private double circularity;
|
||||
private double roundness;
|
||||
private double solidity;
|
||||
private double rugosity;
|
||||
private double MIMCCDR; // Maximum Inscribed Circle To Minimum Circumscribed Circle Ratio
|
||||
private double MICTEACR; // Maximum Inscribed Circle To Equivalent Area Circle Ratio
|
||||
|
||||
public enum ObjType {
|
||||
TRASH, SEED, STUCKTOGETHER
|
||||
}
|
||||
private ObjType objType;
|
||||
|
||||
public SeedData() {}
|
||||
|
||||
public SeedData(double length, double width) {
|
||||
this.length = length;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public SeedData(double length, double width, double height) {
|
||||
this.length = length;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public double getLenght() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public double getArea() {
|
||||
return area;
|
||||
}
|
||||
|
||||
public void setArea(double area) {
|
||||
this.area = area;
|
||||
}
|
||||
|
||||
public double[] getLengthWidthIntersectPoint() {
|
||||
return lengthWidthIntersectPoint;
|
||||
}
|
||||
|
||||
public void setLengthWidthIntersectPoint(double[] lengthWidthIntersectPoint) {
|
||||
this.lengthWidthIntersectPoint = lengthWidthIntersectPoint;
|
||||
}
|
||||
|
||||
public double[] getMassCenter() {
|
||||
return massCenter;
|
||||
}
|
||||
|
||||
public void setMassCenter(double[] massCenter) {
|
||||
this.massCenter = massCenter;
|
||||
}
|
||||
|
||||
public double getDistaceFromMassCenterToWidthLengthIntersectPoint() {
|
||||
return DFMCTWLIP;
|
||||
}
|
||||
|
||||
public void setDistaceFromMassCenterToWidthLengthIntersectPoint(double distaceFromMassCenterToWidthLengthIntersectPoint) {
|
||||
this.DFMCTWLIP = distaceFromMassCenterToWidthLengthIntersectPoint;
|
||||
}
|
||||
|
||||
|
||||
// Additional indexies
|
||||
// getters
|
||||
public double getCircularity() {
|
||||
return circularity;
|
||||
}
|
||||
|
||||
public double getRoundness() {
|
||||
return roundness;
|
||||
}
|
||||
|
||||
public double getSolidity() {
|
||||
return solidity;
|
||||
}
|
||||
|
||||
public double getRugosity() {
|
||||
return rugosity;
|
||||
}
|
||||
|
||||
public double getMaxInscribedMinCircumscribedCirclesDiametersRatio() {
|
||||
return MIMCCDR;
|
||||
}
|
||||
|
||||
public double getMaxInscribedCircleToEqAreaCircleRatio() {
|
||||
return MICTEACR;
|
||||
}
|
||||
|
||||
|
||||
// setters
|
||||
public void setCircularityIndex(double circularityIndex) {
|
||||
this.circularity = circularityIndex;
|
||||
}
|
||||
|
||||
public void setRoundness(double roundness) {
|
||||
this.roundness = roundness;
|
||||
}
|
||||
|
||||
public void setSolidity(double solidity) {
|
||||
this.solidity = solidity;
|
||||
}
|
||||
|
||||
public void setRugosity(double rugosity) {
|
||||
this.rugosity = rugosity;
|
||||
}
|
||||
|
||||
public void setMaxInscribedMinCircumscribedCirclesDiametersRatio(double maxInscribedMinCircumscribedCirclesDiametersRatio) {
|
||||
this.MIMCCDR = maxInscribedMinCircumscribedCirclesDiametersRatio;
|
||||
}
|
||||
|
||||
public void setMaxInscribedCircleToEqAreaCircleRatio(double MaxInscribedCircleToEqAreaCircleRatio) {
|
||||
this.MICTEACR = MaxInscribedCircleToEqAreaCircleRatio;
|
||||
}
|
||||
|
||||
public ObjType getObjType() {
|
||||
return objType;
|
||||
}
|
||||
|
||||
public void setObjType(ObjType objType) {
|
||||
this.objType = objType;
|
||||
}
|
||||
|
||||
// public static List<String> headers() {
|
||||
// var columnNames = new ArrayList<String>();
|
||||
//
|
||||
// columnNames.add("Length");
|
||||
// columnNames.add("Width");
|
||||
// columnNames.add("Area");
|
||||
// columnNames.add("Circularity");
|
||||
// columnNames.add("Roundness");
|
||||
// columnNames.add("Rugosity");
|
||||
// columnNames.add("Solidity");
|
||||
//
|
||||
// return columnNames;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Получить данные в списка в том же порядке, что и заголовки.
|
||||
* @return список значений в строковом представлении.
|
||||
*/
|
||||
@Override
|
||||
public List<String> data() {
|
||||
var data = new ArrayList<String>();
|
||||
|
||||
data.add(Double.toString(length));
|
||||
data.add(Double.toString(width));
|
||||
data.add(Double.toString(area));
|
||||
data.add(Double.toString(circularity));
|
||||
data.add(Double.toString(roundness));
|
||||
data.add(Double.toString(rugosity));
|
||||
data.add(Double.toString(solidity));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String separator) {
|
||||
return length + separator + width + separator + area + separator + circularity + separator + roundness + separator + rugosity + separator + solidity + separator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Представитель в JXML. Используется для экспорта данных в файлы формата XML, а также для передачи данных в базу данных.
|
||||
* @author Komyshev.
|
||||
*/
|
||||
class Representablator implements JXMLRepresentable {
|
||||
@Override
|
||||
public JElement getJXMLRepresentation(Document doc) {
|
||||
JElement root = new JElement(doc, Names.JELEMENT_NAME);
|
||||
root.setAttribute(Names.LENGTH_NAME, Double.toString(length));
|
||||
root.setAttribute(Names.WIDTH_NAME, Double.toString(width));
|
||||
//root.setAttribute(Names.HEIGHT_NAME, Double.toString(height));
|
||||
root.setAttribute(Names.AREA_NAME, Double.toString(area));
|
||||
root.setAttribute(Names.DFMCTWLIP_NAME, Double.toString(DFMCTWLIP));
|
||||
|
||||
root.setAttribute(Names.CIRCULARITY_INDEX_NAME, Double.toString(circularity));
|
||||
root.setAttribute(Names.ROUNDNESS_NAME, Double.toString(roundness));
|
||||
root.setAttribute(Names.SOLIDITY_NAME, Double.toString(solidity));
|
||||
root.setAttribute(Names.RUGOSITY_NAME, Double.toString(rugosity));
|
||||
root.setAttribute(Names.MIMCCDR_NAME, Double.toString(MIMCCDR));
|
||||
root.setAttribute(Names.MICTEACR_NAME, Double.toString(MICTEACR));
|
||||
|
||||
JElement lengthWidthIntersectPointJEl = new JElement(doc, Names.LENGTH_WIDTH_INTERSECT_POINT_NAME);
|
||||
root.appendChild(lengthWidthIntersectPointJEl);
|
||||
lengthWidthIntersectPointJEl.setAttribute(Names.X_COORDINATE_NAME, Double.toString(lengthWidthIntersectPoint[0]));
|
||||
lengthWidthIntersectPointJEl.setAttribute(Names.Y_COORDINATE_NAME, Double.toString(lengthWidthIntersectPoint[1]));
|
||||
|
||||
JElement massCenterPointJEl = new JElement(doc, Names.MASS_CENTER_NAME);
|
||||
root.appendChild(massCenterPointJEl);
|
||||
massCenterPointJEl.setAttribute(Names.X_COORDINATE_NAME, Double.toString(massCenter[0]));
|
||||
massCenterPointJEl.setAttribute(Names.Y_COORDINATE_NAME, Double.toString(massCenter[1]));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recoveryFromJXML(JElement root) {
|
||||
length = Double.parseDouble(root.getAttribute(Names.LENGTH_NAME));
|
||||
width = Double.parseDouble(root.getAttribute(Names.WIDTH_NAME));
|
||||
//height = Double.parseDouble(root.getAttribute(Names.HEIGHT_NAME));
|
||||
area = Double.parseDouble(root.getAttribute(Names.AREA_NAME));
|
||||
DFMCTWLIP = Double.parseDouble(root.getAttribute(Names.DFMCTWLIP_NAME));
|
||||
|
||||
circularity = Double.parseDouble(root.getAttribute(Names.CIRCULARITY_INDEX_NAME));
|
||||
roundness = Double.parseDouble(root.getAttribute(Names.ROUNDNESS_NAME));
|
||||
solidity = Double.parseDouble(root.getAttribute(Names.SOLIDITY_NAME));
|
||||
rugosity = Double.parseDouble(root.getAttribute(Names.RUGOSITY_NAME));
|
||||
MIMCCDR = Double.parseDouble(root.getAttribute(Names.MIMCCDR_NAME));
|
||||
MICTEACR = Double.parseDouble(root.getAttribute(Names.MICTEACR_NAME));
|
||||
|
||||
JElement lengthWidthIntersectPointJEl = root.get(Names.LENGTH_WIDTH_INTERSECT_POINT_NAME);
|
||||
lengthWidthIntersectPoint[0] = Double.parseDouble(lengthWidthIntersectPointJEl.getAttribute(Names.X_COORDINATE_NAME));
|
||||
lengthWidthIntersectPoint[1] = Double.parseDouble(lengthWidthIntersectPointJEl.getAttribute(Names.Y_COORDINATE_NAME));
|
||||
|
||||
JElement massCenterPointJEl = root.get(Names.MASS_CENTER_NAME);
|
||||
massCenter[0] = Double.parseDouble(massCenterPointJEl.getAttribute(Names.X_COORDINATE_NAME));
|
||||
massCenter[1] = Double.parseDouble(massCenterPointJEl.getAttribute(Names.Y_COORDINATE_NAME));
|
||||
}
|
||||
|
||||
class Names {
|
||||
public static final String JELEMENT_NAME = "seedData";
|
||||
private static final String LENGTH_NAME = "length";
|
||||
private static final String WIDTH_NAME = "width";
|
||||
//private static final String HEIGHT_NAME = "height";
|
||||
private static final String AREA_NAME = "area";
|
||||
private static final String LENGTH_WIDTH_INTERSECT_POINT_NAME = "lengthWidthIntersectPoint";
|
||||
private static final String MASS_CENTER_NAME = "massCenter";
|
||||
private static final String DFMCTWLIP_NAME = "dfmctwlip";
|
||||
private static final String X_COORDINATE_NAME = "x";
|
||||
private static final String Y_COORDINATE_NAME = "y";
|
||||
|
||||
private static final String CIRCULARITY_INDEX_NAME = "circularityIndex";
|
||||
private static final String ROUNDNESS_NAME = "roundness";
|
||||
private static final String SOLIDITY_NAME = "solidity";
|
||||
private static final String RUGOSITY_NAME = "rugosity";
|
||||
private static final String MIMCCDR_NAME = "mimccdr";
|
||||
private static final String MICTEACR_NAME = "micteacr";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Представитель в TSV. Используется для экспорта данных в файлы формата TSV.
|
||||
* @author Komyshev.
|
||||
*/
|
||||
class TVSRepresentablator {
|
||||
private String header = "Length\tWidth\tArea\tCircularity Index\tRoundness\tSolidity\tRugosity\t" +
|
||||
"Maximum inscribed circle to minimum circumscribed circle ratio\tMaximum inscribed circle to equivalent area circle ratio\t"+
|
||||
"Main axises intersection point X\tMain axises intersection point Y\tMass center X\tMass center Y\tDistance from mass center point to main axises intersection point\n";
|
||||
|
||||
public String toTSV(boolean first) {
|
||||
String output = length+"\t"+width+"\t"+area+"\t"+
|
||||
circularity+"\t"+roundness+"\t"+solidity+"\t"+rugosity+"\t"+MIMCCDR+"\t"+MICTEACR+"\t"+
|
||||
lengthWidthIntersectPoint[0]+"\t"+lengthWidthIntersectPoint[1]+"\t"+
|
||||
massCenter[0]+"\t"+massCenter[1]+"\t"+DFMCTWLIP+"\r\n";
|
||||
|
||||
if(first) {
|
||||
return header.concat(output);
|
||||
} else {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
|
||||
SeedData other = (SeedData) obj;
|
||||
if (length != other.length)
|
||||
return false;
|
||||
|
||||
if (width != other.width)
|
||||
return false;
|
||||
|
||||
//if (height != other.height)
|
||||
// return false;
|
||||
|
||||
if (area != other.area)
|
||||
return false;
|
||||
|
||||
if(circularity != other.circularity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(roundness != other.roundness) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(rugosity != other.rugosity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(solidity != other.solidity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(MIMCCDR != other.MIMCCDR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(MICTEACR != other.MICTEACR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lengthWidthIntersectPoint[0] != other.lengthWidthIntersectPoint[0]
|
||||
|| lengthWidthIntersectPoint[1] != other.lengthWidthIntersectPoint[1])
|
||||
return false;
|
||||
|
||||
if (massCenter[0] != other.massCenter[0]
|
||||
|| massCenter[1] != other.massCenter[1])
|
||||
return false;
|
||||
|
||||
if (DFMCTWLIP != other.DFMCTWLIP)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public class SeedDataContainer extends SeedData implements PlanarObject {
|
||||
private MatOfPoint contour = null;
|
||||
|
||||
public SeedDataContainer() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SeedDataContainer(MatOfPoint contour, double length, double width) {
|
||||
super(length, width);
|
||||
this.contour = contour;
|
||||
}
|
||||
|
||||
public SeedDataContainer(double length, double width) {
|
||||
super(length, width);
|
||||
}
|
||||
|
||||
public SeedDataContainer(double length, double width, double height) {
|
||||
super(length, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatOfPoint getContour() {
|
||||
return contour;
|
||||
}
|
||||
|
||||
public MatOfPoint2f getContourApprox() {
|
||||
Point[] contourArr = contour.toArray();
|
||||
MatOfPoint2f contour2f = new MatOfPoint2f(contourArr);
|
||||
double perimeter = Imgproc.arcLength(contour2f, true);
|
||||
|
||||
MatOfPoint2f approx2f = new MatOfPoint2f();
|
||||
Imgproc.approxPolyDP(contour2f, approx2f, perimeter*0.02, true);
|
||||
|
||||
return approx2f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.opencv.core.Size;
|
||||
|
||||
|
||||
/**
|
||||
* Formats of sheet for detecting seeds.
|
||||
* @author Komyshev
|
||||
* v.1.0
|
||||
*/
|
||||
public enum SheetFormat {
|
||||
A2, A3, A4, A5, // International standard
|
||||
Letter, Legal, // USA standard
|
||||
B4, B5, B6, // Japanese standard
|
||||
USER; // User settings
|
||||
|
||||
Size userSize = new Size(0, 0);
|
||||
|
||||
|
||||
/**
|
||||
* Returns size of this sheet format.
|
||||
* @return size of sheet format represented by this object.
|
||||
*/
|
||||
public Size getSize() {
|
||||
switch(this) {
|
||||
case A2: return new Size(420, 594);
|
||||
case A3: return new Size(297, 420);
|
||||
case A4: return new Size(210, 297);
|
||||
case A5: return new Size(148, 210);
|
||||
|
||||
case Letter: return new Size(216, 279);
|
||||
case Legal: return new Size(216, 356);
|
||||
|
||||
case B4: return new Size(257, 364);
|
||||
case B5: return new Size(182, 257);
|
||||
case B6: return new Size(128, 182);
|
||||
case USER: {
|
||||
if(null==userSize)
|
||||
throw new IllegalArgumentException("User sheet format must be with width and height arguments.");
|
||||
return userSize;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setUserSize(Size userSize) {
|
||||
if(!this.equals(USER))
|
||||
throw new IllegalArgumentException("Only user sheet may be setted with width and height.");
|
||||
this.userSize = userSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns big side size of this sheet format.
|
||||
* @return the big side size of sheet format represented by this object.
|
||||
*/
|
||||
public double getBigSideSize() {
|
||||
Size size = getSize();
|
||||
return size.height > size.width ? size.height: size.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns small side size of this sheet format.
|
||||
* @return the small side size of sheet format represented by this object.
|
||||
*/
|
||||
public double getSmallSideSize() {
|
||||
Size size = getSize();
|
||||
return size.height > size.width ? size.width : size.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parsed instance of SheedFormat from string.
|
||||
* @param string - source string.
|
||||
* @return parsed instance of SheedFormat from string.
|
||||
*/
|
||||
public static SheetFormat fromString(String string) {
|
||||
if(null == string)
|
||||
return null;
|
||||
|
||||
if("" == string)
|
||||
return null;
|
||||
|
||||
if(A2.toString().equals(string))
|
||||
return SheetFormat.A2;
|
||||
if(A3.toString().equals(string))
|
||||
return SheetFormat.A3;
|
||||
if(A4.toString().equals(string))
|
||||
return SheetFormat.A4;
|
||||
if(A5.toString().equals(string))
|
||||
return SheetFormat.A5;
|
||||
|
||||
if(Letter.toString().equals(string))
|
||||
return SheetFormat.Letter;
|
||||
if(Legal.toString().equals(string))
|
||||
return SheetFormat.Legal;
|
||||
|
||||
if(B4.toString().equals(string))
|
||||
return SheetFormat.B4;
|
||||
if(B5.toString().equals(string))
|
||||
return SheetFormat.B5;
|
||||
if(B6.toString().equals(string))
|
||||
return SheetFormat.B6;
|
||||
|
||||
StringTokenizer st = new StringTokenizer(string);
|
||||
if(st.hasMoreTokens())
|
||||
if(st.nextToken().equals("USER")) {
|
||||
double width = Double.parseDouble(st.nextToken());
|
||||
double height = Double.parseDouble(st.nextToken());
|
||||
|
||||
SheetFormat user = SheetFormat.USER;
|
||||
user.setUserSize(new Size(height, width));
|
||||
return user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if(this==USER) {
|
||||
String sizeStr = "";
|
||||
if(null!=userSize) sizeStr = userSize.height+" "+userSize.width;
|
||||
return "USER "+ sizeStr;
|
||||
}
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Данные, предназначенные для сохранения в файлы.
|
||||
*/
|
||||
public interface WritableData {
|
||||
|
||||
/**
|
||||
* Получить данные в виде списка.
|
||||
* @return список значений в строковом представлении.
|
||||
*/
|
||||
public List<String> data();
|
||||
|
||||
/**
|
||||
* Получить данные в виде строки с разделителями.
|
||||
* @param separator - раделители колонок в файлах (точка с запятой, знак табуляции и т.п.).
|
||||
* @return строковое представление данных.
|
||||
*/
|
||||
public String toString(String separator);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import ru.delkom07.util.xml.JElement;
|
||||
import ru.delkom07.util.xml.XMLDOMHandler;
|
||||
|
||||
public class XMLMapSaverLoader implements ISaverLoader {
|
||||
private Map<String, String> data;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void setData(Object data) {
|
||||
this.data = (Map<String, String>) data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(File toFile) throws IOException {
|
||||
Document document = XMLDOMHandler.newDocument();
|
||||
JElement root = new JElement(document, toFile.getName());
|
||||
document.appendChild(root.getElement());
|
||||
|
||||
for(Entry<String, String> entry : data.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
JElement element = new JElement(document, name);
|
||||
element.setTextContent(value);
|
||||
root.appendChild(element);
|
||||
}
|
||||
|
||||
XMLDOMHandler.writeTree(document, toFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> load(File fromFile) throws IOException, SAXException {
|
||||
Map<String, String> data = new HashMap<String, String>();
|
||||
Document document;
|
||||
|
||||
document = XMLDOMHandler.parse(fromFile);
|
||||
JElement root = new JElement(document.getDocumentElement());
|
||||
List<JElement> elements = root.getChildJElements();
|
||||
|
||||
for(JElement element : elements) {
|
||||
String name = element.getName();
|
||||
String value = element.getTextContent();
|
||||
|
||||
data.put(name, value);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.wheatdb.seedcounter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import ru.delkom07.util.xml.JElement;
|
||||
import ru.delkom07.util.xml.XMLDOMHandler;
|
||||
|
||||
public class XMLSaverLoader implements ISaverLoader {
|
||||
JElement root;
|
||||
|
||||
@Override
|
||||
public void setData(Object obj) {
|
||||
root = (JElement) obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(File toFile) throws IOException {
|
||||
XMLDOMHandler.writeTree(root.getElement().getOwnerDocument(), toFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JElement load(File fromFile) {
|
||||
Document document = null;
|
||||
try {
|
||||
document = XMLDOMHandler.parse(fromFile);
|
||||
} catch (SAXException e) {
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Element root = document.getDocumentElement();
|
||||
if(null!=root)
|
||||
return new JElement(root);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AppPrint {
|
||||
|
||||
public static void print(String str) {
|
||||
System.out.println(str);
|
||||
}
|
||||
|
||||
public static void eprint(String str) {
|
||||
System.err.println(str);
|
||||
}
|
||||
|
||||
public static void printFilePath(File file) throws IOException {
|
||||
System.out.println(file.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.wheatdb.imgproc.colorchecker.MyColorChecker;
|
||||
|
||||
import ru.delkom07.fileutils.FileUtilities;
|
||||
import ru.delkom07.util.Pair;
|
||||
import ru.delkom07.workspaces.SimpleWorkspace.OpUnit;
|
||||
import ru.delkom07.workspaces.StatusWorkspace;
|
||||
|
||||
public class ColorCorrection {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
File inputDir = new File(args[0]);
|
||||
File outputDir = new File(args[1]);
|
||||
|
||||
StatusWorkspace workspace = new StatusWorkspace(inputDir, outputDir, true, FileUtilities.jpgImageFilter, null);
|
||||
List<OpUnit> inputAndOutputs = workspace.getRawOpUnits();
|
||||
|
||||
File dataFile = new File(outputDir, "data.csv");
|
||||
PrintWriter printWriter = new PrintWriter(dataFile);
|
||||
printWriter.println("File name;transformationDeviance");
|
||||
|
||||
System.out.println("Total imgs amount: " + inputAndOutputs.size() + "\n");
|
||||
|
||||
int count = 1;
|
||||
for(OpUnit inputAndOutput : inputAndOutputs) {
|
||||
File inputFile = inputAndOutput.getIn();
|
||||
File outputFile = inputAndOutput.getOut();
|
||||
|
||||
try {
|
||||
System.out.print(count++ + " " + inputFile.getCanonicalPath() + "... ");
|
||||
|
||||
Mat inputImg = Imgcodecs.imread(inputFile.getCanonicalPath());
|
||||
|
||||
MyColorChecker myColorChecker = new MyColorChecker(inputImg);
|
||||
|
||||
Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
|
||||
Mat calibrated = calibratedPair.getLeft();
|
||||
double transformationDeviance = calibratedPair.getRight();
|
||||
|
||||
System.out.println("D: " + transformationDeviance);
|
||||
printWriter.println(inputFile.getCanonicalPath() + ";" + transformationDeviance);
|
||||
printWriter.flush();
|
||||
|
||||
Imgcodecs.imwrite(outputFile.getCanonicalPath(), calibrated);
|
||||
} catch(Exception e) {
|
||||
System.err.println("error. File was skipped.");
|
||||
}
|
||||
}
|
||||
|
||||
printWriter.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
public enum ColorSpace {
|
||||
RGB, HSV, Lab, YCrCb;
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.imgproc.colorchecker.MyColorChecker;
|
||||
import org.wheatdb.seedcounter.SheetFormat;
|
||||
import org.wheatdb.seedcounter.processor.DetectionProcessor;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
import org.wheatdb.seedcounter.server.DefaultFilesAndDirs;
|
||||
|
||||
import ru.delkom07.util.ArgOption;
|
||||
import ru.delkom07.util.CommandRepresentation;
|
||||
import ru.delkom07.util.IncorrectCmdException;
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
/**
|
||||
* It is obsolete?? Check it!
|
||||
* Main class of test application.
|
||||
*/
|
||||
public class DesktopMain {
|
||||
static class ImageFilter implements java.io.FileFilter {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
if(pathname.getName().matches(".+\\.(png|PNG|jpg|JPG|bmp|BMP)"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
try {
|
||||
// processing of command line
|
||||
List<ArgOption> optMap = new ArrayList<ArgOption>(2);
|
||||
optMap.add(new ArgOption("--out", "-o", false));
|
||||
optMap.add(new ArgOption("--help", "-h", true));
|
||||
|
||||
optMap.add(new ArgOption("--colorchecker", "-cc", true));
|
||||
optMap.add(new ArgOption("--calibrate", "-cl", true));
|
||||
optMap.add(new ArgOption("--withoutsheet", "-wh", true));
|
||||
|
||||
CommandRepresentation cmd =
|
||||
new CommandRepresentation(args, optMap, optMap.size(), 0, 2000, 0);
|
||||
|
||||
// command options
|
||||
if(cmd.containsOptionByName("--help"))
|
||||
usage("");
|
||||
|
||||
File outputDir = null;
|
||||
if(cmd.containsOptionByName("--out")) {
|
||||
|
||||
outputDir = new File(cmd.getOptionByName("--out").getValue());
|
||||
|
||||
if(!outputDir.isDirectory())
|
||||
throw new IncorrectCmdException("Option '--out' isn't a path to directory.");
|
||||
} else {
|
||||
outputDir = DefaultFilesAndDirs.outputDir;
|
||||
|
||||
if(outputDir.isFile()) {
|
||||
throw new IncorrectCmdException("Default output directory is existing file.");
|
||||
}
|
||||
|
||||
if(!outputDir.exists()) {
|
||||
outputDir.mkdir();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// preparing input files
|
||||
List<File> inputFiles = new LinkedList<File>();
|
||||
|
||||
String[] appArg = cmd.getAcceptedArgs();
|
||||
if(appArg.length != 0) {
|
||||
for(String inputPathStr : appArg) {
|
||||
File inputPath = new File(inputPathStr);
|
||||
if(!inputPath.exists())
|
||||
throw new IncorrectCmdException("Input file doesn't exists.");
|
||||
|
||||
if(inputPath.isDirectory()) {
|
||||
File[] files = inputPath.listFiles(new DesktopMain.ImageFilter());
|
||||
|
||||
for(File f : files) {
|
||||
inputFiles.add(f);
|
||||
}
|
||||
|
||||
} else {
|
||||
inputFiles.add(inputPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
File[] files = DefaultFilesAndDirs.inputDir.listFiles(new DesktopMain.ImageFilter());
|
||||
|
||||
for(File f : files) {
|
||||
inputFiles.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// processing input files
|
||||
DetectionProcessor detectProcessor = new DetectionProcessor(SheetFormat.A4);
|
||||
for(File inputImg : inputFiles) {
|
||||
Mat srcImg = Imgcodecs.imread(inputImg.getAbsolutePath());
|
||||
if(srcImg.empty())
|
||||
throw new IncorrectCmdException("Input file is not image.");
|
||||
|
||||
Mat region = srcImg;
|
||||
|
||||
if(!cmd.containsOptionByName("--withoutsheet")) {
|
||||
Quad quad = detectProcessor.detectQuad(srcImg);
|
||||
if(null==quad) {
|
||||
System.out.print("Input file:" + inputImg.getName()+". ");
|
||||
System.out.println("Sheet isn't found. Skipping image.");
|
||||
|
||||
continue;
|
||||
}
|
||||
region = detectProcessor.getTransformedField(srcImg, quad);
|
||||
}
|
||||
|
||||
List<PlanarObject> containers;
|
||||
|
||||
// if is colorchecker
|
||||
if(cmd.containsOptionByName("--colorchecker")) {
|
||||
MyColorChecker myColorChecker = new MyColorChecker(region);
|
||||
|
||||
// fill ColorChecker
|
||||
myColorChecker.fillColorChecker();
|
||||
|
||||
if(cmd.containsOptionByName("--calibrate")) {
|
||||
Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
region = calibratedPair.getLeft();
|
||||
Double transformationDeviance = calibratedPair.getRight();
|
||||
|
||||
System.out.println("# Transformation deviance: " + transformationDeviance.toString());
|
||||
}
|
||||
|
||||
containers = detectProcessor.detectSeedContours(region.clone(), myColorChecker.getPixPerMM());
|
||||
} else {
|
||||
containers = detectProcessor.detectSeedContours(region.clone());
|
||||
}
|
||||
|
||||
System.out.println("Seed count: " + containers.size());
|
||||
|
||||
String base = FilenameUtils.getBaseName(inputImg.getName());
|
||||
String extention = "tsv";
|
||||
File outputImgFile = new File(outputDir, base + "." + extention);
|
||||
|
||||
saveResults(outputImgFile, containers);
|
||||
|
||||
File outputFile = new File(outputDir, "output_" + inputImg.getName().replace(".jpg", ".png"));
|
||||
drawContoursWithRectAndNum(region, containers);
|
||||
Imgcodecs.imwrite(outputFile.getCanonicalPath(), region);
|
||||
}
|
||||
|
||||
} catch (IncorrectCmdException e) {
|
||||
System.err.println("Error: "+e.getMessage());
|
||||
usage(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error: "+e.getMessage());
|
||||
System.out.println("IO error. The program will be terminated");
|
||||
System.exit(0);
|
||||
} catch (SeedCounterProcessorException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static void drawContoursWithRectAndNum(Mat img, List<PlanarObject> containers) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
Rect rect = Imgproc.boundingRect(container.getContour());
|
||||
|
||||
Imgproc.rectangle(img, rect.tl(), rect.br(), new Scalar(255, 255, 0));
|
||||
Imgproc.drawContours(img, Arrays.asList(container.getContour()), -1, new Scalar(255, 255, 0), 1);
|
||||
Imgproc.putText(img, Integer.toString(i+1), rect.tl(), 1, 1, new Scalar(255, 255, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static void saveResults(File outputDataFile, List<PlanarObject> containers) throws IOException {
|
||||
PrintWriter writer = new PrintWriter(outputDataFile, "UTF-8");
|
||||
|
||||
int count = 1;
|
||||
writer.println("Number" + " " + "Length" + " " + "Width" + " " + "Area" + " " +
|
||||
"Circularity" + " " + "Roundness" + " " + "Rugosity" + " " + "Solidity");
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
writer.println(Integer.toString(count++) + " " + container.toString(" "));
|
||||
}
|
||||
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
private static void usage(String message) {
|
||||
System.out.println(message + "\n" + Messages.usage);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import static org.wheatdb.seedcounter.desktop.AppPrint.eprint;
|
||||
import static org.wheatdb.seedcounter.desktop.AppPrint.print;
|
||||
import static org.wheatdb.seedcounter.desktop.AppPrint.printFilePath;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.AddInfo;
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.ImageHandler;
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.ResultSaver;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.ex.QuadNotFoundException;
|
||||
import org.wheatdb.seedcounter.server.DefaultFilesAndDirs;
|
||||
|
||||
import nu.pattern.OpenCV;
|
||||
import ru.delkom07.util.ArgOption;
|
||||
import ru.delkom07.util.CommandRepresentation;
|
||||
import ru.delkom07.util.IncorrectCmdException;
|
||||
import ru.delkom07.workspaces.SimpleWorkspace.OpUnit;
|
||||
import ru.delkom07.workspaces.StatusWorkspace;
|
||||
|
||||
/**
|
||||
* Main class of SeedCounter desktop application.
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class DesktopMainWithColorDescriptorsOneThread {
|
||||
private static void handleOptions(CommandRepresentation cmd, RunConfiguration config) throws IncorrectCmdException {
|
||||
// command options
|
||||
if(cmd.containsOptionByName("--help"))
|
||||
usage("");
|
||||
|
||||
// input and output dirs
|
||||
config.inputDir = new File(cmd.getAcceptedArgs()[0]);
|
||||
|
||||
File outputDir = null;
|
||||
if(cmd.containsOptionByName("--out")) {
|
||||
outputDir = new File(cmd.getOptionByName("--out").getValue());
|
||||
|
||||
if(!outputDir.isDirectory())
|
||||
throw new IncorrectCmdException("Option '--out' isn't a path to directory.");
|
||||
} else {
|
||||
outputDir = DefaultFilesAndDirs.outputDir;
|
||||
|
||||
if(outputDir.isFile()) {
|
||||
throw new IncorrectCmdException("Default output directory is existing file.");
|
||||
}
|
||||
|
||||
if(!outputDir.exists()) {
|
||||
outputDir.mkdir();
|
||||
}
|
||||
}
|
||||
|
||||
config.outputDir = outputDir;
|
||||
|
||||
if(cmd.containsOptionByName("--colorchecker")) {
|
||||
config.colorchecker = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--calibrate")) {
|
||||
config.calibrate = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--withoutsheet")) {
|
||||
config.withoutsheet = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--colordescriptors")) {
|
||||
config.colordescriptors = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--texturedescriptors")) {
|
||||
config.texturedescriptors = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--draw")) {
|
||||
config.draw = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--markdescriptor")) {
|
||||
config.markdescriptor = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--debug")) {
|
||||
config.DEBUG_MODE = true;
|
||||
}
|
||||
|
||||
if(cmd.containsOptionByName("--markdescriptor")) {
|
||||
try {
|
||||
String[] splited = cmd.getOptionByName("--markdescriptor").getValue().split("_");
|
||||
config.colorSpace = ColorSpace.valueOf(splited[0]);
|
||||
config.markGCHDimension = Integer.parseInt(splited[1].replace("GCH", ""));
|
||||
config.markGCHBinNumber = Integer.parseInt(splited[2]) - 1;
|
||||
} catch(Exception e) {
|
||||
throw new IncorrectCmdException("Incorrect MarkGCHdescriptor option error.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static CommandRepresentation init(String[] args) throws IncorrectCmdException {
|
||||
print("Start");
|
||||
print("Length: " + args.length);
|
||||
print(Arrays.toString(args));
|
||||
|
||||
// processing of command line
|
||||
List<ArgOption> optMap = new ArrayList<ArgOption>(2);
|
||||
optMap.add(new ArgOption("--help", "-h", true)); // print help
|
||||
optMap.add(new ArgOption("--out", "-o", false)); // set output directory
|
||||
|
||||
optMap.add(new ArgOption("--colorchecker", "-cc", true)); // image contains ColorChecker
|
||||
optMap.add(new ArgOption("--calibrate", "-cl", true)); // do color calibration with ColorChecker
|
||||
optMap.add(new ArgOption("--withoutsheet", "-ws", true)); // background is solid. standard sheet of paper is not used for scale definition.
|
||||
|
||||
optMap.add(new ArgOption("--colordescriptors", "-cd", true)); // calculate color descriptors
|
||||
optMap.add(new ArgOption("--texturedescriptors", "-td", true)); // calculate texture descriptors
|
||||
optMap.add(new ArgOption("--draw", "-dr", true)); // draw additional output images
|
||||
optMap.add(new ArgOption("--markdescriptor", "-md", false)); // mark HSV color descriptor on output image
|
||||
optMap.add(new ArgOption("--debug", "-dg", true)); // enable debug mode
|
||||
|
||||
return new CommandRepresentation(args, optMap, optMap.size(), 0, 2000, 0);
|
||||
}
|
||||
|
||||
|
||||
private static RunConfiguration initAppConfiguration(String[] args) throws IncorrectCmdException {
|
||||
SpringApplication.run(DesktopMainWithColorDescriptorsOneThread.class, args);
|
||||
@SuppressWarnings("resource")
|
||||
ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
|
||||
|
||||
RunConfiguration config = (RunConfiguration) context.getBean("config");
|
||||
CommandRepresentation cmd = init(args);
|
||||
handleOptions(cmd, config);
|
||||
|
||||
config.imageHandler = (ImageHandler) context.getBean("imageHandler");
|
||||
|
||||
if(config.colordescriptors && !config.texturedescriptors) {
|
||||
config.imageHandler.setResultSaver((ResultSaver) context.getBean("colorResultSaver"));
|
||||
} else
|
||||
if(!config.colordescriptors && config.texturedescriptors) {
|
||||
config.imageHandler.setResultSaver((ResultSaver) context.getBean("textureResultSaver"));
|
||||
} else
|
||||
if(config.colordescriptors && config.texturedescriptors) {
|
||||
config.imageHandler.setResultSaver((ResultSaver) context.getBean("colorTextureResultSaver"));
|
||||
}
|
||||
|
||||
// Additional info writing to output table
|
||||
config.info = (AddInfo) context.getBean("simpleAddInfo");
|
||||
config.imageHandler.getResultSaver().setInfo(config.info);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
OpenCV.loadLocally();
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
var config = initAppConfiguration(args);
|
||||
|
||||
String base = "output";
|
||||
String extention = "tsv";
|
||||
File outputDataFile = new File(config.outputDir, base + "." + extention);
|
||||
|
||||
PrintWriter writer = new PrintWriter(outputDataFile, "UTF-8");
|
||||
|
||||
ResultSaver resultSaver = config.imageHandler.getResultSaver();
|
||||
resultSaver.writeFileHead(writer);
|
||||
writer.println();
|
||||
|
||||
StatusWorkspace workspace = new StatusWorkspace(config.inputDir, config.outputDir, true, null, null);
|
||||
List<OpUnit> opUnits = workspace.getRawOpUnits();
|
||||
|
||||
for(OpUnit unit : opUnits) {
|
||||
File inFile = unit.getIn();
|
||||
File outFile = unit.getOut();
|
||||
|
||||
printFilePath(inFile);
|
||||
|
||||
try {
|
||||
resultSaver.setInfo(config.info.from(inFile));
|
||||
|
||||
config.imageHandler.handleImage(inFile, outFile, writer);
|
||||
|
||||
print(inFile.getParent() + "/" + inFile.getName() + " done.");
|
||||
} catch(QuadNotFoundException e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("Sheet isn't found. Skipping image...");
|
||||
} catch (SeedCounterProcessorException e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("Inner error. Skipping image...");
|
||||
} catch (IOException e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("IO error. Skipping image...");
|
||||
} catch (SeedCounterException e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("Skipping image...");
|
||||
} catch (Exception e) {
|
||||
eprint("Input file:" + inFile.getName());
|
||||
eprint("Unknow exception: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
eprint("Skipping image...");
|
||||
} finally {
|
||||
workspace.saveProcessedOpUnit(unit);
|
||||
}
|
||||
}
|
||||
|
||||
writer.close();
|
||||
System.out.println("Computation time: " + Integer.toString((int)(System.currentTimeMillis() - time)/1000));
|
||||
System.exit(0); // STATUS - ok
|
||||
|
||||
} catch (IncorrectCmdException e) {
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("Command line error. The program will be terminated");
|
||||
usage(e.getMessage());
|
||||
System.exit(-1);
|
||||
} catch (IOException e) {
|
||||
eprint("Error: " + e.getMessage());
|
||||
eprint("IO error. The program will be terminated");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void usage(String message) {
|
||||
System.out.println(message + "\n" + Messages.usage);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JFrame;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfByte;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
|
||||
public class Display extends JFrame {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static int screenHeight = 1030;
|
||||
private static int screenWidth = 1284;
|
||||
|
||||
private static int screenUpPadding = 100;
|
||||
private static int screenSidePaddint = 30;
|
||||
|
||||
private int windowHeight = screenHeight-screenUpPadding*2;
|
||||
private int windowWidth = screenWidth-screenSidePaddint*2;
|
||||
|
||||
private BufferedImage image = null;
|
||||
private static int imagePadding = 30;
|
||||
private int imageHeight;
|
||||
private int imageWidth;
|
||||
|
||||
|
||||
public Display() {
|
||||
super("Display...");
|
||||
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
|
||||
setSize(windowWidth, windowHeight);
|
||||
setLocation(screenSidePaddint, screenUpPadding);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
super.paint(g);
|
||||
//Graphics2D graphics = (Graphics2D) g;
|
||||
|
||||
if(null!=image) {
|
||||
double heightResize = ((getHeight()-imagePadding*2)/(double)image.getHeight());
|
||||
double widthResize = ((getWidth()-imagePadding*2)/(double)image.getWidth());
|
||||
double resize = heightResize < widthResize ? heightResize : widthResize;
|
||||
|
||||
imageHeight = (int) (image.getHeight()*resize);
|
||||
imageWidth = (int) (image.getWidth()*resize);
|
||||
|
||||
g.drawImage(image, imagePadding, imagePadding*2, imageWidth, imageHeight,
|
||||
0,0,image.getWidth(),image.getHeight(), new Color(10,255,0), null);
|
||||
}
|
||||
}
|
||||
|
||||
private static BufferedImage matToImage(Mat mat) {
|
||||
MatOfByte bytemat = new MatOfByte();
|
||||
Imgcodecs.imencode(".jpg", mat, bytemat);
|
||||
byte[] bytes = bytemat.toArray();
|
||||
InputStream in = new ByteArrayInputStream(bytes);
|
||||
BufferedImage img = null;
|
||||
try {
|
||||
img = ImageIO.read(in);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
public void setImage(Mat img) {
|
||||
image = matToImage(img);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage img) {
|
||||
image = img;
|
||||
repaint();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.wheatdb.seedcounter.SeedDataContainer;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public class JSONSaver {
|
||||
|
||||
public void saveInJSON(Mat region, List<PlanarObject> containers, File inFile, File outFile) throws IOException {
|
||||
PrintWriter jsonWriter = new PrintWriter(new File(outFile.getParent(), FilenameUtils.getBaseName(outFile.getName()) + ".json"), "UTF-8");
|
||||
|
||||
JSONObject jsonObj = new JSONObject();
|
||||
JSONArray shapes = new JSONArray();
|
||||
|
||||
jsonObj.put("version", "4.0.0");
|
||||
jsonObj.put("flags", new JSONObject());
|
||||
jsonObj.put("shapes", shapes);
|
||||
|
||||
jsonObj.put("imagePath", outFile.getName());
|
||||
jsonObj.put("imageData", null);
|
||||
jsonObj.put("imageHeight", region.height());
|
||||
jsonObj.put("imageWidth", region.width());
|
||||
|
||||
for(PlanarObject container : containers) {
|
||||
|
||||
JSONObject fly = new JSONObject();
|
||||
fly.put("label", "fly");
|
||||
fly.put("group_id", null);
|
||||
fly.put("shape_type", "polygon");
|
||||
fly.put("flags", new JSONObject());
|
||||
|
||||
JSONArray points = new JSONArray();
|
||||
Point[] contourPoints = container.getContourApprox().toArray();
|
||||
|
||||
for(Point point : contourPoints) {
|
||||
|
||||
|
||||
|
||||
JSONArray jsonPoint = new JSONArray();
|
||||
jsonPoint.add(point.x);
|
||||
jsonPoint.add(point.y);
|
||||
|
||||
points.add(jsonPoint);
|
||||
}
|
||||
|
||||
fly.put("points", points);
|
||||
|
||||
shapes.add(fly);
|
||||
}
|
||||
|
||||
try {
|
||||
jsonObj.writeJSONString(jsonWriter);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
jsonWriter.close();
|
||||
Imgcodecs.imwrite(outFile.getCanonicalPath(), region);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
public class Messages {
|
||||
|
||||
public static final String usage = "" +
|
||||
"Usage: java -jar seedCounter.jar [--out outputDir] inputImgsDir \n\n" +
|
||||
|
||||
"-o, --out set output directory\n" +
|
||||
"-cc, --colorchecker image contains ColorChecker\n" +
|
||||
"-cl, --calibrate do color calibration with ColorChecker\n" +
|
||||
"-ws, --withoutsheet background is solid. standard sheet of paper is not used for scale definition\n" +
|
||||
"-cd, --colordescriptors calculate color descriptors\n" +
|
||||
"-dr, --draw draw additional output images\n" +
|
||||
"-md, --markdescriptor mark HSV color descriptor on output image\n";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.AddInfo;
|
||||
import org.wheatdb.seedcounter.desktop.ihandlers.ImageHandler;
|
||||
|
||||
public class RunConfiguration {
|
||||
// options:
|
||||
public boolean colorchecker = false;
|
||||
public boolean calibrate = false;
|
||||
public boolean withoutsheet = false;
|
||||
public boolean colordescriptors = false;
|
||||
public boolean texturedescriptors = false;
|
||||
public boolean draw = false;
|
||||
public boolean markdescriptor = false;
|
||||
public boolean DEBUG_MODE = false;
|
||||
|
||||
// Mark GCH Descriptor
|
||||
public ColorSpace colorSpace = null;
|
||||
public int markGCHDimension = -1;
|
||||
public int markGCHBinNumber = -1;
|
||||
|
||||
public File inputDir;
|
||||
public File outputDir;
|
||||
|
||||
public ImageHandler imageHandler;
|
||||
public AddInfo info;
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
package org.wheatdb.seedcounter.desktop;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.imgproc.colorchecker.MyColorChecker;
|
||||
import org.wheatdb.seedcounter.SheetFormat;
|
||||
import org.wheatdb.seedcounter.processor.DetectionProcessor;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.improc.color.descriptors.ColorLayoutDescriptor;
|
||||
import ru.delkom07.improc.color.descriptors.DominantColorDescriptor;
|
||||
import ru.delkom07.improc.color.descriptors.GlobalColorHistogram;
|
||||
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
|
||||
import ru.delkom07.util.CommandRepresentation;
|
||||
import ru.delkom07.util.IncorrectCmdException;
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
/**
|
||||
* It is obsolete?? Check it!
|
||||
* @author Komyshev
|
||||
*
|
||||
*/
|
||||
public class SeedCounterWCDThread implements Runnable {
|
||||
private static final String SEPARATOR = " ";
|
||||
|
||||
// processing input files
|
||||
private DetectionProcessor detectProcessor = new DetectionProcessor(SheetFormat.A4);
|
||||
private List<Pair<File, File>> inputAndOutputs;
|
||||
private PrintWriter writer;
|
||||
private CommandRepresentation cmd;
|
||||
|
||||
ExecutorService service;
|
||||
|
||||
public SeedCounterWCDThread(ExecutorService service, List<Pair<File, File>> inputAndOutputs, CommandRepresentation cmd, PrintWriter writer) {
|
||||
this.service = service;
|
||||
this.inputAndOutputs = inputAndOutputs;
|
||||
this.cmd = cmd;
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for(Pair<File, File> pair : inputAndOutputs) {
|
||||
File inFile = pair.getLeft();
|
||||
File outFile = pair.getRight();
|
||||
|
||||
String group = inFile.getParentFile().getName();
|
||||
String[] splitedSampleDirName = inFile.getParentFile().getParentFile().getName().split(" ");
|
||||
String sampleName;
|
||||
String year;
|
||||
if(splitedSampleDirName.length == 3) {
|
||||
sampleName = splitedSampleDirName[0] + "_" + splitedSampleDirName[1];
|
||||
year = splitedSampleDirName[2];
|
||||
} else
|
||||
if(splitedSampleDirName.length == 2){
|
||||
sampleName = splitedSampleDirName[0] + "_" + splitedSampleDirName[1];
|
||||
year = "-";
|
||||
} else {
|
||||
sampleName = splitedSampleDirName[0];
|
||||
year = "-";
|
||||
}
|
||||
|
||||
String mixing = FilenameUtils.getBaseName(inFile.getName()).split("_")[0];
|
||||
String duplicate = FilenameUtils.getBaseName(inFile.getName()).split("_")[1];
|
||||
|
||||
System.out.println(inFile.getParent() + "/" + inFile.getName());
|
||||
|
||||
try {
|
||||
|
||||
Mat srcImg = Imgcodecs.imread(inFile.getAbsolutePath());
|
||||
if(srcImg.empty())
|
||||
throw new IncorrectCmdException("Input file is not image.");
|
||||
|
||||
Mat region = srcImg;
|
||||
|
||||
if(!cmd.containsOptionByName("--withoutsheet")) {
|
||||
Quad quad = detectProcessor.detectQuad(srcImg);
|
||||
if(null==quad) {
|
||||
System.out.print("Input file:" + inFile.getName()+". ");
|
||||
System.out.println("Sheet isn't found. Skipping image.");
|
||||
|
||||
continue;
|
||||
}
|
||||
region = detectProcessor.getTransformedField(srcImg, quad);
|
||||
}
|
||||
|
||||
List<PlanarObject> containers;
|
||||
|
||||
// if is colorchecker
|
||||
Double transformationDeviance = 0.0;
|
||||
if(cmd.containsOptionByName("--colorchecker")) {
|
||||
MyColorChecker myColorChecker = new MyColorChecker(region);
|
||||
|
||||
// fill ColorChecker
|
||||
myColorChecker.fillColorChecker();
|
||||
|
||||
containers = detectProcessor.detectSeedContours(region, myColorChecker.getPixPerMM());
|
||||
|
||||
if(cmd.containsOptionByName("--calibrate")) {
|
||||
Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
region = calibratedPair.getLeft();
|
||||
transformationDeviance = calibratedPair.getRight();
|
||||
|
||||
//System.out.println("# Transformation deviance: " + transformationDeviance.toString());
|
||||
}
|
||||
} else {
|
||||
containers = detectProcessor.detectSeedContours(region);
|
||||
}
|
||||
|
||||
//System.out.println("Seed count: " + containers.size());
|
||||
|
||||
saveResults(region, containers, writer, inFile.getName(), year, sampleName, group, mixing, duplicate, transformationDeviance);
|
||||
|
||||
|
||||
if(cmd.containsOptionByName("--draw")) {
|
||||
drawContoursWithRectAndNum(region, containers);
|
||||
Imgcodecs.imwrite(outFile.getCanonicalPath(), region);
|
||||
}
|
||||
|
||||
|
||||
// Releases
|
||||
for(PlanarObject container : containers) {
|
||||
container.getContour().release();
|
||||
}
|
||||
containers.clear();
|
||||
|
||||
if(!srcImg.empty()) {
|
||||
srcImg.release();
|
||||
}
|
||||
|
||||
if(!region.empty()) {
|
||||
region.release();
|
||||
}
|
||||
|
||||
System.out.println(inFile.getParent() + "/" + inFile.getName() + " done.");
|
||||
|
||||
} catch(IncorrectCmdException ex) {
|
||||
service.shutdown();
|
||||
} catch (SeedCounterProcessorException e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
service.shutdown();
|
||||
}
|
||||
|
||||
|
||||
private static void drawContoursWithRectAndNum(Mat img, List<PlanarObject> containers) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
Rect rect = Imgproc.boundingRect(container.getContour());
|
||||
|
||||
Imgproc.rectangle(img, rect.tl(), rect.br(), new Scalar(255, 255, 0));
|
||||
Imgproc.drawContours(img, Arrays.asList(container.getContour()), -1, new Scalar(255, 255, 0), 1);
|
||||
Imgproc.putText(img, Integer.toString(i+1), rect.tl(), 1, 1, new Scalar(255, 255, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void saveResults(Mat region, List<PlanarObject> containers, PrintWriter writer, String fileName, String year, String sampleName, String group, String mixing, String duplicate, double transformationDeviance) throws IOException {
|
||||
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
|
||||
// Color Descriptors:
|
||||
MatOfPoint contour = container.getContour();
|
||||
Rect currentSeedRoi = Imgproc.boundingRect(contour);
|
||||
Mat subMatBGR = region.submat(currentSeedRoi);
|
||||
|
||||
Mat subMatRGB = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatRGB, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Mat subMatHSV = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatHSV, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat subMatLab = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatLab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -currentSeedRoi.x, -currentSeedRoi.y);
|
||||
Mat seedMask = Mat.zeros(currentSeedRoi.height, currentSeedRoi.width, CvType.CV_8UC1);
|
||||
Imgproc.drawContours(seedMask, Arrays.asList(shiftedContour), -1, new Scalar(255), -1);
|
||||
|
||||
// Mean color descriptor:
|
||||
int rejectionSigma = 3;
|
||||
MeanColorDescriptor meanColorRGB = new MeanColorDescriptor(rejectionSigma).calculate(subMatRGB, seedMask);
|
||||
MeanColorDescriptor meanColorHSV = new MeanColorDescriptor(rejectionSigma).calculate(subMatHSV, seedMask);
|
||||
MeanColorDescriptor meanColorLab = new MeanColorDescriptor(rejectionSigma).calculate(subMatLab, seedMask);
|
||||
|
||||
// GCH4:
|
||||
int GCH4Dimension = 4;
|
||||
GlobalColorHistogram gch4RGB = new GlobalColorHistogram(GCH4Dimension).calculate(subMatRGB, seedMask);
|
||||
GlobalColorHistogram gch4HSV = new GlobalColorHistogram(GCH4Dimension).calculate(subMatHSV, seedMask);
|
||||
GlobalColorHistogram gch4Lab = new GlobalColorHistogram(GCH4Dimension).calculate(subMatLab, seedMask);
|
||||
|
||||
// GCH8:
|
||||
int GCH8Dimension = 8;
|
||||
GlobalColorHistogram gch8RGB = new GlobalColorHistogram(GCH8Dimension).calculate(subMatRGB, seedMask);
|
||||
GlobalColorHistogram gch8HSV = new GlobalColorHistogram(GCH8Dimension).calculate(subMatHSV, seedMask);
|
||||
GlobalColorHistogram gch8Lab = new GlobalColorHistogram(GCH8Dimension).calculate(subMatLab, seedMask);
|
||||
|
||||
// Color layout descriptor:
|
||||
ColorLayoutDescriptor cldRGB = new ColorLayoutDescriptor(12).calculate(subMatRGB, seedMask, null);
|
||||
ColorLayoutDescriptor cldHSV = new ColorLayoutDescriptor(12).calculate(subMatHSV, seedMask, null);
|
||||
ColorLayoutDescriptor cldLab = new ColorLayoutDescriptor(12).calculate(subMatLab, seedMask, null);
|
||||
|
||||
// Dominant color descriptor:
|
||||
int colorsAmount = 3;
|
||||
DominantColorDescriptor dcdRGB = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatRGB, seedMask, null);
|
||||
DominantColorDescriptor dcdHSV = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatHSV, seedMask, null);
|
||||
DominantColorDescriptor dcdLab = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatLab, seedMask, null);
|
||||
|
||||
subMatBGR.release();
|
||||
subMatRGB.release();
|
||||
subMatHSV.release();
|
||||
subMatLab.release();
|
||||
seedMask.release();
|
||||
shiftedContour.release();
|
||||
|
||||
synchronized (SeedCounterWCDThread.class) {
|
||||
writer.print(fileName + SEPARATOR);
|
||||
writer.print(year + SEPARATOR);
|
||||
writer.print(sampleName + SEPARATOR);
|
||||
writer.print(group + SEPARATOR);
|
||||
writer.print(mixing + SEPARATOR);
|
||||
writer.print(duplicate + SEPARATOR);
|
||||
|
||||
writer.print(Double.toString(transformationDeviance) + SEPARATOR);
|
||||
|
||||
writer.print(Integer.toString(i+1) + SEPARATOR + container.toString(SEPARATOR));
|
||||
|
||||
writer.print(meanColorRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch4RGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch8RGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cldRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(dcdRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(meanColorHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch4HSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch8HSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cldHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(dcdHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(meanColorLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch4Lab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(gch8Lab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cldLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(dcdLab.toTsvCsvString(SEPARATOR));
|
||||
|
||||
writer.println();
|
||||
}
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
private MatOfPoint shiftContour(MatOfPoint contour, int shiftX, int shiftY) {
|
||||
Point[] pointArr = new Point[contour.rows()*contour.cols()];
|
||||
|
||||
int i = 0;
|
||||
for(Point srcPoint : contour.toArray()) {
|
||||
pointArr[i++] = new Point(srcPoint.x+shiftX, srcPoint.y+shiftY);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = new MatOfPoint();
|
||||
shiftedContour.fromArray(pointArr);
|
||||
return shiftedContour;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.wheatdb.seedcounter.desktop.coloredseedsclassification;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.ml.clustering.CentroidCluster;
|
||||
import org.apache.commons.math3.ml.clustering.Clusterable;
|
||||
|
||||
public class ClusterFunctions {
|
||||
|
||||
public static double clusterRadius(CentroidCluster<ContourContainer> cluster) {
|
||||
Clusterable center = cluster.getCenter();
|
||||
List<ContourContainer> points = cluster.getPoints();
|
||||
|
||||
double maxDistance = 0;
|
||||
for(ContourContainer point : points) {
|
||||
double curDist = euclideanDistance(center.getPoint(), point.getPoint());
|
||||
|
||||
if(curDist > maxDistance) {
|
||||
maxDistance = curDist;
|
||||
}
|
||||
}
|
||||
|
||||
return maxDistance;
|
||||
}
|
||||
|
||||
|
||||
public static double euclideanDistance(double[] point1, double[] point2) {
|
||||
int dimensions = point1.length < point2.length ? point1.length : point2.length;
|
||||
|
||||
double quadSum = 0;
|
||||
for(int i=0; i<dimensions; i++) {
|
||||
quadSum += (point1[i]-point2[i]) * (point1[i]-point2[i]);
|
||||
}
|
||||
|
||||
return Math.sqrt(quadSum);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.wheatdb.seedcounter.desktop.coloredseedsclassification;
|
||||
|
||||
import org.apache.commons.math3.ml.clustering.Clusterable;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.wheatdb.seedcounter.processor.ElementsProcessor;
|
||||
|
||||
import ru.delkom07.geometry.ContoursFunctions;
|
||||
import ru.delkom07.geometry.SimpleGeometry;
|
||||
|
||||
|
||||
|
||||
public class ContourContainer implements Clusterable {
|
||||
MatOfPoint contour;
|
||||
|
||||
|
||||
|
||||
public ContourContainer(MatOfPoint contour) {
|
||||
this.contour = contour;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public double[] getPoint() {
|
||||
return ContoursFunctions.massCenter(contour);
|
||||
}
|
||||
|
||||
|
||||
public MatOfPoint getContour() {
|
||||
return contour;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.wheatdb.seedcounter.desktop.coloredseedsclassification;
|
||||
|
||||
public class Messages {
|
||||
public static final String usage = "" +
|
||||
"Usage: seedCounter [[--out outputDir] || [-o outputDir]] inputImage [inputImage...]";
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public interface AddInfo {
|
||||
public List<String> headers();
|
||||
|
||||
public String toString(String separator);
|
||||
|
||||
public AddInfo from(File inFile);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public class BaseResultSaver implements ResultSaver {
|
||||
private AddInfo info;
|
||||
|
||||
@Override
|
||||
public void saveResults(PrintWriter writer, Mat region, List<PlanarObject> containers, String fileName, double transformationDeviance) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
|
||||
writeData(writer, region, container, i+1, transformationDeviance);
|
||||
|
||||
writer.println();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
public void writeData(PrintWriter writer, Mat region, PlanarObject container, int seedNumber, double transformationDeviance) {
|
||||
//if(null != info)
|
||||
writer.print(info.toString(SEPARATOR));
|
||||
|
||||
writer.print(Double.toString(transformationDeviance) + SEPARATOR);
|
||||
writer.print(Integer.toString(seedNumber) + SEPARATOR);
|
||||
writer.print(container.toString(SEPARATOR));
|
||||
}
|
||||
|
||||
|
||||
public void writeFileHead(PrintWriter writer) {
|
||||
List<String> columnNames = new LinkedList<String>();
|
||||
|
||||
columnNames.addAll(info.headers());
|
||||
|
||||
columnNames.add("D");
|
||||
columnNames.add("Seed_number");
|
||||
columnNames.add("Length");
|
||||
columnNames.add("Width");
|
||||
columnNames.add("Area");
|
||||
columnNames.add("Circularity");
|
||||
columnNames.add("Roundness");
|
||||
columnNames.add("Rugosity");
|
||||
columnNames.add("Solidity");
|
||||
//columnNames.addAll(SeedData.headers());
|
||||
|
||||
for(String columnName : columnNames) {
|
||||
writer.print(columnName + SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setInfo(AddInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import static org.wheatdb.seedcounter.desktop.AppPrint.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.imgproc.colorchecker.MyColorChecker;
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.desktop.JSONSaver;
|
||||
import org.wheatdb.seedcounter.desktop.RunConfiguration;
|
||||
import org.wheatdb.seedcounter.processor.DetectionProcessor;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
public class ClassicImageHandler implements ImageHandler {
|
||||
private DetectionProcessor detectProcessor; // = new DetectionProcessor(SheetFormat.A4); // from DI container
|
||||
private RunConfiguration config; // from DI container
|
||||
private ResultSaver resultSaver; // from DI container
|
||||
|
||||
|
||||
|
||||
public ClassicImageHandler(RunConfiguration config, DetectionProcessor detectProcessor, ResultSaver resultSaver) {
|
||||
this.config = config;
|
||||
this.detectProcessor = detectProcessor;
|
||||
this.resultSaver = resultSaver;
|
||||
}
|
||||
|
||||
|
||||
public void handleImage(File inFile, File outFile, PrintWriter writer) throws IOException, SeedCounterProcessorException, SeedCounterException {
|
||||
Mat srcImg = Imgcodecs.imread(inFile.getCanonicalPath(), Imgcodecs.CV_LOAD_IMAGE_UNCHANGED);
|
||||
|
||||
if(srcImg.empty())
|
||||
throw new SeedCounterException("Input file is not image.");
|
||||
|
||||
Mat region = srcImg;
|
||||
if(!config.withoutsheet) {
|
||||
Quad quad = detectProcessor.detectQuad(srcImg);
|
||||
region = detectProcessor.getTransformedField(srcImg, quad);
|
||||
}
|
||||
|
||||
|
||||
List<PlanarObject> containers;
|
||||
|
||||
// if colorchecker is on the image
|
||||
Double transformationDeviance = 0.0;
|
||||
if(config.colorchecker) {
|
||||
MyColorChecker myColorChecker = new MyColorChecker(region);
|
||||
|
||||
// fill ColorChecker
|
||||
myColorChecker.fillColorChecker();
|
||||
|
||||
if(config.calibrate) {
|
||||
Pair<Mat, Double> calibratedPair = myColorChecker.calibrateImg();
|
||||
|
||||
// resources need to be release
|
||||
if(!region.empty()) {
|
||||
region.release();
|
||||
}
|
||||
|
||||
region = calibratedPair.getLeft();
|
||||
transformationDeviance = calibratedPair.getRight();
|
||||
|
||||
print("# Transformation deviance: " + transformationDeviance.toString());
|
||||
}
|
||||
|
||||
containers = detectProcessor.detectSeedContours(region, myColorChecker.getPixPerMM());
|
||||
} else {
|
||||
containers = detectProcessor.detectSeedContours(region.clone());
|
||||
}
|
||||
|
||||
|
||||
print("Seed count: " + containers.size());
|
||||
|
||||
resultSaver.saveResults(writer, region, containers, inFile.getName(), transformationDeviance);
|
||||
|
||||
new JSONSaver().saveInJSON(region, containers, inFile, outFile);
|
||||
|
||||
if(config.draw) {
|
||||
if(config.markdescriptor) {
|
||||
ColorDescriptorsSet.drawContoursWithRectAndNumAndMarkGCH(region, containers, config.colorSpace, config.markGCHDimension, config.markGCHBinNumber);
|
||||
} else {
|
||||
drawContoursWithRectAndNum(region, containers);
|
||||
}
|
||||
|
||||
Imgcodecs.imwrite(outFile.getCanonicalPath(), region);
|
||||
}
|
||||
|
||||
|
||||
// Releases
|
||||
for(PlanarObject container : containers) {
|
||||
container.getContour().release();
|
||||
}
|
||||
containers.clear();
|
||||
|
||||
if(!srcImg.empty()) {
|
||||
srcImg.release();
|
||||
}
|
||||
|
||||
if(!region.empty()) {
|
||||
region.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void drawContoursWithRectAndNum(Mat img, List<PlanarObject> containers) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
Rect rect = Imgproc.boundingRect(container.getContour());
|
||||
|
||||
Imgproc.rectangle(img, rect.tl(), rect.br(), new Scalar(255, 255, 0));
|
||||
Imgproc.drawContours(img, Arrays.asList(container.getContour()), -1, new Scalar(255, 255, 0), 1);
|
||||
Imgproc.putText(img, Integer.toString(i+1), rect.tl(), 1, 1, new Scalar(255, 255, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ResultSaver getResultSaver() {
|
||||
return resultSaver;
|
||||
}
|
||||
|
||||
|
||||
public void setResultSaver(ResultSaver resultSaver) {
|
||||
this.resultSaver = resultSaver;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.desktop.ColorSpace;
|
||||
import org.wheatdb.seedcounter.processor.ImageArea;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
import ru.delkom07.improc.color.descriptors.ColorLayoutDescriptor;
|
||||
import ru.delkom07.improc.color.descriptors.DominantColorDescriptor;
|
||||
import ru.delkom07.improc.color.descriptors.GlobalColorHistogram;
|
||||
import ru.delkom07.improc.color.descriptors.MeanColorDescriptor;
|
||||
|
||||
public class ColorDescriptorsSet {
|
||||
// Mean color
|
||||
MeanColorDescriptor meanColorRGB;
|
||||
MeanColorDescriptor meanColorHSV;
|
||||
MeanColorDescriptor meanColorLab;
|
||||
MeanColorDescriptor meanColorYCrCb;
|
||||
|
||||
// GCH4:
|
||||
GlobalColorHistogram gch4RGB;
|
||||
GlobalColorHistogram gch4HSV;
|
||||
GlobalColorHistogram gch4Lab;
|
||||
GlobalColorHistogram gch4YCrCb;
|
||||
|
||||
// GCH8:
|
||||
GlobalColorHistogram gch8RGB;
|
||||
GlobalColorHistogram gch8HSV;
|
||||
GlobalColorHistogram gch8Lab;
|
||||
GlobalColorHistogram gch8YCrCb;
|
||||
|
||||
// Color layout descriptor:
|
||||
ColorLayoutDescriptor cldRGB;
|
||||
ColorLayoutDescriptor cldHSV;
|
||||
ColorLayoutDescriptor cldLab;
|
||||
ColorLayoutDescriptor cldYCrCb;
|
||||
|
||||
// Dominant color descriptor:
|
||||
DominantColorDescriptor dcdRGB;
|
||||
DominantColorDescriptor dcdHSV;
|
||||
DominantColorDescriptor dcdLab;
|
||||
DominantColorDescriptor dcdYCrCb;
|
||||
|
||||
|
||||
ColorDescriptorsSet(Mat region, ImageArea imageArea) {
|
||||
MatOfPoint contour = imageArea.getContour();
|
||||
Rect currentSeedRoi = Imgproc.boundingRect(contour);
|
||||
Mat subMatBGR = region.submat(currentSeedRoi);
|
||||
|
||||
Mat subMatRGB = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatRGB, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Mat subMatHSV = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatHSV, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat subMatLab = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatLab, Imgproc.COLOR_BGR2Lab);
|
||||
|
||||
Mat subMatYCrCb = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatYCrCb, Imgproc.COLOR_BGR2YCrCb);
|
||||
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -currentSeedRoi.x, -currentSeedRoi.y);
|
||||
Mat seedMask = Mat.zeros(currentSeedRoi.height, currentSeedRoi.width, CvType.CV_8UC1);
|
||||
Imgproc.drawContours(seedMask, Arrays.asList(shiftedContour), -1, new Scalar(255), -1);
|
||||
|
||||
// Mean color descriptor:
|
||||
int rejectionSigma = 3;
|
||||
meanColorRGB = new MeanColorDescriptor(rejectionSigma).calculate(subMatRGB, seedMask);
|
||||
meanColorHSV = new MeanColorDescriptor(rejectionSigma).calculate(subMatHSV, seedMask);
|
||||
meanColorLab = new MeanColorDescriptor(rejectionSigma).calculate(subMatLab, seedMask);
|
||||
meanColorYCrCb = new MeanColorDescriptor(rejectionSigma).calculate(subMatYCrCb, seedMask);
|
||||
|
||||
// GCH4:
|
||||
int GCH4Dimension = 4;
|
||||
gch4RGB = new GlobalColorHistogram(GCH4Dimension).calculate(subMatRGB, seedMask);
|
||||
gch4HSV = new GlobalColorHistogram(GCH4Dimension, 180, 256, 256).calculate(subMatHSV, seedMask);
|
||||
gch4Lab = new GlobalColorHistogram(GCH4Dimension).calculate(subMatLab, seedMask);
|
||||
gch4YCrCb = new GlobalColorHistogram(GCH4Dimension).calculate(subMatYCrCb, seedMask);
|
||||
|
||||
// GCH8:
|
||||
int GCH8Dimension = 8;
|
||||
gch8RGB = new GlobalColorHistogram(GCH8Dimension).calculate(subMatRGB, seedMask);
|
||||
gch8HSV = new GlobalColorHistogram(GCH8Dimension, 180, 256, 256).calculate(subMatHSV, seedMask);
|
||||
gch8Lab = new GlobalColorHistogram(GCH8Dimension).calculate(subMatLab, seedMask);
|
||||
gch8YCrCb = new GlobalColorHistogram(GCH8Dimension).calculate(subMatYCrCb, seedMask);
|
||||
|
||||
// Color layout descriptor:
|
||||
cldRGB = new ColorLayoutDescriptor(12).calculate(subMatRGB, seedMask, null);
|
||||
cldHSV = new ColorLayoutDescriptor(12).calculate(subMatHSV, seedMask, null);
|
||||
cldLab = new ColorLayoutDescriptor(12).calculate(subMatLab, seedMask, null);
|
||||
cldYCrCb = new ColorLayoutDescriptor(12).calculate(subMatYCrCb, seedMask, null);
|
||||
|
||||
// Dominant color descriptor:
|
||||
int colorsAmount = 3;
|
||||
dcdRGB = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatRGB, seedMask, null);
|
||||
dcdHSV = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatHSV, seedMask, null);
|
||||
dcdLab = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatLab, seedMask, null);
|
||||
dcdYCrCb = new DominantColorDescriptor(colorsAmount, 5, 5, DominantColorDescriptor.SORT_BY_BRIGHTNESS).calculate(subMatYCrCb, seedMask, null);
|
||||
|
||||
subMatBGR.release();
|
||||
subMatRGB.release();
|
||||
subMatHSV.release();
|
||||
subMatLab.release();
|
||||
subMatYCrCb.release();
|
||||
seedMask.release();
|
||||
shiftedContour.release();
|
||||
}
|
||||
|
||||
|
||||
private static MatOfPoint shiftContour(MatOfPoint contour, int shiftX, int shiftY) {
|
||||
Point[] pointArr = new Point[contour.rows()*contour.cols()];
|
||||
|
||||
int i = 0;
|
||||
for(Point srcPoint : contour.toArray()) {
|
||||
pointArr[i++] = new Point(srcPoint.x+shiftX, srcPoint.y+shiftY);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = new MatOfPoint();
|
||||
shiftedContour.fromArray(pointArr);
|
||||
return shiftedContour;
|
||||
}
|
||||
|
||||
|
||||
static void drawContoursWithRectAndNumAndMarkGCH(Mat img, List<PlanarObject> containers, ColorSpace colorSpace, int gchDimension, int binNumber) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
Rect roi = Imgproc.boundingRect(container.getContour());
|
||||
|
||||
MatOfPoint contour = container.getContour();
|
||||
Mat subMatBGR = img.submat(roi);
|
||||
|
||||
Mat subMatColorSpace = new Mat();
|
||||
if(ColorSpace.RGB == colorSpace) {
|
||||
Imgproc.cvtColor(subMatBGR, subMatColorSpace, Imgproc.COLOR_BGR2RGB);
|
||||
} else if(ColorSpace.HSV == colorSpace) {
|
||||
Imgproc.cvtColor(subMatBGR, subMatColorSpace, Imgproc.COLOR_BGR2HSV);
|
||||
} else if(ColorSpace.Lab == colorSpace) {
|
||||
Imgproc.cvtColor(subMatBGR, subMatColorSpace, Imgproc.COLOR_BGR2Lab);
|
||||
} else if(ColorSpace.YCrCb == colorSpace) {
|
||||
Imgproc.cvtColor(subMatBGR, subMatColorSpace, Imgproc.COLOR_BGR2YCrCb);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -roi.x, -roi.y);
|
||||
Mat seedMask = Mat.zeros(roi.height, roi.width, CvType.CV_8UC1);
|
||||
Imgproc.drawContours(seedMask, Arrays.asList(shiftedContour), -1, new Scalar(255), -1);
|
||||
|
||||
GlobalColorHistogram gch = new GlobalColorHistogram(gchDimension);
|
||||
Mat mark = gch.markPixels(subMatColorSpace, seedMask, binNumber);
|
||||
|
||||
for(int ii=0; ii<mark.rows(); ii++) {
|
||||
for(int jj=0; jj<mark.cols(); jj++) {
|
||||
if(0 != mark.get(ii, jj)[0]) {
|
||||
|
||||
double[] imgPix = img.get(ii+roi.y, jj+roi.x);
|
||||
img.put(ii+roi.y, jj+roi.x, new double[] {imgPix[0], imgPix[1]+100, imgPix[2]-100});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mark.release();
|
||||
subMatBGR.release();
|
||||
subMatColorSpace.release();
|
||||
shiftedContour.release();
|
||||
seedMask.release();
|
||||
|
||||
|
||||
//Imgproc.rectangle(img, roi.tl(), roi.br(), new Scalar(255, 255, 0));
|
||||
//Imgproc.drawContours(img, Arrays.asList(container.getSeedContour()), -1, new Scalar(255, 255, 0), 1);
|
||||
Imgproc.putText(img, Integer.toString(i+1), roi.tl(), 1, 1, new Scalar(255, 255, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public class ColorResultSaver implements ResultSaver {
|
||||
private ColorDescriptorsSet cd;
|
||||
|
||||
private ResultSaver baseSaver;
|
||||
|
||||
|
||||
|
||||
public ColorResultSaver() {}
|
||||
|
||||
public ColorResultSaver(ResultSaver baseSaver) {
|
||||
this.baseSaver = baseSaver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveResults(PrintWriter writer, Mat region, List<PlanarObject> containers, String fileName, double transformationDeviance) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
|
||||
writeData(writer, region, container, i+1, transformationDeviance);
|
||||
|
||||
writer.println();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
public void writeData(PrintWriter writer, Mat region, PlanarObject container, int seedNumber, double transformationDeviance) {
|
||||
if(null != baseSaver)
|
||||
baseSaver.writeData(writer, region, container, seedNumber, transformationDeviance);
|
||||
|
||||
cd = new ColorDescriptorsSet(region, container);
|
||||
writer.print(cd.meanColorRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch4RGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch8RGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.cldRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.dcdRGB.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(cd.meanColorHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch4HSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch8HSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.cldHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.dcdHSV.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(cd.meanColorLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch4Lab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch8Lab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.cldLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.dcdLab.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
writer.print(cd.meanColorYCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch4YCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.gch8YCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.cldYCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(cd.dcdYCrCb.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
}
|
||||
|
||||
|
||||
public void writeFileHead(PrintWriter writer) {
|
||||
if(null != baseSaver)
|
||||
baseSaver.writeFileHead(writer);
|
||||
|
||||
List<String> columnNames = new LinkedList<String>();
|
||||
|
||||
// RGB
|
||||
columnNames.add("RGB_meanColor_R");
|
||||
columnNames.add("RGB_meanColor_G");
|
||||
columnNames.add("RGB_meanColor_B");
|
||||
|
||||
for(int i=0; i<64; i++) {
|
||||
columnNames.add("RGB_GCH4_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<512; i++) {
|
||||
columnNames.add("RGB_GCH8_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<12; i++) {
|
||||
columnNames.add("RGB_colorLayout_R_" + (i+1));
|
||||
columnNames.add("RGB_colorLayout_G_" + (i+1));
|
||||
columnNames.add("RGB_colorLayout_B_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++) {
|
||||
columnNames.add("RGB_dominantColor_R_" + (i+1));
|
||||
columnNames.add("RGB_dominantColor_G_" + (i+1));
|
||||
columnNames.add("RGB_dominantColor_B_" + (i+1));
|
||||
columnNames.add("RGB_dominantColor_percent_" + (i+1));
|
||||
columnNames.add("RGB_dominantColor_variance_" + (i+1));
|
||||
|
||||
}
|
||||
|
||||
columnNames.add("RGB_dominantColor_spatialCoherency");
|
||||
|
||||
// HSV
|
||||
columnNames.add("HSV_meanColor_H");
|
||||
columnNames.add("HSV_meanColor_S");
|
||||
columnNames.add("HSV_meanColor_V");
|
||||
|
||||
for(int i=0; i<64; i++) {
|
||||
columnNames.add("HSV_GCH4_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<512; i++) {
|
||||
columnNames.add("HSV_GCH8_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<12; i++) {
|
||||
columnNames.add("HSV_colorLayout_H_" + (i+1));
|
||||
columnNames.add("HSV_colorLayout_S_" + (i+1));
|
||||
columnNames.add("HSV_colorLayout_V_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++) {
|
||||
columnNames.add("HSV_dominantColor_H_" + (i+1));
|
||||
columnNames.add("HSV_dominantColor_S_" + (i+1));
|
||||
columnNames.add("HSV_dominantColor_V_" + (i+1));
|
||||
columnNames.add("HSV_dominantColor_persent_" + (i+1));
|
||||
columnNames.add("HSV_dominantColor_variance_" + (i+1));
|
||||
}
|
||||
|
||||
columnNames.add("HSV_dominantColor_spatialCoherency");
|
||||
|
||||
// L'a'b'
|
||||
columnNames.add("Lab_meanColor_L");
|
||||
columnNames.add("Lab_meanColor_a");
|
||||
columnNames.add("Lab_meanColor_b");
|
||||
|
||||
for(int i=0; i<64; i++) {
|
||||
columnNames.add("Lab_GCH4_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<512; i++) {
|
||||
columnNames.add("Lab_GCH8_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<12; i++) {
|
||||
columnNames.add("Lab_colorLayout_L_" + (i+1));
|
||||
columnNames.add("Lab_colorLayout_a_" + (i+1));
|
||||
columnNames.add("Lab_colorLayout_b_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++) {
|
||||
columnNames.add("Lab_dominantColor_L_" + (i+1));
|
||||
columnNames.add("Lab_dominantColor_a_" + (i+1));
|
||||
columnNames.add("Lab_dominantColor_b_" + (i+1));
|
||||
columnNames.add("Lab_dominantColor_persent_" + (i+1));
|
||||
columnNames.add("Lab_dominantColor_variance_" + (i+1));
|
||||
}
|
||||
|
||||
columnNames.add("Lab_dominantColor_spatialCoherency");
|
||||
|
||||
// YCrCb
|
||||
columnNames.add("YCrCb_meanColor_Y");
|
||||
columnNames.add("YCrCb_meanColor_Cr");
|
||||
columnNames.add("YCrCb_meanColor_Cb");
|
||||
|
||||
for(int i=0; i<64; i++) {
|
||||
columnNames.add("YCrCb_GCH4_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<512; i++) {
|
||||
columnNames.add("YCrCb_GCH8_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<12; i++) {
|
||||
columnNames.add("YCrCb_colorLayout_Y_" + (i+1));
|
||||
columnNames.add("YCrCb_colorLayout_Cr_" + (i+1));
|
||||
columnNames.add("YCrCb_colorLayout_Cb_" + (i+1));
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++) {
|
||||
columnNames.add("YCrCb_dominantColor_Y_" + (i+1));
|
||||
columnNames.add("YCrCb_dominantColor_Cr_" + (i+1));
|
||||
columnNames.add("YCrCb_dominantColor_Cb_" + (i+1));
|
||||
columnNames.add("YCrCb_dominantColor_persent_" + (i+1));
|
||||
columnNames.add("YCrCb_dominantColor_variance_" + (i+1));
|
||||
}
|
||||
|
||||
columnNames.add("YCrCb_dominantColor_spatialCoherency");
|
||||
|
||||
|
||||
for(String columnName : columnNames) {
|
||||
writer.print(columnName + SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInfo(AddInfo info) {
|
||||
this.baseSaver.setInfo(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Дополнительная информация о входные данных, извлекаемая из файловой структуры, специальных файлов описания и т.п.
|
||||
* Функционал класса планируется постоянно расширять.
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class FileStructureAddInfo implements AddInfo {
|
||||
public String file;
|
||||
public String year;
|
||||
public String sampleName;
|
||||
public String group;
|
||||
public String mixing;
|
||||
public String duplicate;
|
||||
|
||||
public FileStructureAddInfo() {}
|
||||
|
||||
private FileStructureAddInfo(String file, String year, String sampleName, String group, String mixing, String duplicate) {
|
||||
this.file = file;
|
||||
this.year = year;
|
||||
this.sampleName = sampleName;
|
||||
this.group = group;
|
||||
this.mixing = mixing;
|
||||
this.duplicate = duplicate;
|
||||
}
|
||||
|
||||
public FileStructureAddInfo from(File inFile) {
|
||||
try {
|
||||
String group = inFile.getParentFile().getName();
|
||||
String[] splitedSampleDirName = inFile.getParentFile().getParentFile().getName().split(" ");
|
||||
String sampleName;
|
||||
String year;
|
||||
if(splitedSampleDirName.length == 3) {
|
||||
sampleName = splitedSampleDirName[0] + "_" + splitedSampleDirName[1];
|
||||
year = splitedSampleDirName[2];
|
||||
} else
|
||||
if(splitedSampleDirName.length == 2){
|
||||
sampleName = splitedSampleDirName[0] + "_" + splitedSampleDirName[1];
|
||||
year = "-";
|
||||
} else {
|
||||
sampleName = splitedSampleDirName[0];
|
||||
year = "-";
|
||||
}
|
||||
|
||||
String mixing = FilenameUtils.getBaseName(inFile.getName()).split("_")[0];
|
||||
String duplicate = FilenameUtils.getBaseName(inFile.getName()).split("_")[1];
|
||||
|
||||
return new FileStructureAddInfo(inFile.getName(), year, sampleName, group, mixing, duplicate);
|
||||
} catch(Exception ex) {
|
||||
return new FileStructureAddInfo(inFile.getName(), "-", "-", "-", "-", "-");
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> headers() {
|
||||
var columnNames = new LinkedList<String>();
|
||||
|
||||
columnNames.add("File");
|
||||
columnNames.add("Year");
|
||||
columnNames.add("Sample");
|
||||
columnNames.add("Group");
|
||||
columnNames.add("Mixing");
|
||||
columnNames.add("Duplicate");
|
||||
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
public String toString(String separator) {
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.append(file + separator);
|
||||
builder.append(year + separator);
|
||||
builder.append(sampleName + separator);
|
||||
builder.append(group + separator);
|
||||
builder.append(mixing + separator);
|
||||
builder.append(duplicate + separator);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
|
||||
public interface ImageHandler {
|
||||
public void handleImage(File inFile, File outFile, PrintWriter writer) throws IOException, SeedCounterProcessorException, SeedCounterException;
|
||||
|
||||
public ResultSaver getResultSaver();
|
||||
|
||||
public void setResultSaver(ResultSaver resultSaver);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
public interface ResultSaver {
|
||||
public static final String SEPARATOR = " ";
|
||||
|
||||
public void saveResults(PrintWriter writer, Mat region, List<PlanarObject> containers, String fileName, double transformationDeviance);
|
||||
|
||||
public void writeData(PrintWriter writer, Mat region, PlanarObject container, int seedNumber, double transformationDeviance);
|
||||
|
||||
public void writeFileHead(PrintWriter writer);
|
||||
|
||||
public void setInfo(AddInfo info);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SimpleAddInfo implements AddInfo {
|
||||
public String file;
|
||||
|
||||
@Override
|
||||
public List<String> headers() {
|
||||
var columnNames = new LinkedList<String>();
|
||||
|
||||
columnNames.add("File");
|
||||
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String separator) {
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.append(file + separator);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddInfo from(File inFile) {
|
||||
var info = new SimpleAddInfo();
|
||||
info.file = inFile.getName();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.ImageArea;
|
||||
|
||||
import ru.delkom07.improc.texture.MaskedGLCM;
|
||||
import ru.delkom07.improc.texture.MaskedGLRM;
|
||||
|
||||
public class TextureDescriptorsSet {
|
||||
MaskedGLCM maskedGLCM;
|
||||
MaskedGLRM maskedGLRM;
|
||||
|
||||
|
||||
TextureDescriptorsSet(Mat region, ImageArea imageArea) {
|
||||
MatOfPoint contour = imageArea.getContour();
|
||||
Rect currentSeedRoi = Imgproc.boundingRect(contour);
|
||||
Mat subMatBGR = region.submat(currentSeedRoi);
|
||||
|
||||
Mat subMatGray = new Mat();
|
||||
Imgproc.cvtColor(subMatBGR, subMatGray, Imgproc.COLOR_BGR2GRAY);
|
||||
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -currentSeedRoi.x, -currentSeedRoi.y);
|
||||
Mat seedMask = Mat.zeros(currentSeedRoi.height, currentSeedRoi.width, CvType.CV_8UC1);
|
||||
Imgproc.drawContours(seedMask, Arrays.asList(shiftedContour), -1, new Scalar(255), -1);
|
||||
|
||||
// Descriptors
|
||||
// GLCM
|
||||
int d = 3;
|
||||
maskedGLCM = new MaskedGLCM(d).calculate(subMatGray, seedMask);
|
||||
|
||||
// GLRM
|
||||
int maxLength = 10;
|
||||
maskedGLRM = new MaskedGLRM(maxLength).calculate(subMatGray, seedMask);
|
||||
|
||||
subMatBGR.release();
|
||||
subMatGray.release();
|
||||
|
||||
seedMask.release();
|
||||
shiftedContour.release();
|
||||
}
|
||||
|
||||
|
||||
private static MatOfPoint shiftContour(MatOfPoint contour, int shiftX, int shiftY) {
|
||||
Point[] pointArr = new Point[contour.rows()*contour.cols()];
|
||||
|
||||
int i = 0;
|
||||
for(Point srcPoint : contour.toArray()) {
|
||||
pointArr[i++] = new Point(srcPoint.x+shiftX, srcPoint.y+shiftY);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = new MatOfPoint();
|
||||
shiftedContour.fromArray(pointArr);
|
||||
return shiftedContour;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.wheatdb.seedcounter.desktop.ihandlers;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
|
||||
import ru.delkom07.improc.texture.GLCM;
|
||||
import ru.delkom07.improc.texture.GLRM;
|
||||
|
||||
public class TextureResultSaver implements ResultSaver {
|
||||
private TextureDescriptorsSet td;
|
||||
|
||||
private ResultSaver baseSaver;
|
||||
|
||||
public TextureResultSaver() {}
|
||||
|
||||
public TextureResultSaver(ResultSaver baseSaver) {
|
||||
this.baseSaver = baseSaver;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void saveResults(PrintWriter writer, Mat region, List<PlanarObject> containers, String fileName, double transformationDeviance) {
|
||||
for(int i=0; i<containers.size(); i++) {
|
||||
PlanarObject container = containers.get(i);
|
||||
|
||||
writeData(writer, region, container, i+1, transformationDeviance);
|
||||
|
||||
writer.println();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
|
||||
public void writeData(PrintWriter writer, Mat region, PlanarObject container, int seedNumber, double transformationDeviance) {
|
||||
if(null != baseSaver)
|
||||
baseSaver.writeData(writer, region, container, seedNumber, transformationDeviance);
|
||||
|
||||
td = new TextureDescriptorsSet(region, container);
|
||||
writer.print(td.maskedGLCM.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
writer.print(td.maskedGLRM.toTsvCsvString(SEPARATOR) + SEPARATOR);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void writeFileHead(PrintWriter writer) {
|
||||
if(null != baseSaver)
|
||||
baseSaver.writeFileHead(writer);
|
||||
|
||||
List<String> columnNames = new LinkedList<String>();
|
||||
|
||||
// GLCM
|
||||
columnNames.addAll(Arrays.asList(GLCM.ChNames()));
|
||||
|
||||
// GLRM
|
||||
columnNames.addAll(Arrays.asList(GLRM.ChNames()));
|
||||
|
||||
for(String columnName : columnNames) {
|
||||
writer.print(columnName + SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInfo(AddInfo info) {
|
||||
this.baseSaver.setInfo(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.RotatedRect;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.SeedDataContainer;
|
||||
|
||||
import ru.delkom07.geometry.AdditionalIndexies;
|
||||
import ru.delkom07.geometry.ContoursFunctions;
|
||||
import ru.delkom07.geometry.SimpleGeometry;
|
||||
import ru.delkom07.geometry.obj.Circle;
|
||||
import ru.delkom07.geometry.obj.Vector;
|
||||
|
||||
|
||||
/**
|
||||
* Процессор для поиска контуров зерен по бинарной маске.
|
||||
* Необходим рефакторинг, т.к. частично повторяет DetectionProcessor.
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class BinaryMaskProcessor implements Processor {
|
||||
|
||||
public List<PlanarObject> detectSeedContours(Mat mask, double pixPerMM) throws SeedCounterProcessorException {
|
||||
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
|
||||
// Find contours
|
||||
Imgproc.findContours(mask, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
|
||||
// Gauge seeds
|
||||
List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
MatOfPoint contour = contours.get(i);
|
||||
|
||||
double xScale = 1.0 / pixPerMM; // this may affect the output
|
||||
double yScale = 1.0 / pixPerMM; // this may affect the output
|
||||
|
||||
if(contour.cols()*contour.rows() > 5) { // There should be at least 5 points to fit the ellipse
|
||||
SeedDataContainer seedDataContainer = gaugeSeed(contour, xScale, yScale);
|
||||
|
||||
seedDataList.add(seedDataContainer);
|
||||
}
|
||||
}
|
||||
|
||||
return seedDataList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get info about seed on image (size in millimeters).
|
||||
* @param image - source image that containing seed.
|
||||
* @return information about seeds.
|
||||
* @throws SeedCounterException - if DetectionProcessor isn't initialized.
|
||||
*/
|
||||
private SeedDataContainer gaugeSeed(MatOfPoint contour, double xScale, double yScale) throws SeedCounterProcessorException {
|
||||
// get contour ellipse approximation
|
||||
MatOfPoint2f forFillEllipse = new MatOfPoint2f();
|
||||
contour.convertTo(forFillEllipse, CvType.CV_32FC2);
|
||||
RotatedRect rotatedRect = Imgproc.fitEllipse(forFillEllipse);
|
||||
Point[] points = new Point[4];
|
||||
rotatedRect.points(points);
|
||||
|
||||
// determine seed orientation related to sheet
|
||||
Vector seedVector = new Vector(points[0], points[1]);
|
||||
double dx = Math.abs(seedVector.x());
|
||||
double dy = Math.abs(seedVector.y());
|
||||
|
||||
double lamdaX = dx/(dx+dy);
|
||||
double lamdaY = dy/(dx+dy);
|
||||
|
||||
double lengthDistortionFactor = lamdaX*xScale + lamdaY*yScale;
|
||||
double widthDistortionFactor = lamdaX*yScale + lamdaY*xScale;
|
||||
|
||||
double seedLength = SimpleGeometry.distance(points[0], points[1])*lengthDistortionFactor;
|
||||
double seedWidth = SimpleGeometry.distance(points[1], points[2])*widthDistortionFactor;
|
||||
|
||||
// area
|
||||
double areaInPix = Imgproc.contourArea(contour, false);
|
||||
double area = areaInPix * lengthDistortionFactor*widthDistortionFactor;
|
||||
|
||||
// calculate mass center
|
||||
Rect seedRect = Imgproc.boundingRect(contour);
|
||||
double[] globalMassCenter = ContoursFunctions.massCenter(contour);
|
||||
double[] localMassCenter = new double[]{globalMassCenter[0]-seedRect.x, globalMassCenter[1]-seedRect.y};
|
||||
|
||||
// axis points
|
||||
double[] lengthAxisPoint1 = new double[]{(points[0].x+points[1].x)/2.0, (points[0].y+points[1].y)/2.0};
|
||||
double[] lengthAxisPoint2 = new double[]{(points[2].x+points[3].x)/2.0, (points[2].y+points[3].y)/2.0};
|
||||
double[] widthAxisPoint1 = new double[]{(points[1].x+points[2].x)/2.0, (points[1].y+points[2].y)/2.0};
|
||||
double[] widthAxisPoint2 = new double[]{(points[0].x+points[3].x)/2.0, (points[0].y+points[3].y)/2.0};
|
||||
|
||||
if(SimpleGeometry.distance(lengthAxisPoint1, lengthAxisPoint2)<SimpleGeometry.distance(widthAxisPoint1, widthAxisPoint2)) {
|
||||
double[] tmp1 = widthAxisPoint1;
|
||||
double[] tmp2 = widthAxisPoint2;
|
||||
|
||||
widthAxisPoint1 = lengthAxisPoint1;
|
||||
widthAxisPoint2 = lengthAxisPoint2;
|
||||
lengthAxisPoint1 = tmp1;
|
||||
lengthAxisPoint2 = tmp2;
|
||||
}
|
||||
|
||||
// calculate intersection point of length and width lines
|
||||
double[] lengthWidthIntersectPoint = SimpleGeometry.computeIntersect(lengthAxisPoint1, lengthAxisPoint2, widthAxisPoint1, widthAxisPoint2);
|
||||
lengthWidthIntersectPoint = new double[]{lengthWidthIntersectPoint[0]-seedRect.x, lengthWidthIntersectPoint[1]-seedRect.y}; // in seedRect
|
||||
|
||||
// distance from mass center to intersection point of length and width lines
|
||||
double distaceFromMassCenterToWidthLengthIntersectPoint = SimpleGeometry.distance(lengthWidthIntersectPoint, localMassCenter) * Math.sqrt(lengthDistortionFactor * widthDistortionFactor); // this may affect the output
|
||||
|
||||
SeedDataContainer seedDataContainer = new SeedDataContainer(contour, seedLength, seedWidth);
|
||||
seedDataContainer.setArea(area);
|
||||
seedDataContainer.setMassCenter(localMassCenter);
|
||||
seedDataContainer.setLengthWidthIntersectPoint(lengthWidthIntersectPoint);
|
||||
seedDataContainer.setDistaceFromMassCenterToWidthLengthIntersectPoint(distaceFromMassCenterToWidthLengthIntersectPoint);
|
||||
|
||||
seedDataContainer.setCircularityIndex(AdditionalIndexies.circularityIndex(contour));
|
||||
seedDataContainer.setRoundness(AdditionalIndexies.roundness(contour));
|
||||
seedDataContainer.setRugosity(AdditionalIndexies.rugosity(contour));
|
||||
seedDataContainer.setSolidity(AdditionalIndexies.solidity(contour));
|
||||
|
||||
Circle maxInscribed = AdditionalIndexies.getMaxInscribedCircle(contour);
|
||||
Circle minCircumscribed = AdditionalIndexies.getMinCircumscribedCircle(contour);
|
||||
|
||||
double equivalentAreaCircleRadius = Math.sqrt(areaInPix/Math.PI);
|
||||
seedDataContainer.setMaxInscribedMinCircumscribedCirclesDiametersRatio(maxInscribed.radius()/minCircumscribed.radius());
|
||||
seedDataContainer.setMaxInscribedCircleToEqAreaCircleRatio(maxInscribed.radius()/equivalentAreaCircleRadius);
|
||||
|
||||
return seedDataContainer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
/**
|
||||
* Калибровка цвета.
|
||||
* @author Komyshev.
|
||||
* v.0.1. (D)
|
||||
*/
|
||||
public class Calibration {
|
||||
// Количетсво цветов принятых на анализ калибровки
|
||||
private int count = 0;
|
||||
|
||||
private Scalar targetColor;
|
||||
private Scalar minChannels;
|
||||
private Scalar maxChannels;
|
||||
|
||||
|
||||
public Calibration() {}
|
||||
|
||||
public Calibration(Scalar targetColor) {
|
||||
this.targetColor = targetColor;
|
||||
this.minChannels = targetColor;
|
||||
this.maxChannels = targetColor;
|
||||
count=1;
|
||||
}
|
||||
|
||||
public void addColor(Scalar color) {
|
||||
if(0==count) {
|
||||
this.targetColor = color;
|
||||
this.minChannels = color;
|
||||
this.maxChannels = color;
|
||||
count=1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
double newH = (targetColor.val[0]*count + color.val[0])/(double)(count+1);
|
||||
double newS = (targetColor.val[1]*count + color.val[1])/(double)(count+1);
|
||||
double newV = (targetColor.val[2]*count + color.val[2])/(double)(count+1);
|
||||
count++;
|
||||
|
||||
targetColor = new Scalar(newH, newS, newV);
|
||||
|
||||
if(color.val[0]>maxChannels.val[0]) {
|
||||
maxChannels.val[0] = color.val[0];
|
||||
}
|
||||
if(color.val[1]>maxChannels.val[1]) {
|
||||
maxChannels.val[1] = color.val[1];
|
||||
}
|
||||
if(color.val[2]>maxChannels.val[2]) {
|
||||
maxChannels.val[2] = color.val[2];
|
||||
}
|
||||
|
||||
if(color.val[0]<minChannels.val[0]) {
|
||||
minChannels.val[0] = color.val[0];
|
||||
}
|
||||
if(color.val[1]<minChannels.val[1]) {
|
||||
minChannels.val[1] = color.val[1];
|
||||
}
|
||||
if(color.val[2]<minChannels.val[2]) {
|
||||
minChannels.val[2] = color.val[2];
|
||||
}
|
||||
}
|
||||
|
||||
public Scalar getTargetColor() {
|
||||
if(0==count) return new Scalar(255/2, 255/2, 255/2);
|
||||
return targetColor;
|
||||
}
|
||||
|
||||
public Scalar getMinChannels() {
|
||||
if(0==count) return new Scalar(0, 0, 0);
|
||||
return minChannels;
|
||||
}
|
||||
|
||||
public Scalar getMaxChannels() {
|
||||
if(0==count) return new Scalar(255, 255, 255);
|
||||
return maxChannels;
|
||||
}
|
||||
|
||||
public static Scalar RGBtoHSV(Scalar rgb, boolean inrange256) {
|
||||
double r_ = rgb.val[0]/255;
|
||||
double g_ = rgb.val[1]/255;
|
||||
double b_ = rgb.val[2]/255;
|
||||
|
||||
double cMax = (r_ > g_ ? r_ : g_) > b_ ? (r_ > g_ ? r_ : g_) : b_;
|
||||
double cMin = (r_ > g_ ? g_ : r_) > b_ ? b_ : (r_ > g_ ? g_ : r_);
|
||||
double delta = cMax - cMin;
|
||||
|
||||
double hh = 0;
|
||||
if(cMax==r_) {
|
||||
double k = ((g_-b_)/delta);
|
||||
hh = (k-( ((int)(k/6))*6))*60;
|
||||
} else
|
||||
if(cMax==g_) {
|
||||
hh = (((b_-r_)/delta)+2)*60;
|
||||
} else {
|
||||
hh = (((r_-g_)/delta)+4)*60;
|
||||
}
|
||||
|
||||
double h = inrange256 ? Math.round(hh/360f*255f) : hh;
|
||||
double s = inrange256 ? Math.round(delta/cMax*255f) : delta/cMax;
|
||||
double v = inrange256 ? Math.round(cMax*255f) : cMax;
|
||||
|
||||
return new Scalar(h, s, v);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.RotatedRect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.SeedCounterException;
|
||||
import org.wheatdb.seedcounter.SeedDataContainer;
|
||||
import org.wheatdb.seedcounter.SheetFormat;
|
||||
import org.wheatdb.seedcounter.processor.ex.QuadNotFoundException;
|
||||
import org.wheatdb.seedcounter.processor.filters.GrayBinarization;
|
||||
import org.wheatdb.seedcounter.processor.filters.HSVBinarization;
|
||||
import org.wheatdb.seedcounter.processor.filters.SeedsBinarizationForWhiteSeeds;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
import ru.delkom07.geometry.AdditionalIndexies;
|
||||
import ru.delkom07.geometry.ContoursFunctions;
|
||||
import ru.delkom07.geometry.SimpleGeometry;
|
||||
import ru.delkom07.geometry.obj.Circle;
|
||||
import ru.delkom07.geometry.obj.Vector;
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
/**
|
||||
* SeedCountProcessor produces detection and measurement of seed.
|
||||
* @version 2.0 (20.12.23)
|
||||
*/
|
||||
public class DetectionProcessor implements Processor {
|
||||
protected double MIN_SIDE_OF_QUAD = 0.33; // ~ minimal side size of quad relation by image height
|
||||
|
||||
// in millimeters
|
||||
protected double mmInBigSideOfSheet = 297; // A4 for default
|
||||
protected double mmInSmallSideOfSheet = 210; // A4 for default
|
||||
|
||||
double minSeedLength = 4; // !!!!!!!!!!!!!!!! SeedCounterApplication.config.getMinSeedLength()
|
||||
double maxSeedLength = 14; // !!!!!!!!!!!!!!!! SeedCounterApplication.config.getMaxSeedLength()
|
||||
|
||||
|
||||
// paddings for sheet transformation
|
||||
protected int widthPadding = -1;
|
||||
protected int heightPadding = -1;
|
||||
|
||||
public DetectionProcessor() {
|
||||
setSheetFormat(SheetFormat.A4);
|
||||
}
|
||||
|
||||
|
||||
public DetectionProcessor(SheetFormat sheetFormat) {
|
||||
setSheetFormat(sheetFormat);
|
||||
}
|
||||
|
||||
public void setSheetFormat(SheetFormat sheetFormat) {
|
||||
mmInBigSideOfSheet = sheetFormat.getBigSideSize();
|
||||
mmInSmallSideOfSheet = sheetFormat.getSmallSideSize();
|
||||
}
|
||||
|
||||
|
||||
// -- Detection --
|
||||
/**
|
||||
* Detection of white sheet.
|
||||
* @param image - source image.
|
||||
* @return detected quad of white sheet.
|
||||
* @throws SeedCounterProcessorException
|
||||
*/
|
||||
public Quad detectQuad(Mat image) throws QuadNotFoundException {
|
||||
// int width = image.cols();
|
||||
// int height = image.rows();
|
||||
|
||||
Mat gray = new Mat(image.rows(), image.cols(), CvType.CV_8UC1);
|
||||
Imgproc.cvtColor(image, gray, Imgproc.COLOR_RGBA2GRAY);
|
||||
Mat hulled = new Mat(image.rows(), image.cols(), CvType.CV_8UC1);
|
||||
Imgproc.threshold(gray, hulled, 120, 255, Imgproc.THRESH_BINARY ); /// 185!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
gray.release();
|
||||
|
||||
List<MatOfPoint> contours = new LinkedList<MatOfPoint>();
|
||||
Imgproc.findContours(hulled.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
|
||||
|
||||
MatOfPoint contour = ContoursFunctions.getMaxAreaContour(contours);
|
||||
|
||||
MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray());
|
||||
double perimeter = Imgproc.arcLength(contour2f, true);
|
||||
|
||||
MatOfPoint2f approx2f = new MatOfPoint2f();
|
||||
Imgproc.approxPolyDP(contour2f, approx2f, 0.02 * perimeter, true);
|
||||
|
||||
if(approx2f.size().height*approx2f.size().width==4) {
|
||||
return new Quad(approx2f.toArray());
|
||||
} else {
|
||||
throw new QuadNotFoundException(String.format("DetectionProcessor: the approximation has %s vertices.", approx2f.size().height*approx2f.size().width));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Detect contours of seeds in image.
|
||||
* @param image - source image.
|
||||
* @return list of contours.
|
||||
*/
|
||||
public List<PlanarObject> detectSeedContours(Mat image, double pixPerMM) throws SeedCounterProcessorException {
|
||||
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
|
||||
HSVBinarization seedContourBinarization = new SeedsBinarizationForWhiteSeeds();
|
||||
Mat hulled = seedContourBinarization.apply(image);
|
||||
|
||||
// Find contours
|
||||
Imgproc.findContours(hulled, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
hulled.release();
|
||||
|
||||
// Gauge seeds
|
||||
List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
MatOfPoint contour = contours.get(i);
|
||||
|
||||
double xScale = 1.0 / pixPerMM; // this may affect the output
|
||||
double yScale = 1.0 / pixPerMM; // this may affect the output
|
||||
|
||||
if(contour.cols()*contour.rows() > 5) { // There should be at least 5 points to fit the ellipse
|
||||
SeedDataContainer seedDataContainer = gaugeSeed(contour, xScale, yScale);
|
||||
|
||||
double seedLen = seedDataContainer.getLenght();
|
||||
if(minSeedLength<seedLen && seedLen<maxSeedLength) { // ElementProcessor.filterCircularContours(...)
|
||||
seedDataList.add(seedDataContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seedDataList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detect contours of seeds in image.
|
||||
* @param image - source image.
|
||||
* @return list of contours.
|
||||
*/
|
||||
public List<PlanarObject> detectSeedContours(Mat image) throws SeedCounterProcessorException {
|
||||
// init size
|
||||
//prepareSize(image.cols(), image.rows());
|
||||
if(-1 == widthPadding || -1 == heightPadding)
|
||||
throw new SeedCounterProcessorException("Padding isn't initialized.");
|
||||
|
||||
|
||||
int width = image.cols();
|
||||
int height = image.rows();
|
||||
|
||||
// Clean
|
||||
GrayBinarization binarization = new GrayBinarization(true, width*height/(210*297)); // ?????????????????????????????
|
||||
Mat hulled = binarization.apply(image);
|
||||
|
||||
// Find contours
|
||||
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
Imgproc.findContours(hulled, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
|
||||
/* // disabled for desktop
|
||||
if(SeedCounterApplication.config.getUseWatershed()) {
|
||||
double maxSeedAreaInMM2 = 78;
|
||||
double maxSeedAreaInPix = maxSeedAreaInMM2 * ((width+2*widthPadding)/mmInBigSideOfSheet);
|
||||
contours = clarifyContours(contours, maxSeedAreaInPix);
|
||||
}
|
||||
*/
|
||||
|
||||
double pixInMM = (width+2*widthPadding)/mmInBigSideOfSheet;
|
||||
|
||||
// Gauge seeds
|
||||
List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
MatOfPoint contour = contours.get(i);
|
||||
|
||||
// sheet size in pixels
|
||||
double sheetWidth = width+widthPadding*2;
|
||||
double sheetHeight = height+heightPadding*2;
|
||||
|
||||
// scales of X and Y pixels coordinates
|
||||
double yScale = mmInSmallSideOfSheet/sheetHeight;
|
||||
double xScale = mmInBigSideOfSheet/sheetWidth;
|
||||
|
||||
if(contour.cols()*contour.rows() > 5) { // There should be at least 5 points to fit the ellipse
|
||||
SeedDataContainer seedDataContainer = gaugeSeed(contour, xScale, yScale);
|
||||
|
||||
double seedLen = seedDataContainer.getLenght();
|
||||
if(minSeedLength<seedLen && seedLen<maxSeedLength) { // ElementProcessor.filterCircularContours(...)
|
||||
seedDataList.add(seedDataContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seedDataList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get info about seed on image (size in millimeters).
|
||||
* @param image - source image that containing seed.
|
||||
* @return information about seeds.
|
||||
* @throws SeedCounterException - if DetectionProcessor isn't initialized.
|
||||
*/
|
||||
private SeedDataContainer gaugeSeed(MatOfPoint contour, double xScale, double yScale) throws SeedCounterProcessorException {
|
||||
// get contour ellipse approximation
|
||||
MatOfPoint2f forFillEllipse = new MatOfPoint2f();
|
||||
contour.convertTo(forFillEllipse, CvType.CV_32FC2);
|
||||
RotatedRect rotatedRect = Imgproc.fitEllipse(forFillEllipse);
|
||||
Point[] points = new Point[4];
|
||||
rotatedRect.points(points);
|
||||
|
||||
// determine seed orientation related to sheet
|
||||
Vector seedVector = new Vector(points[0], points[1]);
|
||||
double dx = Math.abs(seedVector.x());
|
||||
double dy = Math.abs(seedVector.y());
|
||||
|
||||
double lamdaX = dx/(dx+dy);
|
||||
double lamdaY = dy/(dx+dy);
|
||||
|
||||
double lengthDistortionFactor = lamdaX*xScale + lamdaY*yScale;
|
||||
double widthDistortionFactor = lamdaX*yScale + lamdaY*xScale;
|
||||
|
||||
double seedLength = SimpleGeometry.distance(points[0], points[1])*lengthDistortionFactor;
|
||||
double seedWidth = SimpleGeometry.distance(points[1], points[2])*widthDistortionFactor;
|
||||
|
||||
// area
|
||||
double areaInPix = Imgproc.contourArea(contour, false);
|
||||
double area = areaInPix * lengthDistortionFactor*widthDistortionFactor;
|
||||
|
||||
// calculate mass center
|
||||
Rect seedRect = Imgproc.boundingRect(contour);
|
||||
double[] globalMassCenter = ContoursFunctions.massCenter(contour);
|
||||
double[] localMassCenter = new double[]{globalMassCenter[0]-seedRect.x, globalMassCenter[1]-seedRect.y};
|
||||
|
||||
// axis points
|
||||
double[] lengthAxisPoint1 = new double[]{(points[0].x+points[1].x)/2.0, (points[0].y+points[1].y)/2.0};
|
||||
double[] lengthAxisPoint2 = new double[]{(points[2].x+points[3].x)/2.0, (points[2].y+points[3].y)/2.0};
|
||||
double[] widthAxisPoint1 = new double[]{(points[1].x+points[2].x)/2.0, (points[1].y+points[2].y)/2.0};
|
||||
double[] widthAxisPoint2 = new double[]{(points[0].x+points[3].x)/2.0, (points[0].y+points[3].y)/2.0};
|
||||
|
||||
if(SimpleGeometry.distance(lengthAxisPoint1, lengthAxisPoint2)<SimpleGeometry.distance(widthAxisPoint1, widthAxisPoint2)) {
|
||||
double[] tmp1 = widthAxisPoint1;
|
||||
double[] tmp2 = widthAxisPoint2;
|
||||
|
||||
widthAxisPoint1 = lengthAxisPoint1;
|
||||
widthAxisPoint2 = lengthAxisPoint2;
|
||||
lengthAxisPoint1 = tmp1;
|
||||
lengthAxisPoint2 = tmp2;
|
||||
}
|
||||
|
||||
// calculate intersection point of length and width lines
|
||||
double[] lengthWidthIntersectPoint = SimpleGeometry.computeIntersect(lengthAxisPoint1, lengthAxisPoint2, widthAxisPoint1, widthAxisPoint2);
|
||||
lengthWidthIntersectPoint = new double[]{lengthWidthIntersectPoint[0]-seedRect.x, lengthWidthIntersectPoint[1]-seedRect.y}; // in seedRect
|
||||
|
||||
// distance from mass center to intersection point of length and width lines
|
||||
double distaceFromMassCenterToWidthLengthIntersectPoint = SimpleGeometry.distance(lengthWidthIntersectPoint, localMassCenter) * Math.sqrt(lengthDistortionFactor * widthDistortionFactor); // this may affect the output
|
||||
|
||||
SeedDataContainer seedDataContainer = new SeedDataContainer(contour, seedLength, seedWidth);
|
||||
seedDataContainer.setArea(area);
|
||||
seedDataContainer.setMassCenter(localMassCenter);
|
||||
seedDataContainer.setLengthWidthIntersectPoint(lengthWidthIntersectPoint);
|
||||
seedDataContainer.setDistaceFromMassCenterToWidthLengthIntersectPoint(distaceFromMassCenterToWidthLengthIntersectPoint);
|
||||
|
||||
seedDataContainer.setCircularityIndex(AdditionalIndexies.circularityIndex(contour));
|
||||
seedDataContainer.setRoundness(AdditionalIndexies.roundness(contour));
|
||||
seedDataContainer.setRugosity(AdditionalIndexies.rugosity(contour));
|
||||
seedDataContainer.setSolidity(AdditionalIndexies.solidity(contour));
|
||||
|
||||
Circle maxInscribed = AdditionalIndexies.getMaxInscribedCircle(contour);
|
||||
Circle minCircumscribed = AdditionalIndexies.getMinCircumscribedCircle(contour);
|
||||
|
||||
double equivalentAreaCircleRadius = Math.sqrt(areaInPix/Math.PI);
|
||||
seedDataContainer.setMaxInscribedMinCircumscribedCirclesDiametersRatio(maxInscribed.radius()/minCircumscribed.radius());
|
||||
seedDataContainer.setMaxInscribedCircleToEqAreaCircleRatio(maxInscribed.radius()/equivalentAreaCircleRadius);
|
||||
|
||||
return seedDataContainer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private List<MatOfPoint> clarifyContours(List<MatOfPoint> contours, double maxContourArea) {
|
||||
List<MatOfPoint> clarifyedContours = new LinkedList<MatOfPoint>();
|
||||
|
||||
for(MatOfPoint contour : contours) {
|
||||
boolean isGlue = isGlueContours(contour);
|
||||
|
||||
double contourArea = Imgproc.contourArea(contour);
|
||||
|
||||
if(isGlue || contourArea > maxContourArea) {
|
||||
Rect rect = Imgproc.boundingRect(contour);
|
||||
Rect roi = new Rect(rect.x, rect.y, rect.width, rect.height);
|
||||
|
||||
clarifyedContours.addAll(clarifyContour(contour, roi));
|
||||
} else {
|
||||
clarifyedContours.add(contour);
|
||||
}
|
||||
}
|
||||
|
||||
return clarifyedContours;
|
||||
}
|
||||
|
||||
private List<MatOfPoint> clarifyContour(MatOfPoint contour, Rect roi) {
|
||||
MatOfPoint shiftedContour = shiftContour(contour, -roi.x, -roi.y);
|
||||
|
||||
Mat imprint = Mat.zeros(roi.size(), CvType.CV_8UC3);
|
||||
Imgproc.drawContours(imprint, Arrays.asList(shiftedContour), -1, new Scalar(255, 255, 255), -1);
|
||||
|
||||
List<MatOfPoint> separatedAndShiftedContours = watershedContours(imprint);
|
||||
List<MatOfPoint> separatedContours = new ArrayList<MatOfPoint>();
|
||||
|
||||
if(null == separatedAndShiftedContours) {
|
||||
separatedContours.add(contour);
|
||||
return separatedContours;
|
||||
}
|
||||
|
||||
for(MatOfPoint separatedAndShiftedContour : separatedAndShiftedContours) {
|
||||
separatedContours.add(shiftContour(separatedAndShiftedContour, roi.x, roi.y));
|
||||
}
|
||||
|
||||
return separatedContours;
|
||||
}
|
||||
|
||||
|
||||
private MatOfPoint shiftContour(MatOfPoint contour, int shiftX, int shiftY) {
|
||||
Point[] pointArr = new Point[contour.rows()*contour.cols()];
|
||||
|
||||
int i = 0;
|
||||
for(Point srcPoint : contour.toArray()) {
|
||||
pointArr[i++] = new Point(srcPoint.x+shiftX, srcPoint.y+shiftY);
|
||||
}
|
||||
|
||||
MatOfPoint shiftedContour = new MatOfPoint();
|
||||
shiftedContour.fromArray(pointArr);
|
||||
return shiftedContour;
|
||||
}
|
||||
|
||||
|
||||
private static List<MatOfPoint> watershedContours(Mat imprint) {
|
||||
Mat binarized = new Mat();
|
||||
Imgproc.cvtColor(imprint, binarized, Imgproc.COLOR_RGBA2GRAY);
|
||||
|
||||
// noise removal
|
||||
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
|
||||
Mat opening = new Mat();
|
||||
Imgproc.morphologyEx(binarized, opening, Imgproc.MORPH_OPEN, kernel);
|
||||
|
||||
// sure background area
|
||||
Mat sureBG = new Mat();
|
||||
Imgproc.dilate(opening, sureBG, kernel);
|
||||
|
||||
// finding sure foreground area
|
||||
Mat distTransform = new Mat();
|
||||
Imgproc.distanceTransform(opening, distTransform, Imgproc.CV_DIST_L2, 5);
|
||||
Mat sureFG = new Mat();
|
||||
//double epsilon = 0.55+((SeedCounterApplication.config.getWatershedSensivity()/100.0)*0.7)*(Core.minMaxLoc(distTransform)).maxVal; //from 0.5 to 0.8 // optimal = 0.65
|
||||
double epsilon = 0.55+((71/100.0)*0.7)*(Core.minMaxLoc(distTransform)).maxVal; //from 0.5 to 0.8 // optimal = 0.65
|
||||
|
||||
sureFG.convertTo(sureFG, CvType.CV_8UC1);
|
||||
Mat unknown = new Mat();
|
||||
Core.subtract(sureBG, sureFG, unknown);
|
||||
|
||||
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
Imgproc.findContours(sureFG.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
|
||||
if(contours.size() < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Mat markers = Mat.zeros(binarized.size(), CvType.CV_32SC1);
|
||||
markers.setTo(new Scalar(1));
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
Imgproc.drawContours(markers, Arrays.asList(contours.get(i)), -1, new Scalar(i+2), 1);
|
||||
Imgproc.drawContours(markers, Arrays.asList(contours.get(i)), -1, new Scalar(i+2), -1);
|
||||
}
|
||||
|
||||
Mat unknownInv = new Mat(unknown.size(), unknown.type());
|
||||
Core.bitwise_not(unknown, unknownInv);
|
||||
unknownInv.convertTo(unknownInv, CvType.CV_32SC1);
|
||||
Core.bitwise_and(markers, unknownInv, markers, unknown);
|
||||
|
||||
Imgproc.watershed(imprint, markers);
|
||||
|
||||
return separateMarkers(markers, contours.size());
|
||||
}
|
||||
|
||||
|
||||
private static List<MatOfPoint> separateMarkers(Mat markers, int markersNumber) {
|
||||
List<MatOfPoint> separatedContours = new ArrayList<MatOfPoint>();
|
||||
|
||||
for(int k=0; k<markersNumber; k++) {
|
||||
Mat imprint = Mat.zeros(markers.size(), CvType.CV_8UC1);
|
||||
|
||||
for(int i=0; i<markers.rows(); i++) {
|
||||
for(int j=0; j<markers.cols(); j++) {
|
||||
double pix = markers.get(i, j)[0];
|
||||
if((k+2) == pix) {
|
||||
imprint.put(i, j, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<MatOfPoint> contour = new ArrayList<MatOfPoint>();
|
||||
Imgproc.findContours(imprint, contour, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
separatedContours.addAll(contour);
|
||||
|
||||
imprint.release();
|
||||
}
|
||||
|
||||
return separatedContours;
|
||||
}
|
||||
|
||||
|
||||
private static boolean isGlueContours(MatOfPoint contour) {
|
||||
boolean isGlueContours = false;
|
||||
|
||||
MatOfPoint2f contour2f = new MatOfPoint2f( contour.toArray() );
|
||||
|
||||
double contourLength = Imgproc.arcLength(contour2f, true);
|
||||
double approxRadius = contourLength/(2*Math.PI);
|
||||
double[] massCenter = ContoursFunctions.massCenter(contour);
|
||||
|
||||
//int vectLength = (int)contourLength/64 > 4 ? (int)contourLength/64 : 4;
|
||||
int vectLength = 8;
|
||||
List<Vector> vectors = ContoursFunctions.getVectors(contour, vectLength);
|
||||
List<Pair<Point, Vector>> bendListIndxs = ContoursFunctions.getBendsIndxs(vectors);
|
||||
|
||||
{
|
||||
short toCenterVectorsCount = 0;
|
||||
if(bendListIndxs.size() >= 2) {
|
||||
for(Pair<Point, Vector> bend : bendListIndxs) {
|
||||
Vector vector = bend.getRight();
|
||||
|
||||
double val = SimpleGeometry.fromLineToPointDistance(vector, massCenter);
|
||||
if(val < approxRadius/2.0) {
|
||||
toCenterVectorsCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(toCenterVectorsCount >= 3) {
|
||||
isGlueContours = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(bendListIndxs.size()>=2) {
|
||||
for(int i=0; i<bendListIndxs.size(); i++) {
|
||||
for(int j=i+1; j<bendListIndxs.size(); j++) {
|
||||
Vector v1 = bendListIndxs.get(i).getRight();
|
||||
Vector v2 = bendListIndxs.get(j).getRight();
|
||||
|
||||
|
||||
if(SimpleGeometry.angleInDegree(v1, v2) > 140) {
|
||||
if(SimpleGeometry.fromLineToPointDistance(v1, v2.p1()) < approxRadius/4.0) {
|
||||
isGlueContours = true;
|
||||
}
|
||||
|
||||
if(SimpleGeometry.fromLineToPointDistance(v2, v1.p1()) < approxRadius/4.0) {
|
||||
isGlueContours = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isGlueContours;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort contours by place on sheet in text order.
|
||||
* @param contours
|
||||
* @return
|
||||
*/
|
||||
public static List<MatOfPoint> sortSeedContours(List<MatOfPoint> contours, final int stringHeight) {
|
||||
List<MatOfPoint> sorted = new ArrayList<MatOfPoint>();
|
||||
int counts = contours.size();
|
||||
|
||||
while(counts != sorted.size()) {
|
||||
MatOfPoint next = Collections.min(contours, new Comparator<MatOfPoint>() {
|
||||
@Override
|
||||
public int compare(MatOfPoint o1, MatOfPoint o2) {
|
||||
double[] p1 = o1.get(0, 0);
|
||||
double[] p2 = o2.get(0, 0);
|
||||
|
||||
if(p1[1]<p2[1] && (p2[1]-p1[1])>stringHeight) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(p1[0]<p2[0]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
contours.remove(next);
|
||||
sorted.add(next);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
||||
// -- Extraction --
|
||||
/**
|
||||
* Getting transformed field of image by quad.
|
||||
* @param image - source image.
|
||||
* @param quad - quad for transformation.
|
||||
* @return transformed field.
|
||||
*/
|
||||
public Mat getTransformedField(Mat image, Quad quad) {
|
||||
widthPadding = (int) (image.cols()*0.02);
|
||||
heightPadding = (int) (image.rows()*0.02);
|
||||
|
||||
// init size
|
||||
//prepareSize(image.cols(), image.rows());
|
||||
|
||||
// Define the destination image
|
||||
Mat transformed = new Mat(image.rows(), image.cols(), image.type());
|
||||
|
||||
// Corners of the destination image
|
||||
Point[] quad_pts = new Point[4];
|
||||
quad_pts[0] = new Point(0, 0);
|
||||
quad_pts[1] = new Point(transformed.cols(), 0);
|
||||
quad_pts[2] = new Point(transformed.cols(), transformed.rows());
|
||||
quad_pts[3] = new Point(0, transformed.rows());
|
||||
|
||||
// Get transformation matrix
|
||||
Mat transmtx = Imgproc.getPerspectiveTransform(new MatOfPoint2f(quad.getPoints()), new MatOfPoint2f(quad_pts));
|
||||
|
||||
// Apply perspective transformation
|
||||
Imgproc.warpPerspective(image, transformed, transmtx, transformed.size());
|
||||
|
||||
transformed = transformed.submat(heightPadding, transformed.rows()-heightPadding,
|
||||
widthPadding, transformed.cols()-widthPadding);
|
||||
|
||||
return transformed;
|
||||
}
|
||||
|
||||
|
||||
public List<RotatedRect> getSeedsRotatedRects(List<MatOfPoint> contours) {
|
||||
List<RotatedRect> quads = new ArrayList<RotatedRect>();
|
||||
for(int i=0; i<contours.size(); i++) {
|
||||
MatOfPoint2f forFillEllipse = new MatOfPoint2f();
|
||||
contours.get(i).convertTo(forFillEllipse, CvType.CV_32FC2);
|
||||
RotatedRect rotatedRect = Imgproc.fitEllipse(forFillEllipse);
|
||||
|
||||
quads.add(rotatedRect);
|
||||
}
|
||||
|
||||
return quads;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get four lines of quad from given lines.
|
||||
* @param lines - given lines.
|
||||
* @return lines of quad.
|
||||
*/
|
||||
//private static Quad getQuad(Mat lines) {
|
||||
// // Filter corners
|
||||
// Point[] corners = new Point[4];
|
||||
//
|
||||
// if(ElementsProcessor.nearAngle(lines.get(0, 0), lines.get(0, 2))) {
|
||||
// corners[0] = ElementsProcessor.computeIntersectPoint(lines.get(0, 0), lines.get(0, 1));
|
||||
// corners[1] = ElementsProcessor.computeIntersectPoint(lines.get(0, 1), lines.get(0, 2));
|
||||
// corners[2] = ElementsProcessor.computeIntersectPoint(lines.get(0, 2), lines.get(0, 3));
|
||||
// corners[3] = ElementsProcessor.computeIntersectPoint(lines.get(0, 3), lines.get(0, 0));
|
||||
// } else
|
||||
// if (ElementsProcessor.nearAngle(lines.get(0, 0), lines.get(0, 1))) {
|
||||
// corners[0] = ElementsProcessor.computeIntersectPoint(lines.get(0, 0), lines.get(0, 2));
|
||||
// corners[1] = ElementsProcessor.computeIntersectPoint(lines.get(0, 2), lines.get(0, 1));
|
||||
// corners[2] = ElementsProcessor.computeIntersectPoint(lines.get(0, 1), lines.get(0, 3));
|
||||
// corners[3] = ElementsProcessor.computeIntersectPoint(lines.get(0, 3), lines.get(0, 0));
|
||||
// } else {
|
||||
// corners[0] = ElementsProcessor.computeIntersectPoint(lines.get(0, 0), lines.get(0, 2));
|
||||
// corners[1] = ElementsProcessor.computeIntersectPoint(lines.get(0, 2), lines.get(0, 3));
|
||||
// corners[2] = ElementsProcessor.computeIntersectPoint(lines.get(0, 3), lines.get(0, 1));
|
||||
// corners[3] = ElementsProcessor.computeIntersectPoint(lines.get(0, 1), lines.get(0, 0));
|
||||
// }
|
||||
//
|
||||
// ElementsProcessor.sortQuadCorners(corners);
|
||||
// Quad quad = new Quad(corners[0], corners[1], corners[2], corners[3]);
|
||||
// return quad;
|
||||
//}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.subjects.Quad;
|
||||
|
||||
public class Drawing {
|
||||
// Colors
|
||||
private Scalar lineColor = new Scalar(0, 0, 255); // red
|
||||
private Scalar quadColor = new Scalar(255, 0, 0); // blue
|
||||
private Scalar seedColor = new Scalar(0, 255, 0); // green
|
||||
|
||||
|
||||
/**
|
||||
* Draw quad on image.
|
||||
* @param image - input image.
|
||||
* @param quad - quad for drawing.
|
||||
* @param drawCorners - is draw corners of quad?
|
||||
* @return input image with drawing quad.
|
||||
*/
|
||||
public void drawQuad(Mat image, Quad quad, boolean drawCorners) {
|
||||
Imgproc.line(image, quad.tl(), quad.tr(), quadColor, 3);
|
||||
Imgproc.line(image, quad.bl(), quad.br(), quadColor, 3);
|
||||
Imgproc.line(image, quad.tl(), quad.bl(), quadColor, 3);
|
||||
Imgproc.line(image, quad.tr(), quad.br(), quadColor, 3);
|
||||
|
||||
if(drawCorners) {
|
||||
Imgproc.circle(image, quad.tl(), 5, quadColor);
|
||||
Imgproc.circle(image, quad.tr(), 5, quadColor);
|
||||
Imgproc.circle(image, quad.br(), 5, quadColor);
|
||||
Imgproc.circle(image, quad.bl(), 5, quadColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Draw lines on image.
|
||||
* @param image - input image.
|
||||
* @param lines - lines for drawing.
|
||||
*/
|
||||
public void drawLines(Mat image, Mat lines) {
|
||||
for(int i=0; i<lines.cols(); i++) {
|
||||
double[] line = lines.get(0, i);
|
||||
Imgproc.line(image, new Point(line[0], line[1]), new Point(line[2], line[3]), lineColor, 3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw contours on image.
|
||||
* @param img - input image.
|
||||
* @param contours - contours for drawing.
|
||||
*/
|
||||
public void drawContours(Mat img, List<MatOfPoint> contours) {
|
||||
Imgproc.drawContours(img, contours, -1, seedColor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
/**
|
||||
* DELETE IT!
|
||||
* Processor for elementary operations with points, lines and etc.
|
||||
* @author Komyshev
|
||||
* v.1.0. (20.12.23)
|
||||
*/
|
||||
public class ElementsProcessor {
|
||||
/**
|
||||
* Is targeting point near to middle normal of vector defined by two points.
|
||||
* @param p1 - first point.
|
||||
* @param p2 - second point.
|
||||
* @param p - targeting point.
|
||||
* @param sizeOfRegion - threshold of close.
|
||||
* @return true if targeting point lies on middle normal of given points and false otherwise.
|
||||
* @deprecated !!! Error: function calculates incorrect distance from point to line: |f(Point.x) - Point.y| is not correct distance.
|
||||
*/
|
||||
// @Deprecated
|
||||
// private static boolean isOnMiddleNormal(Point p1, Point p2, Point p, double sizeOfRegion) {
|
||||
// // Middle point
|
||||
// double xMdl = (p1.x+p2.x)/2.0;
|
||||
// double yMdl = (p1.y+p2.y)/2.0;
|
||||
//
|
||||
// // y(x) = k*x + b;
|
||||
// double k = (double)(p2.x-p1.x)/(double)(p2.y-p1.y);
|
||||
// double b = yMdl + k*(double)xMdl;
|
||||
// double y = k*(double)p.x + b; //??
|
||||
//
|
||||
// // y - value of function in p.x - should be near to p.y
|
||||
// if(Math.abs(p.y - y)<sizeOfRegion)
|
||||
// return true;
|
||||
// else
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Is targeting point near to middle normal of vector defined by two points.
|
||||
* @param p1 - first point.
|
||||
* @param p2 - second point.
|
||||
* @param p - targeting point.
|
||||
* @param sizeOfRegion - threshold of close.
|
||||
* @return true if targeting point lies on middle normal of given points and false otherwise.
|
||||
* @deprecated !!! Error: function calculates incorrect distance from point to line: |f(Point.x) - Point.y| is not correct distance.
|
||||
*/
|
||||
// @Deprecated
|
||||
// private static boolean isOnMiddleNormal(double[] p1, double[] p2, double[] p, double sizeOfRegion) {
|
||||
// // Middle point
|
||||
// double xMdl = (p1[0]+p2[0])/2.0;
|
||||
// double yMdl = (p1[1]+p2[1])/2.0;
|
||||
//
|
||||
// // y(x) = k*x + b;
|
||||
// double k = (double)(p2[0]-p1[0])/(double)(p2[1]-p1[1]);
|
||||
// double b = yMdl + k*(double)xMdl;
|
||||
// double y = k*(double)p[0] + b; //??
|
||||
//
|
||||
// // y - value of function in p.x - should be near to p.y
|
||||
// if(Math.abs(p[1] - y)<sizeOfRegion)
|
||||
// return true;
|
||||
// else
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Filter out contours that are larger or smaller than the specified size values.
|
||||
* @param contours
|
||||
* @param minSeedLength
|
||||
* @param maxSeedLength
|
||||
* @return
|
||||
*/
|
||||
// private static List<MatOfPoint> filterCircularContours(List<MatOfPoint> contours, double minSeedLength, double maxSeedLength) {
|
||||
// List<MatOfPoint> filtered = new ArrayList<MatOfPoint>();
|
||||
//
|
||||
// for(MatOfPoint contour : contours) {
|
||||
// // Filter big length object
|
||||
// if(containsTwoDistantPoints(contour, maxSeedLength))
|
||||
// continue;
|
||||
//
|
||||
// // Filter small length object
|
||||
// if(!containsTwoDistantPoints(contour, minSeedLength))
|
||||
// continue;
|
||||
//
|
||||
// filtered.add(contour);
|
||||
// }
|
||||
//
|
||||
// return filtered;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Calculate whether mat contains two point with distance over threshold.
|
||||
* @param mat - matrix of points.
|
||||
* @param thres - threshold of distance.
|
||||
* @return true if contains, and false otherwise.
|
||||
*/
|
||||
// private static boolean containsTwoDistantPoints(MatOfPoint mat, double thres) {
|
||||
// for(int i=0; i<mat.rows(); i++) {
|
||||
// for(int j=0; j<mat.cols(); j++) {
|
||||
// for(int ii=0; ii<mat.rows(); ii++) {
|
||||
// for(int jj=0; jj<mat.cols(); jj++) {
|
||||
// if(SimpleGeometry.distance(mat.get(i, j), mat.get(ii, jj)) > thres) return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Find two points which have max distance between them.
|
||||
* @param points - mat of points for search.
|
||||
* @return two points which max distance between them.
|
||||
*/
|
||||
// private static double[][] findMaxDistancePoints(MatOfPoint points) {
|
||||
// double maxDistance = 0;
|
||||
// double[][] maxPoints = new double[2][2];
|
||||
//
|
||||
// for(int i=0; i<points.rows(); i++) {
|
||||
// for(int j=i+1; j<points.rows(); j++) {
|
||||
// double[] p1 = points.get(i, 0);
|
||||
// double[] p2 = points.get(j, 0);
|
||||
//
|
||||
// double distance = SimpleGeometry.distance(p1, p2);
|
||||
// if(distance>maxDistance) {
|
||||
// maxDistance = distance;
|
||||
// maxPoints[0] = p1;
|
||||
// maxPoints[1] = p2;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return maxPoints;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Calculate whether list contains two point with distance over threshold.
|
||||
* @param list - list of points.
|
||||
* @param thres - threshold of distance.
|
||||
* @return true if contains, and false otherwise.
|
||||
*/
|
||||
// private static boolean containsTwoDistantPoints(List<double[]> list, double thres) {
|
||||
// for(int i=0; i<list.size(); i++) {
|
||||
// for(int j=i+1; j<list.size(); j++) {
|
||||
// if(SimpleGeometry.distance(list.get(i), list.get(j))>thres) return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Calculate whether two mats contains two near point.
|
||||
* @param mat1 - first mat of point.
|
||||
* @param mat2 - second mat of point.
|
||||
* @param thres - threshold of distance.
|
||||
* @return true if contains two near point, and false otherwise.
|
||||
*/
|
||||
// private static boolean containsNearPoints(MatOfPoint mat1, MatOfPoint mat2, double thres) {
|
||||
// // for each point in mat1
|
||||
// for(int i=0; i<mat1.rows(); i++) {
|
||||
// for(int j=0; j<mat1.cols(); j++) {
|
||||
// // for each point in mat1
|
||||
// for(int ii=0; ii<mat2.rows(); ii++) {
|
||||
// for(int jj=0; jj<mat2.cols(); jj++) {
|
||||
//
|
||||
// if(SimpleGeometry.distance(mat1.get(i, j), mat2.get(ii, jj))<thres)
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
|
||||
/**
|
||||
* Интерфейс области изображения.
|
||||
* Область изображения характеризуется контуром.
|
||||
* Для такой области можно вычислять цветовые характеристики.
|
||||
* @author Komyshev
|
||||
*/
|
||||
|
||||
public interface ImageArea {
|
||||
public MatOfPoint getContour();
|
||||
|
||||
public MatOfPoint2f getContourApprox();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import org.wheatdb.seedcounter.WritableData;
|
||||
|
||||
public interface PlanarObject extends WritableData, ImageArea {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
public interface Processor {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
public class SeedCounterProcessorException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SeedCounterProcessorException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SeedCounterProcessorException(String mes) {
|
||||
super(mes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.wheatdb.seedcounter.processor;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class Utils {
|
||||
/**
|
||||
* Do action for each element of mat.
|
||||
* @param mat - matrix of element.
|
||||
* @param act - action.
|
||||
*/
|
||||
// public static void forEach(Mat mat, Action act) {
|
||||
// for(int i=0; i<mat.rows(); i++) {
|
||||
// for(int j=0; j<mat.cols(); j++) {
|
||||
// double[] point = mat.get(i, j);
|
||||
// act.doAction(point);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
//interface Action {
|
||||
// void doAction(double[] point);
|
||||
//}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.wheatdb.seedcounter.processor.ex;
|
||||
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
|
||||
public class QuadNotFoundException extends SeedCounterProcessorException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public QuadNotFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public QuadNotFoundException(String mes) {
|
||||
super(mes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import org.opencv.calib3d.Calib3d;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
|
||||
public class GrayBinarization implements IFilter {
|
||||
// Outer parameters:
|
||||
private boolean inv = true;
|
||||
|
||||
public static int blockSize = 3;
|
||||
public static int C = 1;
|
||||
|
||||
public static float medianBlur = 0; //8*log11(x/22530,7)
|
||||
public static int maxSpecklesSize = 0;
|
||||
|
||||
private final double maxDiff = 20;
|
||||
//private Size objSizes;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public GrayBinarization(boolean withSeeds, double dpmm) {
|
||||
//this.withSeeds = withSeeds;
|
||||
//this.dpmm = dpmm;
|
||||
|
||||
|
||||
// Recalculate parameters
|
||||
//int resolution = height*width;
|
||||
if(dpmm<0.340339907) { //~ 17649px(to 26473) 0.22671156004 dpmm
|
||||
blockSize = withSeeds ? 5 : 3;
|
||||
C = withSeeds ? 9 : 15;
|
||||
medianBlur = withSeeds ? 0 : 0;
|
||||
maxSpecklesSize = withSeeds ? 4 : 9;
|
||||
} else
|
||||
if(dpmm<1.362089145) { //~ 70818px (to 106227) 0.91008497675 dpmm
|
||||
blockSize = withSeeds ? 7 : 5;
|
||||
C = withSeeds ? 11 : 7;
|
||||
medianBlur = withSeeds ? 0 : 3;
|
||||
maxSpecklesSize = withSeeds ? 6 : 30;
|
||||
} else
|
||||
if(dpmm<5.436050986) { //~ 282828px (to 424242) 3.62486772487 dpmm
|
||||
blockSize = withSeeds ? 15 : 7;
|
||||
C = withSeeds ? 5 : 7;
|
||||
medianBlur = withSeeds ? 3 : 3;
|
||||
maxSpecklesSize = withSeeds ? 15 : 80;
|
||||
} else
|
||||
if(dpmm<21.79067661) { //~ 1132200px (to 1698300) 14.5303992304 dpmm
|
||||
blockSize = withSeeds ? 27 : 7; // old 35 witout seeds
|
||||
C = withSeeds ? 5 : 7;
|
||||
medianBlur = withSeeds ? 5 : 5;
|
||||
maxSpecklesSize = withSeeds ? 60 : 300;
|
||||
} else
|
||||
if(dpmm<60.08225108) {
|
||||
blockSize = withSeeds ? 71 : 25;
|
||||
C = withSeeds ? 7 : 3;
|
||||
medianBlur = withSeeds ? 7 : 7;
|
||||
maxSpecklesSize = withSeeds ? 200 : 1500;
|
||||
} else
|
||||
if(dpmm<90.0) {
|
||||
blockSize = withSeeds ? 131 : 35;
|
||||
C = withSeeds ? 7 : 7;
|
||||
medianBlur = withSeeds ? 0 : 0;
|
||||
maxSpecklesSize = withSeeds ? 200 : 1500;
|
||||
} else
|
||||
if(dpmm<150.0) {
|
||||
blockSize = withSeeds ? 171 : 35;
|
||||
C = withSeeds ? 5 : 7;
|
||||
medianBlur = withSeeds ? 0 : 0;
|
||||
maxSpecklesSize = withSeeds ? 200 : 2500;
|
||||
} else
|
||||
if(dpmm<348.7240019) { // >~ 18106549px (to 27159823) 232.486387686 dpmm
|
||||
blockSize = withSeeds ? 201 : 33;
|
||||
C = withSeeds ? 9 : 9;
|
||||
medianBlur = withSeeds ? 0 : 5;
|
||||
maxSpecklesSize = withSeeds ? 340 : 3500;
|
||||
} else { // >~ 72419094 px 930.111992945 dpmm
|
||||
blockSize = withSeeds ? 3551 : 71; // 251
|
||||
C = withSeeds ? 41 : 27 ; // 15
|
||||
medianBlur = withSeeds ? 7 : 7;
|
||||
maxSpecklesSize = withSeeds ? 2500 : 5000;
|
||||
}
|
||||
|
||||
// 1551
|
||||
// 39
|
||||
|
||||
//medianBlur = medianBlur%2 == 0 ? medianBlur-1 : medianBlur; // should be odd
|
||||
//blockSize = blockSize%2 == 0 ? blockSize+1 : blockSize; // should be odd
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mat apply(Mat img) {
|
||||
int height = img.height();
|
||||
int width = img.width();
|
||||
|
||||
|
||||
Mat blured;
|
||||
if(0!=medianBlur) {
|
||||
blured = new Mat(img.rows(), img.cols(), CvType.CV_8UC3);
|
||||
Imgproc.medianBlur(img, blured, (int)medianBlur);
|
||||
img.release();
|
||||
} else {
|
||||
blured = img;
|
||||
}
|
||||
|
||||
// to gray and threshold
|
||||
Mat gray = new Mat(height, width, CvType.CV_8UC1);
|
||||
Imgproc.cvtColor(blured, gray, Imgproc.COLOR_RGBA2GRAY);
|
||||
blured.release();
|
||||
|
||||
Mat outImg = new Mat(height, width, CvType.CV_8UC1);
|
||||
Imgproc.adaptiveThreshold(gray, outImg, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
inv?Imgproc.THRESH_BINARY_INV:Imgproc.THRESH_BINARY, blockSize, C);
|
||||
gray.release();
|
||||
|
||||
|
||||
try {
|
||||
Calib3d.filterSpeckles(outImg, inv? 0:255, maxSpecklesSize, maxDiff);
|
||||
} catch(Exception e) {
|
||||
int blur = (maxSpecklesSize/150);
|
||||
Imgproc.medianBlur(outImg, outImg, blur%2==0 ? blur+1 : blur);
|
||||
}
|
||||
|
||||
|
||||
//String text = "\nblockSize="+blockSize+"\nC="+C+"\nmedianBlur="+medianBlur+"\nmaxSpecklesSize=" + maxSpecklesSize;
|
||||
//System.out.println(text);
|
||||
//Core.putText(outImg, text, new Point(outImg.cols()/10, outImg.rows()/10), 2, 0.3, new Scalar(0));
|
||||
|
||||
return outImg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set invertion for binaryzation.
|
||||
* @param inv - true for invertion and false otherwise.
|
||||
*/
|
||||
public void setInverting(boolean inv) {
|
||||
this.inv = inv;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
|
||||
public class HSVBinarization {
|
||||
private final List<Pair<Scalar, Scalar>> targetsAndRanges;
|
||||
|
||||
public HSVBinarization(List<Pair<Scalar, Scalar>> targetsAndRanges) {
|
||||
this.targetsAndRanges = targetsAndRanges;
|
||||
}
|
||||
|
||||
|
||||
public Mat apply(Mat input) {
|
||||
Mat hsvImg = new Mat(input.size(), CvType.CV_8UC3);
|
||||
Imgproc.cvtColor(input, hsvImg, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat output = Mat.zeros(input.size(), CvType.CV_8UC1);
|
||||
for(int i=0; i<hsvImg.rows(); i++) {
|
||||
for(int j=0; j<hsvImg.cols(); j++) {
|
||||
double[] value = hsvImg.get(i, j);
|
||||
|
||||
boolean hitting = false;
|
||||
for(Pair<Scalar, Scalar> targetAndRange : targetsAndRanges) {
|
||||
Scalar target = targetAndRange.getLeft();
|
||||
Scalar range = targetAndRange.getRight();
|
||||
|
||||
if(Math.abs(value[0]-target.val[0])<range.val[0] && Math.abs(value[1]-target.val[1])<range.val[1] && Math.abs(value[2]-target.val[2])<range.val[2])
|
||||
hitting = true;
|
||||
}
|
||||
|
||||
if(hitting) {
|
||||
output.put(i, j, 255);
|
||||
} else {
|
||||
output.put(i, j, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hsvImg.release();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public Mat applyToRoi(Mat input, Rect roi) {
|
||||
Mat hsvImg = new Mat(input.size(), CvType.CV_8UC3);
|
||||
Imgproc.cvtColor(input, hsvImg, Imgproc.COLOR_BGR2HSV);
|
||||
|
||||
Mat output = Mat.zeros(input.size(), CvType.CV_8UC1);
|
||||
for(int i=roi.y; i<roi.y+roi.height && i<hsvImg.rows(); i++) {
|
||||
for(int j=roi.x; j<roi.x+roi.width && j<hsvImg.cols(); j++) {
|
||||
double[] value = hsvImg.get(i, j);
|
||||
|
||||
boolean hitting = false;
|
||||
for(Pair<Scalar, Scalar> targetAndRange : targetsAndRanges) {
|
||||
Scalar target = targetAndRange.getLeft();
|
||||
Scalar range = targetAndRange.getRight();
|
||||
|
||||
if(Math.abs(value[0]-target.val[0])<range.val[0] && Math.abs(value[1]-target.val[1])<range.val[1] && Math.abs(value[2]-target.val[2])<range.val[2])
|
||||
hitting = true;
|
||||
}
|
||||
|
||||
if(hitting) {
|
||||
output.put(i, j, 255);
|
||||
} else {
|
||||
output.put(i, j, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hsvImg.release();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public interface IFilter {
|
||||
Mat apply(Mat img);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
public class SeedsBinarization extends HSVBinarization {
|
||||
static List<Pair<Scalar, Scalar>> targetsAndRanges = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
static {
|
||||
Scalar target1 = new Scalar(12, 153, 76.5);
|
||||
Scalar range1 = new Scalar(30, 80, 120);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(19.125, 147.9, 153);
|
||||
Scalar range2 = new Scalar(30, 80, 120);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
Scalar target3 = new Scalar(22.7, 119.85, 193.8);
|
||||
Scalar range3 = new Scalar(30, 30, 100);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(32.6, 71.4, 204);
|
||||
Scalar range4 = new Scalar(30, 30, 40);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
Scalar target5 = new Scalar(20, 66, 71);
|
||||
Scalar range5 = new Scalar(20, 20, 10);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target5, range5));
|
||||
}
|
||||
|
||||
|
||||
public SeedsBinarization() {
|
||||
super(targetsAndRanges);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
public class SeedsBinarization2 extends HSVBinarization {
|
||||
static List<Pair<Scalar, Scalar>> targetsAndRanges = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
static {
|
||||
Scalar target1 = new Scalar(161.0, 81.0, 144.0);
|
||||
Scalar range1 = new Scalar(29.0, 43.0, 108.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(161.0, 72.0, 142.0);
|
||||
Scalar range2 = new Scalar(27.0, 38.0, 109.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
Scalar target3 = new Scalar(15.0, 138.0, 207.0);
|
||||
Scalar range3 = new Scalar(41.0, 94.0, 144.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(161.0, 72.0, 142.0);
|
||||
Scalar range4 = new Scalar(27.0, 38.0, 109.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
Scalar target5 = new Scalar(37.0, 231.0, 41.0);
|
||||
Scalar range5 = new Scalar(51.0, 198.0, 145.0);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target5, range5));
|
||||
}
|
||||
|
||||
|
||||
public SeedsBinarization2() {
|
||||
super(targetsAndRanges);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
public class SeedsBinarizationForWhiteSeeds extends HSVBinarization {
|
||||
static List<Pair<Scalar, Scalar>> targetsAndRanges = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
static {
|
||||
Scalar target1 = new Scalar(19.125, 147.9, 153);
|
||||
Scalar range1 = new Scalar(30, 120, 120);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(32.6, 71.4, 204);
|
||||
Scalar range2 = new Scalar(30, 30, 40);
|
||||
targetsAndRanges.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
}
|
||||
|
||||
|
||||
public SeedsBinarizationForWhiteSeeds() {
|
||||
super(targetsAndRanges);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class WhiteBalanser {
|
||||
|
||||
public static void whiteBalance(Mat img) {
|
||||
double[] rowR = new double[img.cols()*img.rows()];
|
||||
double[] rowG = new double[img.cols()*img.rows()];
|
||||
double[] rowB = new double[img.cols()*img.rows()];
|
||||
|
||||
for(int i=0; i<img.rows(); i++) {
|
||||
for(int j=0; j<img.cols(); j++) {
|
||||
double[] pixel = img.get(i, j);
|
||||
|
||||
rowR[i*img.cols()+j] = pixel[0];
|
||||
rowG[i*img.cols()+j] = pixel[1];
|
||||
rowB[i*img.cols()+j] = pixel[2];
|
||||
}
|
||||
}
|
||||
|
||||
double[] sortedRowR = rowR.clone();
|
||||
double[] sortedRowG = rowG.clone();
|
||||
double[] sortedRowB = rowB.clone();
|
||||
|
||||
Arrays.sort(sortedRowR);
|
||||
Arrays.sort(sortedRowG);
|
||||
Arrays.sort(sortedRowB);
|
||||
|
||||
double percentForBalance = 0.01;
|
||||
|
||||
double rPerc05 = percentile(sortedRowR, percentForBalance);
|
||||
double rPerc95 = percentile(sortedRowR, 100 - percentForBalance);
|
||||
|
||||
double gPerc05 = percentile(sortedRowG, percentForBalance);
|
||||
double gPerc95 = percentile(sortedRowG, 100 - percentForBalance);
|
||||
|
||||
double bPerc05 = percentile(sortedRowB, percentForBalance);
|
||||
double bPerc95 = percentile(sortedRowB, 100 - percentForBalance);
|
||||
|
||||
|
||||
for(int l=0; l < img.cols()*img.rows(); l++) {
|
||||
double rValueBalanced = (rowR[l] - rPerc05) * 255.0 / (rPerc95 - rPerc05);
|
||||
double gValueBalanced = (rowG[l] - gPerc05) * 255.0 / (gPerc95 - gPerc05);
|
||||
double bValueBalanced = (rowB[l] - bPerc05) * 255.0 / (bPerc95 - bPerc05);
|
||||
|
||||
rowR[l] = rValueBalanced < 0 ? 0 : (rValueBalanced > 255 ? 255 : rValueBalanced);
|
||||
rowG[l] = gValueBalanced < 0 ? 0 : (gValueBalanced > 255 ? 255 : gValueBalanced);
|
||||
rowB[l] = bValueBalanced < 0 ? 0 : (bValueBalanced > 255 ? 255 : bValueBalanced);
|
||||
}
|
||||
|
||||
|
||||
for(int i=0; i < img.rows(); i++) {
|
||||
for(int j=0; j < img.cols(); j++) {
|
||||
img.put(i, j, new double[] {rowR[i*img.cols()+j], rowG[i*img.cols()+j], rowB[i*img.cols()+j]});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static double percentile(double[] array, double percentile) {
|
||||
int nArray = array.length;
|
||||
|
||||
double nPercent = (nArray + 1) * percentile / 100.0;
|
||||
|
||||
if(nPercent == 1) {
|
||||
return array[0];
|
||||
} else if(nPercent == nArray) {
|
||||
return array[nArray - 1];
|
||||
} else {
|
||||
int intNPercent = (int)nPercent;
|
||||
double d = nPercent - intNPercent;
|
||||
return array[intNPercent - 1] + d * (array[intNPercent] - array[intNPercent - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
package org.wheatdb.seedcounter.processor.filters;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class WhiteBalanser {
|
||||
|
||||
public static void whiteBalance(Mat img) {
|
||||
double[] rowR = new double[img.cols()*img.rows()];
|
||||
double[] rowG = new double[img.cols()];
|
||||
double[] rowB = new double[img.cols()];
|
||||
|
||||
for(int i=0; i<img.rows(); i++) {
|
||||
|
||||
|
||||
//Mat mRow = img.row(i);
|
||||
|
||||
for(int j=0; j<img.cols(); j++) {
|
||||
double[] pixel = img.get(i, j);
|
||||
|
||||
rowR[j] = pixel[0];
|
||||
rowG[j] = pixel[1];
|
||||
rowB[j] = pixel[2];
|
||||
}
|
||||
}
|
||||
|
||||
double[] sortedRowR = rowR.clone();
|
||||
double[] sortedRowG = rowG.clone();
|
||||
double[] sortedRowB = rowB.clone();
|
||||
|
||||
Arrays.sort(sortedRowR);
|
||||
Arrays.sort(sortedRowG);
|
||||
Arrays.sort(sortedRowB);
|
||||
|
||||
double percentForBalance = 0.6;
|
||||
|
||||
double rPerc05 = percentile(sortedRowR, percentForBalance);
|
||||
double rPerc95 = percentile(sortedRowR, 100 - percentForBalance);
|
||||
|
||||
double gPerc05 = percentile(sortedRowG, percentForBalance);
|
||||
double gPerc95 = percentile(sortedRowG, 100 - percentForBalance);
|
||||
|
||||
double bPerc05 = percentile(sortedRowB, percentForBalance);
|
||||
double bPerc95 = percentile(sortedRowB, 100 - percentForBalance);
|
||||
|
||||
|
||||
for(int j=0; j < img.cols(); j++) {
|
||||
double rValueBalanced = (rowR[j] - rPerc05) * 255.0 / (rPerc95 - rPerc05);
|
||||
double gValueBalanced = (rowG[j] - gPerc05) * 255.0 / (gPerc95 - gPerc05);
|
||||
double bValueBalanced = (rowB[j] - bPerc05) * 255.0 / (bPerc95 - bPerc05);
|
||||
|
||||
rowR[j] = rValueBalanced < 0 ? 0 : (rValueBalanced > 255 ? 255 : rowR[j]);
|
||||
rowG[j] = gValueBalanced < 0 ? 0 : (gValueBalanced > 255 ? 255 : rowG[j]);
|
||||
rowB[j] = bValueBalanced < 0 ? 0 : (bValueBalanced > 255 ? 255 : rowB[j]);
|
||||
}
|
||||
|
||||
for(int j=0; j < img.cols(); j++) {
|
||||
img.put(i, j, new double[] {rowR[j], rowG[j], rowB[j]});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static double percentile(double[] array, double percentile) {
|
||||
int nArray = array.length;
|
||||
|
||||
double nPercent = (nArray + 1) * percentile / 100.0;
|
||||
|
||||
if(nPercent == 1) {
|
||||
return array[0];
|
||||
} else if(nPercent == nArray) {
|
||||
return array[nArray - 1];
|
||||
} else {
|
||||
int intNPercent = (int)nPercent;
|
||||
double d = nPercent - intNPercent;
|
||||
return array[intNPercent - 1] + d * (array[intNPercent] - array[intNPercent - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.wheatdb.seedcounter.processor.subjects;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint2f;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.ex.QuadNotFoundException;
|
||||
|
||||
/**
|
||||
* Quad.
|
||||
* @author Komyshev
|
||||
* @version 0.2.
|
||||
*/
|
||||
public class Quad {
|
||||
private Point tl = null;
|
||||
private Point tr = null;
|
||||
private Point br = null;
|
||||
private Point bl = null;
|
||||
|
||||
public Quad(Point[] points) throws QuadNotFoundException {
|
||||
double cx = (points[0].x + points[1].x + points[2].x + points[3].x)/4.0;
|
||||
double cy = (points[0].y + points[1].y + points[2].y + points[3].y)/4.0;
|
||||
|
||||
definePoint(points[0], cx, cy);
|
||||
definePoint(points[1], cx, cy);
|
||||
definePoint(points[2], cx, cy);
|
||||
definePoint(points[3], cx, cy);
|
||||
|
||||
if(tl == tr || tr == br || br == bl || bl == tl || tl == br || tr == bl || tl == bl)
|
||||
throw new QuadNotFoundException("Quad: Weird rectangle");
|
||||
}
|
||||
|
||||
private void definePoint(Point p, double cx, double cy) {
|
||||
if(p.x<cx) {
|
||||
if(p.y<cy) {
|
||||
tl = p;
|
||||
} else {
|
||||
bl = p;
|
||||
}
|
||||
} else {
|
||||
if(p.y<cy) {
|
||||
tr = p;
|
||||
} else {
|
||||
br = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Quad(Point tl, Point tr, Point br, Point bl) {
|
||||
this.tl = tl;
|
||||
this.tr = tr;
|
||||
this.br = br;
|
||||
this.bl = bl;
|
||||
}
|
||||
|
||||
public Quad(Point tl, Point br) {
|
||||
this.tl = tl;
|
||||
this.br = br;
|
||||
|
||||
this.tr = new Point(br.x, tl.y);
|
||||
this.bl = new Point(tl.x, br.y);
|
||||
}
|
||||
|
||||
public Quad(double[][] points) {
|
||||
this.tl = new Point(points[0][0], points[0][1]);
|
||||
this.tr = new Point(points[1][0], points[1][1]);
|
||||
this.br = new Point(points[2][0], points[2][1]);
|
||||
this.bl = new Point(points[3][0], points[3][1]);
|
||||
}
|
||||
|
||||
public Quad(double tlx, double tly, double trx, double tryy, double blx, double bly, double brx, double bry) {
|
||||
this.tl = new Point(tlx, tly);
|
||||
this.tr = new Point(trx, tryy);
|
||||
this.br = new Point(blx, bly);
|
||||
this.bl = new Point(brx, bry);
|
||||
}
|
||||
|
||||
public Point[] getPoints() {
|
||||
Point[] points = {tl, tr, br, bl};
|
||||
return points;
|
||||
}
|
||||
|
||||
public double getBigSideSize() {
|
||||
if(Math.abs(tl.x-tr.x) > Math.abs(tl.y-bl.y)) {
|
||||
return tl.x-tr.x;
|
||||
} else {
|
||||
return tl.y-bl.y;
|
||||
}
|
||||
}
|
||||
|
||||
public double getSmallSideSize() {
|
||||
if(Math.abs(tl.x-tr.x) > Math.abs(tl.y-bl.y)) {
|
||||
return tl.y-bl.y;
|
||||
} else {
|
||||
return tl.x-tr.x;
|
||||
}
|
||||
}
|
||||
|
||||
public Point tl() {
|
||||
return tl;
|
||||
}
|
||||
|
||||
public Point tr() {
|
||||
return tr;
|
||||
}
|
||||
|
||||
public Point bl() {
|
||||
return bl;
|
||||
}
|
||||
|
||||
public Point br() {
|
||||
return br;
|
||||
}
|
||||
|
||||
public boolean isInside(Point point) {
|
||||
double minX = Math.min(Math.min(tl.x, tr.x), Math.min(bl.x, br.x));
|
||||
double minY = Math.min(Math.min(tl.y, tr.y), Math.min(bl.y, br.y));
|
||||
double maxX = Math.max(Math.max(tl.x, tr.x), Math.max(bl.x, br.x));
|
||||
double maxY = Math.max(Math.max(tl.y, tr.y), Math.max(bl.y, br.y));
|
||||
return point.x >= minX && point.x <= maxX && point.y >= minY && point.y <= maxY;
|
||||
}
|
||||
|
||||
public double getArea() {
|
||||
double dx1 = tr.x - tl.x;
|
||||
double dy1 = tr.y - tl.y;
|
||||
double dx2 = bl.x - tl.x;
|
||||
double dy2 = bl.y - tl.y;
|
||||
|
||||
return Math.abs(dx1 * dy2 - dy1 * dx2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate relational points coordinates at source image for distinct image. The result is quad that has corresponding coordinates in distinct image.
|
||||
* @param from is the source image.
|
||||
* @param to is the distinct image.
|
||||
* @param srcQuad is the quad for recalculate.
|
||||
* @return recalculated quad.
|
||||
*/
|
||||
public static Quad recalculateQuadPoints(Mat from, Mat to, Quad srcQuad) {
|
||||
int width = from.cols();
|
||||
int height = from.rows();
|
||||
|
||||
int newWidth = to.cols();
|
||||
int newHeight = to.rows();
|
||||
|
||||
Point newTl = new Point(srcQuad.tl().x/width*newWidth, srcQuad.tl().y/height*newHeight);
|
||||
Point newTr = new Point(srcQuad.tr().x/width*newWidth, srcQuad.tr().y/height*newHeight);
|
||||
Point newBl = new Point(srcQuad.bl().x/width*newWidth, srcQuad.bl().y/height*newHeight);
|
||||
Point newBr = new Point(srcQuad.br().x/width*newWidth, srcQuad.br().y/height*newHeight);
|
||||
|
||||
return new Quad(newTl, newTr, newBr, newBl);
|
||||
}
|
||||
|
||||
public Mat getTransformedField(Mat image) {
|
||||
// Define the destination image
|
||||
Mat transformed = new Mat(image.rows(), image.cols(), image.type());
|
||||
|
||||
// Corners of the destination image
|
||||
Point[] quad_pts = new Point[4];
|
||||
quad_pts[0] = new Point(0, 0);
|
||||
quad_pts[1] = new Point(transformed.cols(), 0);
|
||||
quad_pts[2] = new Point(transformed.cols(), transformed.rows());
|
||||
quad_pts[3] = new Point(0, transformed.rows());
|
||||
|
||||
// Get transformation matrix
|
||||
Mat transmtx = Imgproc.getPerspectiveTransform(new MatOfPoint2f(getPoints()),
|
||||
new MatOfPoint2f(quad_pts));
|
||||
|
||||
// Apply perspective transformation
|
||||
Imgproc.warpPerspective(image, transformed, transmtx, transformed.size());
|
||||
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package org.wheatdb.seedcounter.processor.test;
|
||||
|
||||
public class SeedSizesFromMicroscope {
|
||||
public static double[] arrData = {
|
||||
6.768271966, 3.977383901, // 1
|
||||
7.194846395, 3.907059088,
|
||||
7.152581919, 4.270789414,
|
||||
7.18656747, 3.855635604,
|
||||
6.907621932, 4.414672834,
|
||||
7.116211957, 3.877207884,
|
||||
6.572688757, 3.118555435,
|
||||
7.295257783, 3.365848667,
|
||||
7.439182136, 2.9111525,
|
||||
7.072391985, 4.073834913,
|
||||
7.068605579, 2.974672015,
|
||||
7.084068442, 3.103235842,
|
||||
7.366084038, 4.339364293,
|
||||
7.222691828, 3.798747416,
|
||||
6.932601977, 3.138551751,
|
||||
6.742821179, 2.959669662,
|
||||
6.880073272, 3.636893919,
|
||||
7.042264475, 3.051198346,
|
||||
7.108905217, 3.277574244,
|
||||
7.168351788, 3.076362594,
|
||||
7.477905811, 3.602304591,
|
||||
6.728719376, 3.695603676,
|
||||
7.095775599, 2.990707956,
|
||||
6.150361244, 3.45383655,
|
||||
6.943060644, 3.968941239,
|
||||
|
||||
6.803567408, 4.121564093, // 2
|
||||
6.762121615, 3.087711578,
|
||||
6.465287869, 3.603133507,
|
||||
7.611023558, 3.417313085,
|
||||
7.189842199, 3.452137784,
|
||||
6.765406578, 3.858941034,
|
||||
6.399895618, 3.180273849,
|
||||
7.014705581, 3.397562373,
|
||||
6.918960683, 3.902484701,
|
||||
7.031672773, 3.921038089,
|
||||
7.214279867, 3.871272437,
|
||||
6.812644549, 3.93849649,
|
||||
7.437718742, 3.430360834,
|
||||
6.426206021, 3.655662212,
|
||||
6.648560142, 4.069987106,
|
||||
6.88743118, 4.208876563,
|
||||
6.655089134, 2.839804335,
|
||||
7.370126282, 3.182494525,
|
||||
6.887871221, 4.117920956,
|
||||
7.158322929, 3.859790417,
|
||||
6.364712745, 3.509128308,
|
||||
7.11236415, 4.188951882,
|
||||
7.620591907, 3.686117194,
|
||||
6.613049796, 3.106981314,
|
||||
6.67876952, 3.135369123,
|
||||
|
||||
7.703022985, 2.273593401,
|
||||
7.898636894, 2.023189177,
|
||||
8.957909495, 2.593391187,
|
||||
8.4808326, 2.009292044,
|
||||
8.395710105, 2.196289322,
|
||||
7.728504472, 2.153318733,
|
||||
7.804590761, 1.888741071,
|
||||
8.253832457, 2.471540555,
|
||||
7.74772304, 2.106766409,
|
||||
8.227307149, 2.22559815,
|
||||
8.947072187, 2.44148468,
|
||||
7.767299781, 2.719099859,
|
||||
7.707566671, 2.327217094,
|
||||
8.220829325, 2.41692421,
|
||||
8.008964572, 1.947041487,
|
||||
8.007040668, 2.736097751,
|
||||
6.919912401, 2.227266215,
|
||||
9.063468348, 2.295595489,
|
||||
8.529063223, 2.233897542,
|
||||
6.993174236, 1.654393254,
|
||||
7.576853804, 1.963363966,
|
||||
8.189054217, 2.569414028,
|
||||
8.834564768, 2.425919483,
|
||||
8.756513641, 2.282261201,
|
||||
8.295544321, 2.359585747,
|
||||
|
||||
7.536339262, 2.543369696,
|
||||
8.696934035, 2.53204118,
|
||||
7.552938046, 2.59844655,
|
||||
7.163521562, 2.516844389,
|
||||
7.477762541, 1.937401502,
|
||||
7.392609345, 2.293323646,
|
||||
8.64426206, 2.745164657,
|
||||
8.630385395, 2.515554964,
|
||||
7.939417508, 2.336437504,
|
||||
8.39703023, 2.237479277,
|
||||
7.707566671, 2.074940134,
|
||||
7.824750814, 2.373155406,
|
||||
7.917927096, 1.942160093,
|
||||
8.414447696, 2.745164657,
|
||||
8.177418695, 2.198489531,
|
||||
7.886049653, 2.674502139,
|
||||
8.094250803, 2.104873207,
|
||||
8.563232977, 2.194764526,
|
||||
7.351961768, 1.899363474,
|
||||
7.366687816, 2.068605579,
|
||||
7.109201989, 2.697957388,
|
||||
7.882181379, 2.258601281,
|
||||
8.429572852, 2.414498864,
|
||||
7.40329315, 1.870474222,
|
||||
6.053715794, 2.136822285,
|
||||
|
||||
7.504789292, 3.371231503,
|
||||
7.97874496, 3.435917641,
|
||||
6.439796148, 2.281278782,
|
||||
6.614850897, 2.863505188,
|
||||
6.757659797, 2.868632186,
|
||||
5.652684255, 2.329847111,
|
||||
6.550737837, 2.571870075,
|
||||
6.65383041, 2.887553982,
|
||||
7.1010254, 3.304304222,
|
||||
7.364415972, 2.531887677,
|
||||
7.304508893, 2.484721341,
|
||||
7.03465073, 3.036983974,
|
||||
6.986297304, 2.726652203,
|
||||
6.794203729, 2.873728484,
|
||||
6.815387134, 2.826664484,
|
||||
7.29040709, 3.167451237,
|
||||
7.130057922, 2.869737408,
|
||||
6.888884341, 2.765068872,
|
||||
6.134509507, 2.853834503,
|
||||
7.033750179, 3.484741808,
|
||||
7.027036984, 3.345207638,
|
||||
7.515012587, 3.329755009,
|
||||
7.722630426, 3.087476207,
|
||||
7.469688287, 2.565208048,
|
||||
6.986297304, 3.411285536,
|
||||
|
||||
7.256841114, 3.087445506,
|
||||
7.753904091, 3.000624245,
|
||||
7.19390491, 2.825375059,
|
||||
7.116119855, 2.977199697,
|
||||
6.811672363, 2.557717104,
|
||||
7.20332999, 2.816400254,
|
||||
6.794142328, 2.744079903,
|
||||
5.941075339, 2.595867701,
|
||||
7.638367547, 2.924558423,
|
||||
7.523199411, 2.492918398,
|
||||
7.644978407, 2.457408052,
|
||||
6.337624593, 2.375161178,
|
||||
6.740610737, 2.921529299,
|
||||
6.834841073, 3.042643116,
|
||||
6.80018011, 3.117603717,
|
||||
6.606633374, 3.06237336,
|
||||
7.791174604, 3.493153769,
|
||||
6.886049653, 2.910927362,
|
||||
6.575861151, 2.607892098,
|
||||
7.07455126, 2.631275712,
|
||||
6.152305614, 2.864538775,
|
||||
7.746658753, 2.811109519,
|
||||
6.858337256, 2.856444053,
|
||||
6.674757977, 2.902699605,
|
||||
7.309861029, 2.836324935,
|
||||
|
||||
4.884975133, 2.565238748,
|
||||
5.290642461, 2.620673776,
|
||||
4.862758141, 2.100483023,
|
||||
4.912247488, 2.476012608,
|
||||
5.119097812, 2.362154363,
|
||||
5.403579688, 2.810945783,
|
||||
4.894870955, 2.852268773,
|
||||
5.047954318, 2.326367711,
|
||||
5.253279846, 2.565351317,
|
||||
4.858746597, 2.59479318,
|
||||
5.479021265, 2.48573446,
|
||||
4.768333367, 2.376102663,
|
||||
4.918305737, 2.044751223,
|
||||
5.234112446, 2.346138889,
|
||||
4.494146421, 1.968337461,
|
||||
4.946918684, 2.278669232,
|
||||
4.901185043, 2.360690968,
|
||||
5.248316584, 2.052201232,
|
||||
4.948494648, 2.156941403,
|
||||
5.328424651, 2.597658569,
|
||||
5.197415011, 2.522759369,
|
||||
5.579217749, 2.475112057,
|
||||
5.253371948, 2.606172865,
|
||||
5.368632186, 2.198868172,
|
||||
4.92802759, 2.238400295,
|
||||
|
||||
5.868335414, 2.757209521,
|
||||
4.782680775, 2.42910211,
|
||||
4.864426206, 2.785157289,
|
||||
5.970476268, 2.72406312,
|
||||
5.326858921, 1.832507829,
|
||||
5.182351256, 2.711588448,
|
||||
5.529759103, 2.331013733,
|
||||
5.035766184, 2.212509466,
|
||||
4.953099736, 2.574479625,
|
||||
5.142972636, 2.623365194,
|
||||
4.93849649, 2.718997523,
|
||||
4.942722937, 2.30922655,
|
||||
5.034967969, 2.646155263,
|
||||
4.93849649, 2.348318631,
|
||||
5.145848257, 2.432049367,
|
||||
4.896702757, 2.162068401,
|
||||
5.368212612, 2.529022289,
|
||||
5.186495835, 2.479318038,
|
||||
5.026064799, 2.495456313,
|
||||
5.371548742, 2.170582697,
|
||||
5.420454778, 2.88029841,
|
||||
5.203340224, 2.378916883,
|
||||
4.776233652, 2.248357519,
|
||||
5.08554207, 2.64475327,
|
||||
5.318743732, 2.226764772,
|
||||
|
||||
6.377586524, 2.622198571,
|
||||
6.014736282, 2.593462822,
|
||||
6.935283162, 2.401502282,
|
||||
6.777062568, 2.784727481,
|
||||
6.838709347, 2.726652203,
|
||||
6.568380442, 2.388290796,
|
||||
5.97265601, 2.575666714,
|
||||
6.445158517, 2.750179087,
|
||||
6.601342639, 2.506651794,
|
||||
6.150780818, 2.714064962,
|
||||
6.69355697, 2.793170143,
|
||||
7.099776909, 2.516844389,
|
||||
7.121697128, 3.028080804,
|
||||
6.223316073, 2.383143331,
|
||||
6.213747723, 2.75676948,
|
||||
5.843058597, 2.4721034,
|
||||
6.576915205, 3.044270247,
|
||||
6.848349332, 2.567602693,
|
||||
7.083495364, 2.757076485,
|
||||
6.216060501, 2.967682515,
|
||||
6.841155161, 2.492355554,
|
||||
6.477783008, 2.142614462,
|
||||
6.567694795, 2.483370515,
|
||||
6.417722426, 2.638510817,
|
||||
6.855635604, 2.783652961,
|
||||
|
||||
6.082789251, 2.419769132,
|
||||
6.18482777, 2.5,
|
||||
6.389457418, 3.207546204,
|
||||
7.013590127, 2.701958697,
|
||||
6.092275732, 2.677183323,
|
||||
6.358562394, 2.265386111,
|
||||
6.470813975, 2.520763831,
|
||||
6.829181932, 2.689811498,
|
||||
6.183159704, 2.509619517,
|
||||
6.262663992, 2.640342619,
|
||||
6.667348902, 2.542346344,
|
||||
6.904705377, 2.701651692,
|
||||
7.477609038, 2.578992611,
|
||||
6.823788862, 2.86011789,
|
||||
6.00629362, 2.293323646,
|
||||
6.243332856, 1.989367363,
|
||||
6.59548906, 2.619814159,
|
||||
7.123385661, 2.397787511,
|
||||
6.920772017, 2.928119691,
|
||||
7.180243149, 2.413997421,
|
||||
6.952925766, 2.678851389,
|
||||
5.800794122, 2.394983524,
|
||||
7.165752471, 2.624961624,
|
||||
6.765416812, 2.807793856,
|
||||
6.048773, 2.625780307
|
||||
};
|
||||
|
||||
public static double[][][] structData = new double[10][][]; // [image№][seed№][length/width]
|
||||
|
||||
static {
|
||||
for(int imageNum = 0; imageNum<10; imageNum++) {
|
||||
structData[imageNum] = new double[25][];
|
||||
|
||||
for(int seedNum = 0; seedNum<25; seedNum++) {
|
||||
structData[imageNum][seedNum] = new double[2];
|
||||
|
||||
structData[imageNum][seedNum][0] = arrData[(imageNum+1)*(seedNum+1)*(2)-2]; // length
|
||||
structData[imageNum][seedNum][1] = arrData[(imageNum+1)*(seedNum+1)*(2)-1]; // width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.wheatdb.seedcounter.processor.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.wheatdb.seedcounter.desktop.Display;
|
||||
|
||||
/**
|
||||
* Main class of test application.
|
||||
*/
|
||||
public class TestMain {
|
||||
private static File imgSrcDir = ImgDirs.SonyL4;
|
||||
|
||||
private static File imgOutDir = new File(imgSrcDir, "out");
|
||||
|
||||
private static List<File> inputImgs = new LinkedList<File>();
|
||||
private static List<File> markedImgs = new LinkedList<File>();
|
||||
private static List<File> outputImgs = new LinkedList<File>();
|
||||
|
||||
private static int fromImg = 1;
|
||||
private static int toImg = 10;
|
||||
|
||||
static {
|
||||
if(!imgOutDir.exists()) {
|
||||
imgOutDir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
linkImage();
|
||||
doTests();
|
||||
//display(outputImgs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute tests.
|
||||
*/
|
||||
private static void doTests() {
|
||||
/*
|
||||
DetectionTester detectTester = new DetectionTester(new DetectionProcessor(SheetFormat.A4), inputImgs, markedImgs, outputImgs);
|
||||
|
||||
double appraisal = detectTester.doTests();
|
||||
|
||||
System.out.println("Appraisal: " + appraisal);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Link to source, marked and output image files.
|
||||
*/
|
||||
private static void linkImage() {
|
||||
// download source and marked image
|
||||
for(int i=fromImg; i<toImg+1; i++) {
|
||||
File sourceImgJPG = new File(imgSrcDir+"/"+i+".jpg");
|
||||
File markedImgJPG = new File(imgSrcDir+"/"+i+"b.jpg");
|
||||
File outputImgJPG = new File(imgOutDir+"/"+i+"_out"+".jpg");
|
||||
|
||||
// check: whether file exist or not
|
||||
if(!sourceImgJPG.exists()) {
|
||||
System.out.println("Source image file №"+i+" not found.");
|
||||
} else {
|
||||
inputImgs.add(sourceImgJPG);
|
||||
outputImgs.add(outputImgJPG);
|
||||
|
||||
if(!outputImgJPG.exists())
|
||||
try {outputImgJPG.createNewFile();} catch (IOException e) {}
|
||||
|
||||
if(!markedImgJPG.exists()) {
|
||||
System.out.println("Marked image file №"+i+" not found.");
|
||||
} else {
|
||||
markedImgs.add(markedImgJPG);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To display images on windows.
|
||||
* @param images
|
||||
*/
|
||||
private static void display(List<File> images) {
|
||||
for(File out : images) {
|
||||
Display display = new Display();
|
||||
|
||||
Mat img = Imgcodecs.imread(out.getAbsolutePath());
|
||||
display.setImage(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ImgDirs {
|
||||
public static File SonyL1 = new File("../data/seedcounter/estimate/Sony/L1");
|
||||
public static File SonyL2 = new File("../data/seedcounter/estimate/Sony/L2");
|
||||
public static File SonyL3 = new File("../data/seedcounter/estimate/Sony/L3");
|
||||
public static File SonyL4 = new File("../data/seedcounter/estimate/Sony/L4");
|
||||
public static File SonyDark = new File("../data/seedcounter/estimate/Sony/dark");
|
||||
public static File SonyOutsideSun = new File("../data/seedcounter/estimate/Sony/outside_sun");
|
||||
|
||||
public static File SamsungL1 = new File("../data/seedcounter/estimate/Samsung/L1");
|
||||
public static File SamsungL2 = new File("../data/seedcounter/estimate/Samsung/L2");
|
||||
public static File SamsungL3 = new File("../data/seedcounter/estimate/Samsung/L3");
|
||||
public static File SamsungL4 = new File("../data/seedcounter/estimate/Samsung/L4");
|
||||
public static File SamsungDark = new File("../data/seedcounter/estimate/Samsung/dark");
|
||||
public static File SamsungOutsideSun = new File("../data/seedcounter/estimate/Samsung/outside_sun");
|
||||
|
||||
public static File DNSL1 = new File("../data/seedcounter/estimate/DNS/L1");
|
||||
public static File DNSL2 = new File("../data/seedcounter/estimate/DNS/L2");
|
||||
public static File DNSL3 = new File("../data/seedcounter/estimate/DNS/L3");
|
||||
public static File DNSL4 = new File("../data/seedcounter/estimate/DNS/L4");
|
||||
public static File DNSDark = new File("../data/seedcounter/estimate/DNS/dark");
|
||||
public static File DNSOutsideSun = new File("../data/seedcounter/estimate/DNS/outside_sun");
|
||||
|
||||
public static File Lenovo = new File("../data/seedcounter/estimate/Lenovo");
|
||||
|
||||
public static File testWatersheld = new File("../data/seedcounter/test/watersheld");
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.wheatdb.seedcounter.processor.test.testers;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Scalar;
|
||||
import org.wheatdb.seedcounter.desktop.Display;
|
||||
import org.wheatdb.seedcounter.processor.Calibration;
|
||||
|
||||
public class CalibrationDisplay extends Display implements MouseListener {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int COLORS_NUM = 5;
|
||||
|
||||
List<Scalar> colors = new ArrayList<Scalar>(10);
|
||||
|
||||
|
||||
public CalibrationDisplay() {
|
||||
addMouseListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
BufferedImage screenshot = getScreenShot();
|
||||
|
||||
int[] pixel = new int[3];
|
||||
screenshot.getData().getPixel(e.getX(), e.getY(), pixel);
|
||||
|
||||
synchronized (colors) {
|
||||
colors.add(new Scalar(pixel[0], pixel[1], pixel[2]));
|
||||
|
||||
if(COLORS_NUM==colors.size()) {
|
||||
colors.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Calibration getCalibration() {
|
||||
synchronized (colors) {
|
||||
try {
|
||||
while(COLORS_NUM>colors.size())
|
||||
colors.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
Calibration calibration = new Calibration();
|
||||
|
||||
for(Scalar color : colors) {
|
||||
calibration.addColor(Calibration.RGBtoHSV(color, true));
|
||||
}
|
||||
|
||||
return calibration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public BufferedImage getScreenShot() {
|
||||
|
||||
BufferedImage image = new BufferedImage(
|
||||
this.getWidth(),
|
||||
this.getHeight(),
|
||||
BufferedImage.TYPE_INT_RGB
|
||||
);
|
||||
this.paint( image.getGraphics() );
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.wheatdb.seedcounter.processor.test.testers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfFloat;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.filters.GrayBinarization;
|
||||
|
||||
public class FiltersTester {
|
||||
public Mat doTests(Mat img) {
|
||||
Mat outImg = binarizationFilterTest(img);
|
||||
|
||||
return outImg;
|
||||
}
|
||||
|
||||
public Mat binarizationFilterTest(Mat srcImg) {
|
||||
//Calibration calibration = calibrate(srcImg);
|
||||
|
||||
GrayBinarization binarization = new GrayBinarization(false, srcImg.height()*srcImg.width()/(297*210*4/3));
|
||||
//HSVBinarization binarization = new HSVBinarization(calibration.targetColor);
|
||||
|
||||
Mat outImg = binarization.apply(srcImg.clone());
|
||||
|
||||
//FrameProcessor fp = new FrameProcessor();
|
||||
//Mat outImg = fp.treshHullFilterLines(srcImg, false);
|
||||
|
||||
return outImg;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static Mat adjastImage(Mat img) {
|
||||
Mat histB = histogram(img, 0);
|
||||
Mat histG = histogram(img, 1);
|
||||
Mat histR = histogram(img, 2);
|
||||
|
||||
List<Mat> rgb = new LinkedList<Mat>(); Core.split(img, rgb);
|
||||
|
||||
HistAnalyzer histAnalyzerR = new HistAnalyzer(histR);
|
||||
HistAnalyzer histAnalyzerG = new HistAnalyzer(histG);
|
||||
HistAnalyzer histAnalyzerB = new HistAnalyzer(histB);
|
||||
|
||||
contrastAndBrightAdjast(rgb.get(0), histAnalyzerR);
|
||||
contrastAndBrightAdjast(rgb.get(1), histAnalyzerG);
|
||||
contrastAndBrightAdjast(rgb.get(2), histAnalyzerB);
|
||||
|
||||
Mat out = new Mat(); Core.merge(rgb, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
private static void contrastAndBrightAdjast(Mat src, HistAnalyzer histAnalyzer) {
|
||||
int histLenght = histAnalyzer.getHistLength();
|
||||
int leftCenter = histAnalyzer.getLeftCenterPos();
|
||||
int leftWidth = histAnalyzer.getLeftWidth();
|
||||
int rightCenter = histAnalyzer.getRightCenterPos();
|
||||
int rightWidth = histAnalyzer.getRightWidth();
|
||||
|
||||
|
||||
float contrast = (float)(histLenght/(float)((rightCenter+rightWidth/2)-(leftCenter-leftWidth/2)));
|
||||
float bright = -(float)((256.0/histLenght)*leftCenter*contrast*0.4);
|
||||
|
||||
Mat kernel2 = new MatOfFloat(contrast);
|
||||
Imgproc.filter2D(src, src, -1, kernel2, new Point(), bright);
|
||||
}
|
||||
|
||||
|
||||
private static Mat histogram(Mat img, int channel) {
|
||||
MatOfInt channels = new MatOfInt(channel);
|
||||
|
||||
MatOfInt histSize = new MatOfInt(256);
|
||||
MatOfFloat ranges=new MatOfFloat(0, 256);
|
||||
|
||||
/// Compute the histograms:
|
||||
Mat hist = new Mat();
|
||||
Imgproc.calcHist(Arrays.asList(img, img, img), channels, new Mat(), hist, histSize, ranges);
|
||||
Core.normalize(hist, hist, 256, 0, Core.NORM_INF);
|
||||
|
||||
return hist;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package org.wheatdb.seedcounter.processor.test.testers;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
|
||||
/**
|
||||
* It's analyzer for image histograms.
|
||||
* @author Komyshev
|
||||
* @version 0.1
|
||||
*/
|
||||
public class HistAnalyzer {
|
||||
public Mat hist; // histogram for analyzing
|
||||
private int histLength;
|
||||
|
||||
private static final float persentOfNonSignificantMinValues = 0.1f; // 10% from average value of histogram
|
||||
private double nonSignHistValueThres; // histogram values under non-significant threshold must be deleted
|
||||
|
||||
private double sumValue; // sum values of histogram
|
||||
private double averageValue; // average value of histogram
|
||||
|
||||
private int leftLimitPos; // minimal by position(left) position of significant histogram value
|
||||
private int rightLimitPos; // maximal by position(right) position of significant histogram value
|
||||
|
||||
private int leftCenterPos; // center of left contrast part
|
||||
private int leftWidth; // significant width of left contrast part
|
||||
|
||||
private int rightCenterPos; // center of right contrast part
|
||||
private int rightWidth; // significant width of right contrast part
|
||||
|
||||
// center of contrast division.
|
||||
private int centerPos; // it is on non-significant area between contrast parts. it will be new histogram center of future image.
|
||||
|
||||
|
||||
|
||||
HistAnalyzer(Mat hist) {
|
||||
this.hist = hist;
|
||||
histLength = hist.cols()*hist.rows();
|
||||
|
||||
sumValue = getSumValues();
|
||||
|
||||
averageValue = getAverageValue();
|
||||
|
||||
nonSignHistValueThres = averageValue*persentOfNonSignificantMinValues;
|
||||
|
||||
determLimits(nonSignHistValueThres);
|
||||
|
||||
detectSideCenterPos(sumValue);
|
||||
|
||||
determCenterPos(leftCenterPos, leftWidth, rightCenterPos, rightWidth);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detect centers and widths of right and left local highs in histogram.
|
||||
* @param leftLim
|
||||
* @param rightLim
|
||||
* @param average
|
||||
* @param sum
|
||||
*/
|
||||
private void detectSideCenterPos(double sum) {
|
||||
HistIterator histIter = new HistIterator();
|
||||
|
||||
// getting big-half side of maximal histogram values
|
||||
int massCenterPos = 0;
|
||||
int massAccum = 0;
|
||||
List<Integer> positionCollection = new LinkedList<Integer>();
|
||||
while(positionCollection.size()!=histLength) {
|
||||
int curPosition = histIter.getNextPosOfMaxValue();
|
||||
double curValue = hist.get(curPosition, 0)[0];
|
||||
|
||||
massCenterPos = (int)(massCenterPos*(massAccum/(massAccum+curValue))+curPosition*(curValue/(massAccum+curValue)));
|
||||
|
||||
massAccum +=curValue;
|
||||
positionCollection.add(curPosition);
|
||||
|
||||
if(massAccum>=(sum) && positionCollection.size()>=2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(positionCollection);
|
||||
|
||||
// divide to left and right side by positions of values
|
||||
List<Integer> leftPositions = new LinkedList<Integer>();
|
||||
List<Integer> rightPositions = new LinkedList<Integer>();
|
||||
while(true) {
|
||||
if(!positionCollection.isEmpty()) {
|
||||
int pos = positionCollection.get(0);
|
||||
if(pos>massCenterPos) {
|
||||
rightPositions.add(positionCollection.get(0));
|
||||
positionCollection.remove(0);
|
||||
} else {
|
||||
leftPositions.add(positionCollection.get(0));
|
||||
positionCollection.remove(0);
|
||||
}
|
||||
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
// determining of center of left and right positions
|
||||
for(Integer cur : leftPositions) {
|
||||
leftCenterPos +=cur;
|
||||
}
|
||||
leftCenterPos /= leftPositions.size();
|
||||
leftWidth = Collections.max(leftPositions)-Collections.min(leftPositions);
|
||||
|
||||
for(Integer cur : rightPositions) {
|
||||
rightCenterPos +=cur;
|
||||
}
|
||||
rightCenterPos /= rightPositions.size();
|
||||
rightWidth = Collections.max(rightPositions)-Collections.min(rightPositions);
|
||||
}
|
||||
|
||||
|
||||
private void determCenterPos(int leftCenterPos, int leftWidth, int rightCenterPos, int rightWidth) {
|
||||
double normLeftWidth = (double)leftWidth/((double)(leftWidth+rightWidth));
|
||||
double normRightWidth = (double)rightWidth/((double)(leftWidth+rightWidth));
|
||||
centerPos = (int)Math.round(leftCenterPos*normLeftWidth + rightCenterPos*normRightWidth);
|
||||
}
|
||||
|
||||
|
||||
private void determLimits(double threshold) {
|
||||
// to establish left limit
|
||||
for(int i=0; i<histLength; i++) {
|
||||
int curLeftPosition = i;
|
||||
double curValue = hist.get(curLeftPosition, 0)[0];
|
||||
|
||||
if(curValue<threshold) {
|
||||
leftLimitPos = curLeftPosition;
|
||||
} else break;
|
||||
}
|
||||
|
||||
// to establish right limit
|
||||
for(int i=0; i<histLength; i++) {
|
||||
int curRightPosition = histLength-i-1;
|
||||
double curValue = hist.get(curRightPosition, 0)[0];
|
||||
|
||||
if(curValue<threshold) {
|
||||
rightLimitPos = curRightPosition;
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
private double getSumValues() {
|
||||
double sum = 0;
|
||||
for(int i=0; i<hist.rows(); i++)
|
||||
sum += hist.get(i, 0)[0];
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
private double getAverageValue() {
|
||||
return getSumValues()/histLength;
|
||||
}
|
||||
|
||||
|
||||
public int getLeftLimitPos() {
|
||||
return leftLimitPos;
|
||||
}
|
||||
|
||||
public int getRightLimitPos() {
|
||||
return rightLimitPos;
|
||||
}
|
||||
|
||||
public int getLeftCenterPos() {
|
||||
return leftCenterPos;
|
||||
}
|
||||
|
||||
public int getLeftWidth() {
|
||||
return leftWidth;
|
||||
}
|
||||
|
||||
public int getCenterPos() {
|
||||
return centerPos;
|
||||
}
|
||||
|
||||
public int getRightCenterPos() {
|
||||
return rightCenterPos;
|
||||
}
|
||||
|
||||
public int getRightWidth() {
|
||||
return rightWidth;
|
||||
}
|
||||
|
||||
public int getHistLength() {
|
||||
return histLength;
|
||||
}
|
||||
|
||||
|
||||
|
||||
class HistIterator {
|
||||
int curPosOfPosOfMinHistVal;
|
||||
int curPosOfPosOfMaxHistVal;
|
||||
List<Integer> sortedPosHist; // sorted position of histogram by value of histogram
|
||||
|
||||
private HistIterator() {
|
||||
// forming sorted histogram
|
||||
List<Double> initHistValues = new LinkedList<Double>();
|
||||
for(int i=0; i<hist.rows(); i++)
|
||||
initHistValues.add(hist.get(i, 0)[0]);
|
||||
|
||||
List<Double> reducedHistValues = new LinkedList<Double>();
|
||||
reducedHistValues.addAll(initHistValues);
|
||||
|
||||
sortedPosHist = new LinkedList<Integer>();
|
||||
while(!reducedHistValues.isEmpty()) {
|
||||
Double curMin = Collections.min(reducedHistValues);
|
||||
int posMin = initHistValues.indexOf(curMin);
|
||||
reducedHistValues.remove(curMin);
|
||||
|
||||
sortedPosHist.add(posMin);
|
||||
}
|
||||
|
||||
curPosOfPosOfMinHistVal = 0;
|
||||
curPosOfPosOfMaxHistVal = sortedPosHist.size()-1;
|
||||
}
|
||||
|
||||
public boolean hasNextPosOfMinValue() {
|
||||
if(curPosOfPosOfMinHistVal<sortedPosHist.size())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasNextPosOfMaxValue() {
|
||||
if(curPosOfPosOfMaxHistVal>=0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getNextPosOfMinValue() {
|
||||
return sortedPosHist.get(curPosOfPosOfMinHistVal++);
|
||||
}
|
||||
|
||||
public int getNextPosOfMaxValue() {
|
||||
return sortedPosHist.get(curPosOfPosOfMaxHistVal--);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.wheatdb.seedcounter.server;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Директории и файлы по умолчанию.
|
||||
* @author Komyshev
|
||||
*/
|
||||
public class DefaultFilesAndDirs {
|
||||
// Директория входных/выходных данных
|
||||
public static final File dataDir = new File("../data/seedcounter");
|
||||
|
||||
// Директория входных файлов
|
||||
public static final File inputDir = new File(dataDir, "input/");
|
||||
|
||||
// Директория выходных файлов
|
||||
public static final File outputDir = new File(dataDir, "output/");
|
||||
|
||||
// Директория выходных файлов
|
||||
public static final File markedDir = new File(dataDir, "marked/");
|
||||
|
||||
// Директория шаблонов для распознавания
|
||||
public static final File templateDir = new File(dataDir, "templates/");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.delkom07.recognition.potatoes;
|
||||
|
||||
|
||||
|
||||
public enum PotatoType {
|
||||
WHITE, DARK;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package ru.delkom07.recognition.potatoes.refactor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.SeedDataContainer;
|
||||
import org.wheatdb.seedcounter.processor.ElementsProcessor;
|
||||
import org.wheatdb.seedcounter.processor.PlanarObject;
|
||||
import org.wheatdb.seedcounter.processor.SeedCounterProcessorException;
|
||||
import org.wheatdb.seedcounter.processor.filters.GrayBinarization;
|
||||
import org.wheatdb.seedcounter.processor.filters.HSVBinarization;
|
||||
|
||||
import ru.delkom07.recognition.potatoes.training.PotatoBinarizationTraining;
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
public class DetectionProcessor {
|
||||
|
||||
/**
|
||||
* Detect contours of potatoes in image.
|
||||
* @param image - source image.
|
||||
* @return list of contours.
|
||||
*/
|
||||
// @Deprecated
|
||||
// public List<PlanarObject> detectPotatoesContours(Mat image, double pixPerMM) throws SeedCounterProcessorException {
|
||||
// List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
// synchronized (DetectionProcessor.class) {
|
||||
// List<Pair<Scalar, Scalar>> targetsAndRanges = PotatoBinarizationTraining.getCommonPotatoesHSVPreset2().get(0).getParams(); // differ
|
||||
// HSVBinarization seedContourBinarization = new HSVBinarization(targetsAndRanges); // differ
|
||||
//
|
||||
// Mat hulled = seedContourBinarization.apply(image);
|
||||
//
|
||||
// // Find contours
|
||||
// Imgproc.findContours(hulled, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
// hulled.release();
|
||||
// }
|
||||
//
|
||||
//
|
||||
// contours = ElementsProcessor.filterCircularContours(contours, 8*pixPerMM, 100*pixPerMM); // differ
|
||||
//
|
||||
// // Gauge seeds
|
||||
// List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
// for(int i=0; i<contours.size(); i++) {
|
||||
// MatOfPoint contour = contours.get(i);
|
||||
//
|
||||
// SeedDataContainer seedDataContainer = gaugeSeed(contour, pixPerMM);
|
||||
// seedDataList.add(seedDataContainer);
|
||||
// }
|
||||
//
|
||||
// return seedDataList;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Detect contours of seeds in image.
|
||||
* @param image - source image.
|
||||
* @return list of contours.
|
||||
*/
|
||||
// @Deprecated
|
||||
// public List<PlanarObject> detectPotatoesContours(Mat image) throws SeedCounterProcessorException {
|
||||
// // init size
|
||||
// //prepareSize(image.cols(), image.rows());
|
||||
// if(-1 == widthPadding || -1 == heightPadding)
|
||||
// throw new SeedCounterProcessorException("Padding isn't initialized.");
|
||||
//
|
||||
//
|
||||
// int width = image.cols();
|
||||
// int height = image.rows();
|
||||
//
|
||||
// // Clean
|
||||
// GrayBinarization binarization = new GrayBinarization(true, width*height/(210*297)); // ?????????????????????????????
|
||||
// Mat hulled = binarization.apply(image);
|
||||
//
|
||||
// // Find contours
|
||||
// List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
|
||||
// Imgproc.findContours(hulled, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);
|
||||
//
|
||||
// /* // disabled for desktop
|
||||
// if(SeedCounterApplication.config.getUseWatershed()) {
|
||||
// double maxSeedAreaInMM2 = 78;
|
||||
// double maxSeedAreaInPix = maxSeedAreaInMM2 * ((width+2*widthPadding)/mmInBigSideOfSheet);
|
||||
// contours = clarifyContours(contours, maxSeedAreaInPix);
|
||||
// }
|
||||
// */
|
||||
//
|
||||
// // Filter
|
||||
// double pixInMM = (width+2*widthPadding)/mmInBigSideOfSheet;
|
||||
// //contours = ElementsProcessor.filterCircularContours(contours, SeedCounterApplication.config.getMinSeedLength()*pixInMM, SeedCounterApplication.config.getMaxSeedLength()*pixInMM);
|
||||
// contours = ElementsProcessor.filterCircularContours(contours, 8*pixInMM, 100*pixInMM);
|
||||
//
|
||||
// // Gauge seeds
|
||||
// List<PlanarObject> seedDataList = new ArrayList<PlanarObject>(contours.size());
|
||||
// for(int i=0; i<contours.size(); i++) {
|
||||
// MatOfPoint contour = contours.get(i);
|
||||
//
|
||||
// SeedDataContainer seedDataContainer = gaugeSeed(width, height, contour);
|
||||
// seedDataList.add(seedDataContainer);
|
||||
// }
|
||||
//
|
||||
// return seedDataList;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package ru.delkom07.recognition.potatoes.training;
|
||||
|
||||
import java.util.List;
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
|
||||
public class HSVParameters {
|
||||
List<Pair<Scalar, Scalar>> params;
|
||||
Double jIndex = -1.0;
|
||||
|
||||
|
||||
|
||||
HSVParameters(List<Pair<Scalar, Scalar>> params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
HSVParameters(List<Pair<Scalar, Scalar>> params, Double jIndex) {
|
||||
this.params = params;
|
||||
this.jIndex = jIndex;
|
||||
}
|
||||
|
||||
public List<Pair<Scalar, Scalar>> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public Double getJIndex() {
|
||||
return jIndex;
|
||||
}
|
||||
|
||||
public void setJIndex(Double jIndex) {
|
||||
this.jIndex = jIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\nJaccard Index: " + jIndex + "\n" + "Parameters: " + params.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package ru.delkom07.recognition.potatoes.training;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.fitting.leastsquares.LeastSquaresBuilder;
|
||||
import org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer;
|
||||
import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
|
||||
import org.apache.commons.math3.fitting.leastsquares.LevenbergMarquardtOptimizer;
|
||||
import org.apache.commons.math3.fitting.leastsquares.MultivariateJacobianFunction;
|
||||
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
|
||||
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
|
||||
import org.apache.commons.math3.linear.ArrayRealVector;
|
||||
import org.apache.commons.math3.linear.RealMatrix;
|
||||
import org.apache.commons.math3.linear.RealVector;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.wheatdb.seedcounter.processor.filters.HSVBinarization;
|
||||
import org.wheatdb.seedcounter.server.DefaultFilesAndDirs;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
|
||||
public class LMTraining {
|
||||
final Vector2D[] observedPoints;
|
||||
public double rms;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
File inputDir = DefaultFilesAndDirs.inputDir;
|
||||
File markedDir = DefaultFilesAndDirs.markedDir;
|
||||
|
||||
File[] inputImgFiles = inputDir.listFiles(new PotatoBinarizationTraining.ImageFilter());
|
||||
|
||||
List<Mat> inputs = new ArrayList<Mat>();
|
||||
List<Mat> markets = new ArrayList<Mat>();
|
||||
|
||||
for(File inputImgFile: inputImgFiles) {
|
||||
File markedImgFile = new File(markedDir, PotatoBinarizationTraining.changeExtension(inputImgFile.getName(), "png"));
|
||||
|
||||
Mat input = Imgcodecs.imread(inputImgFile.getCanonicalPath());
|
||||
Mat marked = Imgcodecs.imread(markedImgFile.getCanonicalPath());
|
||||
|
||||
/*
|
||||
Size newSize = new Size(input.size().width/8, input.size().height/8);
|
||||
Imgproc.resize(input, input, newSize);
|
||||
Imgproc.resize(marked, marked, newSize);
|
||||
*/
|
||||
|
||||
inputs.add(input);
|
||||
markets.add(marked);
|
||||
}
|
||||
|
||||
LMTraining training = new LMTraining(inputs, markets);
|
||||
|
||||
for(int i=0; i<inputs.size(); i++) {
|
||||
inputs.get(i).release();
|
||||
markets.get(i).release();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public LMTraining(List<Mat> imgs, List<Mat> marked) {
|
||||
observedPoints = new Vector2D[imgs.size()];
|
||||
for(int i=0; i<imgs.size(); i++) {
|
||||
observedPoints[i] = new Vector2D(1, 1);
|
||||
}
|
||||
|
||||
MultivariateJacobianFunction distances = new MultivariateJacobianFunction() {
|
||||
public org.apache.commons.math3.util.Pair<RealVector, RealMatrix> value(final RealVector currentParam) {
|
||||
|
||||
double h = currentParam.getEntry(0);
|
||||
double s = currentParam.getEntry(1);
|
||||
double v = currentParam.getEntry(2);
|
||||
//double rh = currentParam.getEntry(3);
|
||||
//double rs = currentParam.getEntry(4);
|
||||
//double rv = currentParam.getEntry(5);
|
||||
|
||||
RealVector value = new ArrayRealVector(observedPoints.length);
|
||||
RealMatrix jacobian = new Array2DRowRealMatrix(observedPoints.length, 3);
|
||||
|
||||
|
||||
for (int i = 0; i < observedPoints.length; ++i) {
|
||||
//Random rand = new Random();
|
||||
double delta = 1;
|
||||
|
||||
double val = function(imgs, marked, h, s, v);
|
||||
|
||||
double derivativeH = (function(imgs, marked, h+delta, s, v)-function(imgs, marked, h, s, v))/delta;
|
||||
double derivativeS = (function(imgs, marked, h, s+delta, v)-function(imgs, marked, h, s, v))/delta;
|
||||
double derivativeV = (function(imgs, marked, h, s, v+delta)-function(imgs, marked, h, s, v))/delta;
|
||||
|
||||
//double derivativeRH = (function(imgs, marked, h, s, v, rh+delta, rs, rv)-function(imgs, marked, h, s, v, rh, rs, rv))/delta;
|
||||
//double derivativeRS = (function(imgs, marked, h, s, v, rh, rs+delta, rv)-function(imgs, marked, h, s, v, rh, rs, rv))/delta;
|
||||
//double derivativeRV = (function(imgs, marked, h, s, v, rh, rs, rv+delta)-function(imgs, marked, h, s, v, rh, rs, rv))/delta;
|
||||
|
||||
value.setEntry(i, val);
|
||||
|
||||
jacobian.setEntry(i, 0, derivativeH);
|
||||
jacobian.setEntry(i, 1, derivativeS);
|
||||
jacobian.setEntry(i, 2, derivativeV);
|
||||
|
||||
//jacobian.setEntry(i, 3, derivativeRH);
|
||||
//jacobian.setEntry(i, 4, derivativeRS);
|
||||
//jacobian.setEntry(i, 5, derivativeRV);
|
||||
|
||||
}
|
||||
|
||||
return new org.apache.commons.math3.util.Pair<RealVector, RealMatrix>(value, jacobian);
|
||||
}
|
||||
};
|
||||
|
||||
double[] zeros = new double[observedPoints.length];
|
||||
Arrays.fill(zeros, 1.0); // prev version
|
||||
|
||||
// least squares problem to solve : modeled radius should be close to target radius
|
||||
LeastSquaresProblem problem = new LeastSquaresBuilder().
|
||||
start(new double[] {10, 244, 74}).
|
||||
model(distances).
|
||||
target(zeros).
|
||||
lazyEvaluation(false).
|
||||
maxEvaluations(1000_000).
|
||||
maxIterations(1000_000).
|
||||
build();
|
||||
|
||||
LevenbergMarquardtOptimizer optimazer = new LevenbergMarquardtOptimizer();
|
||||
|
||||
try {
|
||||
LeastSquaresOptimizer.Optimum optimum = optimazer.optimize(problem);
|
||||
|
||||
/*
|
||||
double H = optimum.getPoint().getEntry(0);
|
||||
double S = optimum.getPoint().getEntry(1);
|
||||
double V = optimum.getPoint().getEntry(2);
|
||||
|
||||
double RH = optimum.getPoint().getEntry(3);
|
||||
double RS = optimum.getPoint().getEntry(4);
|
||||
double RV = optimum.getPoint().getEntry(5);
|
||||
|
||||
|
||||
rms = optimum.getRMS();
|
||||
*/
|
||||
|
||||
//System.out.println(rms);
|
||||
|
||||
} catch(org.apache.commons.math3.exception.TooManyEvaluationsException ex) {
|
||||
System.out.println("Too many evaluations for Levenberg-Marquardt optimizer. Trapeze parameters will be calculating using brute-force method.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private double function(List<Mat> imgs, List<Mat> marked, double h, double s, double v) {
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(h, s, v);
|
||||
Scalar range1 = new Scalar(20, 200, 200);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
HSVBinarization hsv = new HSVBinarization(parameters);
|
||||
|
||||
double sumJIndex = 0;
|
||||
|
||||
for(int i=0; i<imgs.size(); i++) {
|
||||
Mat input = imgs.get(i);
|
||||
Mat mark = marked.get(i);
|
||||
|
||||
Mat binarized = hsv.apply(input);
|
||||
|
||||
sumJIndex += MatFunctions.jaccardIndex(binarized, mark);
|
||||
}
|
||||
|
||||
System.out.println("HSV: " + h + " " + s + " " + v);
|
||||
//System.out.println("R-HSV: " + rh + " " + rs + " " + rv);
|
||||
|
||||
|
||||
double average = sumJIndex/imgs.size();
|
||||
|
||||
System.out.println("JIndex: " + average);
|
||||
|
||||
System.out.println();
|
||||
|
||||
return average;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
package ru.delkom07.recognition.potatoes.training;
|
||||
|
||||
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Rect;
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
|
||||
public class MatFunctions {
|
||||
|
||||
public static double[] calculatePrecisionRecall(Mat img, Mat marked) {
|
||||
int falsePositive = 0;
|
||||
int falseNegative = 0;
|
||||
|
||||
int truePositive = 0;
|
||||
//int trueNegative = 0;
|
||||
|
||||
for(int i=0; i<img.rows(); i++) {
|
||||
for(int j=0; j<img.cols(); j++) {
|
||||
double imgPix = img.get(i, j)[0];
|
||||
double markedPix = marked.get(i, j)[0];
|
||||
|
||||
if(imgPix != markedPix) {
|
||||
if(imgPix != 0) {
|
||||
falsePositive++;
|
||||
}
|
||||
|
||||
if(markedPix != 0) {
|
||||
falseNegative++;
|
||||
}
|
||||
} else {
|
||||
truePositive++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double precision = truePositive / (truePositive + falsePositive);
|
||||
double recall = truePositive / (truePositive + falseNegative);
|
||||
|
||||
return new double[] {precision, recall};
|
||||
}
|
||||
|
||||
|
||||
public static double jaccardIndex(Mat img, Mat marked) {
|
||||
int ABintersectionCount = 0;
|
||||
int Acount = 0;
|
||||
int Bcount = 0;
|
||||
|
||||
for(int i=0; i<img.rows(); i++) {
|
||||
for(int j=0; j<img.cols(); j++) {
|
||||
double imgPix = img.get(i, j)[0];
|
||||
double markedPix = marked.get(i, j)[0];
|
||||
|
||||
if(imgPix != 0) {
|
||||
Acount++;
|
||||
}
|
||||
|
||||
if(markedPix != 0) {
|
||||
Bcount++;
|
||||
}
|
||||
|
||||
if(imgPix != 0 && markedPix != 0) {
|
||||
ABintersectionCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (double)ABintersectionCount / (double)(Acount + Bcount - ABintersectionCount);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Mat fillRectWithColor(Mat src, Rect rect, Scalar color) throws Exception {
|
||||
Mat result = src.clone();
|
||||
|
||||
byte[] colorInByteArr = null;
|
||||
|
||||
if(1 == src.channels()) {
|
||||
colorInByteArr = new byte[]{
|
||||
(byte)(color.val[0])};
|
||||
} else if(3 == src.channels()) {
|
||||
colorInByteArr = new byte[]{
|
||||
(byte)(color.val[0]),
|
||||
(byte)(color.val[1]),
|
||||
(byte)(color.val[2])};
|
||||
} else {
|
||||
throw new Exception("Source image has count of channels != '1' or '3'.");
|
||||
}
|
||||
|
||||
for(int i=rect.x; i<rect.x+rect.width; i++) {
|
||||
for(int j=rect.y; j<rect.y+rect.height; j++) {
|
||||
result.put(j, i, colorInByteArr);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void fillRectWithGradient(Mat img, Rect rect, Scalar xColor1, Scalar xColor2) {
|
||||
double lamdaX = 0;
|
||||
//double lamdaY = 0;
|
||||
double counterX = 1;
|
||||
//double counterY = 1;
|
||||
for(int i=rect.x; i<rect.x+rect.width; i++) {
|
||||
counterX++;
|
||||
for(int j=rect.y; j<rect.y+rect.height; j++) {
|
||||
//counterY++;
|
||||
|
||||
lamdaX = counterX/(double)rect.width;
|
||||
//lamdaY = counterY/(double)rect.height;
|
||||
|
||||
img.put(j, i, new byte[]{
|
||||
(byte)(xColor1.val[0]*(1-lamdaX) + xColor2.val[0]*(lamdaX)),
|
||||
(byte)(xColor1.val[1]*(1-lamdaX) + xColor2.val[1]*(lamdaX)),
|
||||
(byte)(xColor1.val[2]*(1-lamdaX) + xColor2.val[2]*(lamdaX))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static double[] getMiddleColour(Mat img, int row, int col) {
|
||||
double result[] = new double[3];
|
||||
|
||||
double[] pixel1 = img.get(row-1, col-1);
|
||||
double[] pixel2 = img.get(row-1, col);
|
||||
double[] pixel3 = img.get(row-1, col+1);
|
||||
double[] pixel4 = img.get(row, col-1);
|
||||
double[] pixel5 = img.get(row, col);
|
||||
double[] pixel6 = img.get(row, col+1);
|
||||
double[] pixel7 = img.get(row+1, col-1);
|
||||
double[] pixel8 = img.get(row+1, col);
|
||||
double[] pixel9 = img.get(row+1, col+1);
|
||||
|
||||
result[0] = (pixel1[0]+pixel2[0]+pixel3[0]+pixel4[0]+pixel5[0]+pixel6[0]+pixel7[0]+pixel8[0]+pixel9[0])/9.0;
|
||||
result[1] = (pixel1[1]+pixel2[1]+pixel3[1]+pixel4[1]+pixel5[1]+pixel6[1]+pixel7[1]+pixel8[1]+pixel9[1])/9.0;
|
||||
result[2] = (pixel1[2]+pixel2[2]+pixel3[2]+pixel4[2]+pixel5[2]+pixel6[2]+pixel7[2]+pixel8[2]+pixel9[2])/9.0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static Mat skeletonize1(Mat src, Rect contourRect) {
|
||||
Mat skeleton = src;//src.clone();
|
||||
|
||||
int iterationsAmount = 90;
|
||||
for(int k=0; k<iterationsAmount; k++) {
|
||||
System.out.println("Iteration: " + k);
|
||||
|
||||
// Поиск "внешних" точек области
|
||||
List<Point> outsidePoints = new LinkedList<Point>();
|
||||
for(int i=contourRect.y; i<contourRect.y+contourRect.height; i++) {
|
||||
for(int j=contourRect.x; j<contourRect.x+contourRect.width; j++) {
|
||||
if((byte)skeleton.get(i, j)[0] != 0) {
|
||||
int[] fourNeighbors = new int[4];
|
||||
|
||||
// four direct neighbors
|
||||
fourNeighbors[0] = i-1 > 0 ? (int)skeleton.get(i-1, j)[0] & 0xff : 0;
|
||||
fourNeighbors[1] = j+1 < src.cols() ? (int)skeleton.get(i, j+1)[0] & 0xff : 0;
|
||||
fourNeighbors[2] = i+1 < src.rows() ? (int)skeleton.get(i+1, j)[0] & 0xff : 0;
|
||||
fourNeighbors[3] = j-1 > 0 ? (int)skeleton.get(i, j-1)[0] & 0xff : 0;
|
||||
|
||||
for(int neighbor : fourNeighbors) {
|
||||
if(neighbor == 0) {
|
||||
outsidePoints.add(new Point(j, i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(Point removingPoint : outsidePoints) {
|
||||
if(!isTearsUpArea(skeleton, removingPoint)) {
|
||||
skeleton.put((int)removingPoint.y, (int)removingPoint.x, (byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Разрывает ли область удаление данной точки с изображения.
|
||||
* @param img
|
||||
* @param point
|
||||
* @return true, если разрывает; false, если нет.
|
||||
*/
|
||||
public static boolean isTearsUpArea(Mat img, Point point) {
|
||||
int pointRow = (int)point.y;
|
||||
int pointCol = (int)point.x;
|
||||
|
||||
boolean[] neighbors = new boolean[9];
|
||||
|
||||
// считывание соседний пикслелей
|
||||
neighbors[0] = img.get(pointRow-1, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[1] = img.get(pointRow-1, pointCol )[0] != 0.0 ? true : false;
|
||||
neighbors[2] = img.get(pointRow-1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
neighbors[3] = img.get(pointRow, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[4] = img.get(pointRow, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
neighbors[5] = img.get(pointRow+1, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[6] = img.get(pointRow+1, pointCol)[0] != 0.0 ? true : false;
|
||||
neighbors[7] = img.get(pointRow+1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
// вертикальное разделение
|
||||
if(!neighbors[1] && !neighbors[6] && (neighbors[0] || neighbors[3] || neighbors[5]) && (neighbors[2] || neighbors[4] || neighbors[7]))
|
||||
return true;
|
||||
|
||||
// горизонтальное разделение
|
||||
if(!neighbors[3] && !neighbors[4] && (neighbors[0] || neighbors[1] || neighbors[2]) && (neighbors[5] || neighbors[6] || neighbors[7]))
|
||||
return true;
|
||||
|
||||
// случаи изолирования диагональных пикслелей
|
||||
// верхний левый
|
||||
if(!neighbors[3] && !neighbors[1] && (neighbors[5] || neighbors[6] || neighbors[7] || neighbors[4] || neighbors[2]) && neighbors[0])
|
||||
return true;
|
||||
|
||||
// нижний правый
|
||||
if(!neighbors[6] && !neighbors[4] && (neighbors[5] || neighbors[3] || neighbors[0] || neighbors[1] || neighbors[2]) && neighbors[7])
|
||||
return true;
|
||||
|
||||
// нижний левый
|
||||
if(!neighbors[3] && !neighbors[6] && (neighbors[0] || neighbors[1] || neighbors[2] || neighbors[4] || neighbors[7]) && neighbors[5])
|
||||
return true;
|
||||
|
||||
// верхний правый
|
||||
if(!neighbors[1] && !neighbors[4] && (neighbors[0] || neighbors[3] || neighbors[5] || neighbors[6] || neighbors[7]) && neighbors[2])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Разрывает ли область удаление данной точки с изображения.
|
||||
* @param img
|
||||
* @param point
|
||||
* @return true, если разрывает; false, если нет.
|
||||
*/
|
||||
public static boolean isReduceSkeleton(Mat img, Point point) {
|
||||
int pointRow = (int)point.y;
|
||||
int pointCol = (int)point.x;
|
||||
|
||||
boolean[] neighbors = new boolean[9];
|
||||
|
||||
// считывание соседний пикслелей
|
||||
neighbors[0] = img.get(pointRow-1, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[1] = img.get(pointRow-1, pointCol )[0] != 0.0 ? true : false;
|
||||
neighbors[2] = img.get(pointRow-1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
neighbors[3] = img.get(pointRow, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[4] = img.get(pointRow, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
neighbors[5] = img.get(pointRow+1, pointCol-1)[0] != 0.0 ? true : false;
|
||||
neighbors[6] = img.get(pointRow+1, pointCol)[0] != 0.0 ? true : false;
|
||||
neighbors[7] = img.get(pointRow+1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
// укорачивание остова толщиной в 1 пиксель
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[7]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow+2, pointCol+2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow+1, pointCol+2 )[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+2, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[7] && neighbors[6]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow+2, pointCol)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow+2, pointCol-1 )[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+2, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[7] && !neighbors[6] && neighbors[5]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow+2, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow+1, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+2, pointCol-1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[7] && !neighbors[5] && !neighbors[6] && neighbors[4]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow, pointCol+2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-1, pointCol+2)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+1, pointCol+1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[7] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[3]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-1, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow+1, pointCol-2)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[7] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[2]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow-2, pointCol+2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-2, pointCol+1)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow-1, pointCol+2)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!neighbors[0] && !neighbors[7] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[1]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow-2, pointCol)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-2, pointCol+1)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow-2, pointCol-1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!neighbors[7] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[0]) {
|
||||
boolean[] neighbors2 = new boolean[3];
|
||||
|
||||
neighbors2[0] = img.get(pointRow-2, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[1] = img.get(pointRow-1, pointCol-2)[0] != 0.0 ? true : false;
|
||||
neighbors2[2] = img.get(pointRow-2, pointCol-1)[0] != 0.0 ? true : false;
|
||||
|
||||
if(neighbors2[0] && !neighbors2[1] && !neighbors2[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Разрывает ли область удаление данной точки с изображения.
|
||||
* @param img
|
||||
* @param point
|
||||
* @return true, если разрывает; false, если нет.
|
||||
*/
|
||||
public static boolean isTearsUpAreaOrReduceSkeleton(Mat img, Point point) {
|
||||
int pointRow = (int)point.y;
|
||||
int pointCol = (int)point.x;
|
||||
|
||||
boolean[] neighbors = new boolean[9];
|
||||
|
||||
// считывание соседний пикслелей
|
||||
neighbors[0] = (pointRow-1 > 0 && pointCol-1 > 0) ? img.get(pointRow-1, pointCol-1)[0] != 0.0 ? true : false : false;
|
||||
neighbors[1] = (pointRow-1 > 0) ? img.get(pointRow-1, pointCol )[0] != 0.0 ? true : false : false;
|
||||
neighbors[2] = (pointRow-1 > 0 && pointCol+1 < img.cols()) ? img.get(pointRow-1, pointCol+1)[0] != 0.0 ? true : false : false;
|
||||
|
||||
neighbors[3] = (pointCol-1 > 0) ? img.get(pointRow, pointCol-1)[0] != 0.0 ? true : false : false;
|
||||
neighbors[4] = (pointCol+1 < img.cols()) ? img.get(pointRow, pointCol+1)[0] != 0.0 ? true : false : false;
|
||||
|
||||
neighbors[5] = (pointRow+1 < img.rows() && pointCol-1 > 0) ? img.get(pointRow+1, pointCol-1)[0] != 0.0 ? true : false : false;
|
||||
neighbors[6] = (pointRow+1 < img.rows()) ? img.get(pointRow+1, pointCol)[0] != 0.0 ? true : false : false;
|
||||
neighbors[7] = (pointRow+1 < img.rows() && pointCol+1 < img.cols()) ? img.get(pointRow+1, pointCol+1)[0] != 0.0 ? true : false : false;
|
||||
|
||||
// вертикальное разделение
|
||||
if(!neighbors[1] && !neighbors[6] && (neighbors[0] || neighbors[3] || neighbors[5]) && (neighbors[2] || neighbors[4] || neighbors[7]))
|
||||
return true;
|
||||
|
||||
// горизонтальное разделение
|
||||
if(!neighbors[3] && !neighbors[4] && (neighbors[0] || neighbors[1] || neighbors[2]) && (neighbors[5] || neighbors[6] || neighbors[7]))
|
||||
return true;
|
||||
|
||||
// случаи изолирования диагональных пикслелей
|
||||
// верхний левый
|
||||
if(!neighbors[3] && !neighbors[1] && (neighbors[5] || neighbors[6] || neighbors[7] || neighbors[4] || neighbors[2]) && neighbors[0])
|
||||
return true;
|
||||
|
||||
// нижний правый
|
||||
if(!neighbors[6] && !neighbors[4] && (neighbors[5] || neighbors[3] || neighbors[0] || neighbors[1] || neighbors[2]) && neighbors[7])
|
||||
return true;
|
||||
|
||||
// нижний левый
|
||||
if(!neighbors[3] && !neighbors[6] && (neighbors[0] || neighbors[1] || neighbors[2] || neighbors[4] || neighbors[7]) && neighbors[5])
|
||||
return true;
|
||||
|
||||
// верхний правый
|
||||
if(!neighbors[1] && !neighbors[4] && (neighbors[0] || neighbors[3] || neighbors[5] || neighbors[6] || neighbors[7]) && neighbors[2])
|
||||
return true;
|
||||
|
||||
|
||||
// укорачивание остова толщиной в 1 пиксель
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[7])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[7] && neighbors[6])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[7] && !neighbors[6] && neighbors[5])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[7] && !neighbors[5] && !neighbors[6] && neighbors[4])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[2] && !neighbors[7] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[3])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[1] && !neighbors[7] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[2])
|
||||
return true;
|
||||
|
||||
if(!neighbors[0] && !neighbors[7] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[1])
|
||||
return true;
|
||||
|
||||
if(!neighbors[7] && !neighbors[1] && !neighbors[2] && !neighbors[3] && !neighbors[4] && !neighbors[5] && !neighbors[6] && neighbors[0])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static Mat skeletonize0(Mat src, Rect contourRect) {
|
||||
Mat skeleton = src; //src.clone();
|
||||
|
||||
List<Point> outsidePoints = new LinkedList<Point>();
|
||||
int iterationsAmount = 100;
|
||||
for(int k=0; k<iterationsAmount; k++) {
|
||||
System.out.println("Iteration: " + k);
|
||||
|
||||
// Выбираются "внешние точки".
|
||||
for(int i=contourRect.y; i<contourRect.y+contourRect.height; i++) {
|
||||
for(int j=contourRect.x; j<contourRect.x+contourRect.width; j++) {
|
||||
if((byte)skeleton.get(i, j)[0] != 0) {
|
||||
int[] fourNeighbors = new int[4];
|
||||
|
||||
// four direct neighbors
|
||||
fourNeighbors[0] = i-1 > 0 ? (int)skeleton.get(i-1, j)[0] & 0xff : 0;
|
||||
fourNeighbors[1] = j+1 < skeleton.cols() ? (int)skeleton.get(i, j+1)[0] & 0xff : 0;
|
||||
fourNeighbors[2] = i+1 < skeleton.rows() ? (int)skeleton.get(i+1, j)[0] & 0xff : 0;
|
||||
fourNeighbors[3] = j-1 > 0 ? (int)skeleton.get(i, j-1)[0] & 0xff : 0;
|
||||
|
||||
for(int neighbor : fourNeighbors) {
|
||||
if(neighbor == 0) {
|
||||
outsidePoints.add(new Point(j, i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Внешние точки удаляются с изображения и их список очищается.
|
||||
//for(Point removingPoint : outsidePoints) {
|
||||
/*
|
||||
for(int l=0; l<outsidePoints.size(); l++) {
|
||||
Point removingPoint = outsidePoints.get(l);
|
||||
|
||||
if(isReduceSkeleton(skeleton, removingPoint)) {
|
||||
outsidePoints.remove(l--);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
for(Point removingPoint : outsidePoints) {
|
||||
if(!isTearsUpArea(skeleton, removingPoint) && !isReduceSkeleton(skeleton, removingPoint)) {
|
||||
skeleton.put((int)removingPoint.y, (int)removingPoint.x, 0);
|
||||
}
|
||||
}
|
||||
|
||||
outsidePoints.clear();
|
||||
}
|
||||
|
||||
return skeleton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
package ru.delkom07.recognition.potatoes.training;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.opencv.core.Core;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.core.Size;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.wheatdb.seedcounter.processor.filters.HSVBinarization;
|
||||
import org.wheatdb.seedcounter.server.DefaultFilesAndDirs;
|
||||
|
||||
import ru.delkom07.util.Pair;
|
||||
|
||||
|
||||
|
||||
public class PotatoBinarizationTraining {
|
||||
static class ImageFilter implements java.io.FileFilter {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
if(pathname.getName().matches(".+\\.(png|PNG|jpg|JPG|bmp|BMP)"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String changeExtension(String fileName, String extension) {
|
||||
return FilenameUtils.removeExtension(fileName) + "." + extension;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
||||
|
||||
File inputDir = DefaultFilesAndDirs.inputDir;
|
||||
File outputDir = DefaultFilesAndDirs.outputDir;
|
||||
File markedDir = DefaultFilesAndDirs.markedDir;
|
||||
|
||||
File outputDataFile = new File(outputDir, "data.txt");
|
||||
PrintWriter pw = new PrintWriter(outputDataFile);
|
||||
|
||||
File[] inputImgFiles = inputDir.listFiles(new PotatoBinarizationTraining.ImageFilter());
|
||||
|
||||
List<Mat> inputs = new ArrayList<Mat>();
|
||||
List<Mat> markeds = new ArrayList<Mat>();
|
||||
|
||||
for(File inputImgFile: inputImgFiles) {
|
||||
File markedImgFile = new File(markedDir, changeExtension(inputImgFile.getName(), "png"));
|
||||
|
||||
Mat input = Imgcodecs.imread(inputImgFile.getCanonicalPath());
|
||||
Mat marked = Imgcodecs.imread(markedImgFile.getCanonicalPath());
|
||||
|
||||
|
||||
|
||||
Size newSize = new Size(input.size().width/8, input.size().height/8);
|
||||
Imgproc.resize(input, input, newSize);
|
||||
Imgproc.resize(marked, marked, newSize);
|
||||
|
||||
|
||||
inputs.add(input);
|
||||
markeds.add(marked);
|
||||
}
|
||||
|
||||
// Training
|
||||
//List<HSVParameters> parentPopulation = generateRandomPopulation(300, 3);
|
||||
//List<HSVParameters> parentPopulation = getWhitePotatoesHSVPreset();
|
||||
//List<HSVParameters> parentPopulation = getDarkPotatoesHSVPreset2();
|
||||
|
||||
List<HSVParameters> parentPopulation = getCommonPotatoesHSVPreset();
|
||||
//parentPopulation.addAll(getCommonPotatoesHSVPreset2());
|
||||
//parentPopulation.addAll(getWhitePotatoesHSVPreset());
|
||||
//parentPopulation.addAll(getWhitePotatoesHSVPreset2());
|
||||
//parentPopulation.addAll(getDarkPotatoesHSVPreset());
|
||||
//parentPopulation.addAll(getDarkPotatoesHSVPreset2());
|
||||
|
||||
for(int i=0; i<100; i++) {
|
||||
System.out.println("Training iteration: " + i);
|
||||
|
||||
List<HSVParameters> childPopulation = new LinkedList<HSVParameters>();
|
||||
if(i==0) {
|
||||
childPopulation.addAll(parentPopulation);
|
||||
} else {
|
||||
childPopulation.addAll(newGeneration(parentPopulation, 10));
|
||||
}
|
||||
|
||||
for(HSVParameters childParam : childPopulation) {
|
||||
HSVBinarization hsv = new HSVBinarization(childParam.getParams());
|
||||
|
||||
double middleJIndex = 0;
|
||||
double sumJIndex = 0;
|
||||
|
||||
for(int imgI=0; imgI<inputs.size(); imgI++) {
|
||||
Mat input = inputs.get(imgI);
|
||||
Mat marked = markeds.get(imgI);
|
||||
|
||||
Mat binarized = hsv.apply(input);
|
||||
|
||||
sumJIndex += MatFunctions.jaccardIndex(binarized, marked);
|
||||
binarized.release();
|
||||
}
|
||||
|
||||
middleJIndex = sumJIndex/((double)inputImgFiles.length);
|
||||
childParam.setJIndex(middleJIndex);
|
||||
System.out.println(middleJIndex);
|
||||
}
|
||||
|
||||
Collections.sort(childPopulation, new Comparator<HSVParameters>() {
|
||||
@Override
|
||||
public int compare(HSVParameters arg0, HSVParameters arg1) {
|
||||
if(arg0.getJIndex() < arg1.getJIndex()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(arg0.getJIndex() > arg1.getJIndex()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
parentPopulation.clear();
|
||||
for(int l=0; l<10 && l<childPopulation.size(); l++) {
|
||||
double jIndex = childPopulation.get(l).getJIndex();
|
||||
|
||||
if(l>2 && jIndex < 0.6) {
|
||||
break;
|
||||
}
|
||||
|
||||
System.out.println("\nOne of best param: " + childPopulation.get(l).getParams().toString());
|
||||
System.out.println("JIndex: " + jIndex + "\n");
|
||||
|
||||
pw.println("\nOne of best param: " + childPopulation.get(l).getParams().toString());
|
||||
pw.println("JIndex: " + jIndex + "\n");
|
||||
pw.flush();
|
||||
|
||||
parentPopulation.add(childPopulation.get(l));
|
||||
}
|
||||
childPopulation.clear();
|
||||
}
|
||||
|
||||
for(int j=0; j<inputs.size(); j++) {
|
||||
inputs.get(j).release();
|
||||
markeds.get(j).release();
|
||||
}
|
||||
|
||||
|
||||
HSVParameters bestParam = null;
|
||||
double bestJIndex = 0;
|
||||
for(HSVParameters hsvParam : parentPopulation) {
|
||||
if(hsvParam.getJIndex() > bestJIndex) {
|
||||
bestJIndex = hsvParam.getJIndex();
|
||||
bestParam = hsvParam;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(bestParam != null) {
|
||||
for(File inputImgFile: inputImgFiles) {
|
||||
File outputImgFile = new File(outputDir, changeExtension("binarized_" + inputImgFile.getName(), "png"));
|
||||
|
||||
Mat input = Imgcodecs.imread(inputImgFile.getCanonicalPath());
|
||||
|
||||
HSVBinarization hsv = new HSVBinarization(bestParam.getParams());
|
||||
Mat binarized = hsv.apply(input);
|
||||
|
||||
Imgcodecs.imwrite(outputImgFile.getCanonicalPath(), binarized);
|
||||
//new Display().setImage(binarized);
|
||||
|
||||
input.release();
|
||||
binarized.release();
|
||||
|
||||
System.out.println("Result best param: " + bestParam.toString());
|
||||
pw.println("Result best param: " + bestParam.toString());
|
||||
pw.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pw.close();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static List<HSVParameters> generateRandomPopulation(int populationSize, int paramDimention) {
|
||||
List<HSVParameters> randomPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
for(int i=0; i<populationSize; i++) {
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
for(int j=0; j<paramDimention; j++) {
|
||||
int randH = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
int randS = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
int randV = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
|
||||
int randDivH = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
int randDivS = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
int randDivV = ThreadLocalRandom.current().nextInt(0, 255);
|
||||
|
||||
Scalar target = new Scalar(randH, randS, randV);
|
||||
Scalar range = new Scalar(randDivH, randDivS, randDivV);
|
||||
|
||||
parameters.add(new Pair<Scalar, Scalar>(target, range));
|
||||
}
|
||||
|
||||
randomPopulation.add(new HSVParameters(parameters));
|
||||
}
|
||||
|
||||
return randomPopulation;
|
||||
}
|
||||
|
||||
|
||||
public static List<HSVParameters> getCommonPotatoesHSVPreset() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target3 = new Scalar(1, 241, 73);
|
||||
Scalar range3 = new Scalar(56, 208, 197);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(171, 81, 144);
|
||||
Scalar range4 = new Scalar(20, 43, 108);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
public static List<HSVParameters> getCommonPotatoesHSVPreset2() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(161.0, 81.0, 144.0);
|
||||
Scalar range1 = new Scalar(29.0, 43.0, 108.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(161.0, 72.0, 142.0);
|
||||
Scalar range2 = new Scalar(27.0, 38.0, 109.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
Scalar target3 = new Scalar(15.0, 138.0, 207.0);
|
||||
Scalar range3 = new Scalar(41.0, 94.0, 144.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(161.0, 72.0, 142.0);
|
||||
Scalar range4 = new Scalar(27.0, 38.0, 109.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
Scalar target5 = new Scalar(37.0, 231.0, 41.0);
|
||||
Scalar range5 = new Scalar(51.0, 198.0, 145.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target5, range5));
|
||||
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<HSVParameters> getWhitePotatoesHSVPreset() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(14.0, 139.0, 211.0);
|
||||
Scalar range1 = new Scalar(41.0, 90.0, 147.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(12.0, 134.0, 201.0);
|
||||
Scalar range2 = new Scalar(37.0, 94.0, 142.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
Scalar target3 = new Scalar(18.0, 138.0, 204.0);
|
||||
Scalar range3 = new Scalar(46.0, 91.0, 149.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target3, range3));
|
||||
|
||||
Scalar target4 = new Scalar(13.0, 133.0, 207.0);
|
||||
Scalar range4 = new Scalar(42.0, 89.0, 141.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target4, range4));
|
||||
|
||||
Scalar target5 = new Scalar(42.0, 238.0, 50.0);
|
||||
Scalar range5 = new Scalar(54.0, 199.0, 153.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target5, range5));
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
|
||||
public static List<HSVParameters> getWhitePotatoesHSVPreset2() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(99, 130, 150);
|
||||
Scalar range1 = new Scalar(188, 74, 232);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static List<HSVParameters> getDarkPotatoesHSVPreset() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
Scalar target1 = new Scalar(13.0, 192.0, 27.0);
|
||||
Scalar range1 = new Scalar(241.0, 158.0, 141.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
Scalar target2 = new Scalar(170.0, 35.0, 44.0);
|
||||
Scalar range2 = new Scalar(40.0, 50.0, 30.0);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
public static List<HSVParameters> getDarkPotatoesHSVPreset2() {
|
||||
List<HSVParameters> presetPopulation = new LinkedList<HSVParameters>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> parameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
// fiolet
|
||||
Scalar target1 = new Scalar(170, 75, 160);
|
||||
Scalar range1 = new Scalar(25, 40, 100);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target1, range1));
|
||||
|
||||
// yellow
|
||||
Scalar target2 = new Scalar(10, 87, 200);
|
||||
Scalar range2 = new Scalar(10, 50, 150);
|
||||
parameters.add(new Pair<Scalar, Scalar>(target2, range2));
|
||||
|
||||
presetPopulation.add(new HSVParameters(parameters));
|
||||
|
||||
return presetPopulation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static List<HSVParameters> newGeneration(List<HSVParameters> generation, int increment) {
|
||||
List<HSVParameters> newGeneration = new LinkedList<HSVParameters>();
|
||||
|
||||
Random generator = new Random();
|
||||
for(int i=0; i<increment; i++) {
|
||||
int rand1 = generator.nextInt(generation.size());
|
||||
int rand2 = generator.nextInt(generation.size());
|
||||
|
||||
|
||||
HSVParameters child = cross(generation.get(rand1), generation.get(rand2));
|
||||
if(generator.nextInt(5) < 2) {
|
||||
child = mutation(child);
|
||||
}
|
||||
|
||||
newGeneration.add(child);
|
||||
|
||||
}
|
||||
|
||||
newGeneration.addAll(generation);
|
||||
|
||||
return newGeneration;
|
||||
}
|
||||
|
||||
|
||||
public static HSVParameters cross(HSVParameters parent1, HSVParameters parent2) {
|
||||
List<Pair<Scalar, Scalar>> params = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
|
||||
List<Pair<Scalar, Scalar>> prt1Params = parent1.getParams();
|
||||
List<Pair<Scalar, Scalar>> prt2Params = parent2.getParams();
|
||||
|
||||
int prt1ParamsSize = prt1Params.size();
|
||||
int prt2ParamsSize = prt2Params.size();
|
||||
|
||||
int childParamsSize = Integer.max(prt1ParamsSize, prt2ParamsSize);
|
||||
|
||||
Random generator = new Random();
|
||||
for(int i=0; i<childParamsSize; i++) {
|
||||
if(i%2 !=0) {
|
||||
int rand = generator.nextInt(prt1ParamsSize);
|
||||
params.add(prt1Params.get(rand));
|
||||
} else {
|
||||
int rand = generator.nextInt(prt2ParamsSize);
|
||||
params.add(prt2Params.get(rand));
|
||||
}
|
||||
}
|
||||
|
||||
return new HSVParameters(params);
|
||||
}
|
||||
|
||||
|
||||
public static HSVParameters mutation(HSVParameters parent) {
|
||||
final int mutationSize = 10;
|
||||
|
||||
List<Pair<Scalar, Scalar>> childParameters = new LinkedList<Pair<Scalar, Scalar>>();
|
||||
for(Pair<Scalar, Scalar> param : parent.getParams()) {
|
||||
Scalar parentTarget = param.getLeft();
|
||||
Scalar parentRange = param.getRight();
|
||||
|
||||
int mutatedH = (int)(parentTarget.val[0]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
int mutatedS = (int)(parentTarget.val[1]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
int mutatedV = (int)(parentTarget.val[2]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
|
||||
int mutatedDivH = (int)(parentRange.val[0]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
int mutatedDivS = (int)(parentRange.val[1]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
int mutatedDivV = (int)(parentRange.val[2]+ThreadLocalRandom.current().nextInt(0, mutationSize) - mutationSize/2);
|
||||
|
||||
// проврка что > 0 && < 255
|
||||
mutatedH = mutatedH < 0 ? 0 : (mutatedH > 255 ? 255 : mutatedH);
|
||||
mutatedS = mutatedS < 0 ? 0 : (mutatedS > 255 ? 255 : mutatedS);
|
||||
mutatedV = mutatedV < 0 ? 0 : (mutatedV > 255 ? 255 : mutatedV);
|
||||
|
||||
mutatedDivH = mutatedDivH < 0 ? 0 : (mutatedDivH > 255 ? 255 : mutatedDivH);
|
||||
mutatedDivS = mutatedDivS < 0 ? 0 : (mutatedDivS > 255 ? 255 : mutatedDivS);
|
||||
mutatedDivV = mutatedDivV < 0 ? 0 : (mutatedDivV > 255 ? 255 : mutatedDivV);
|
||||
|
||||
Scalar childTarget = new Scalar(mutatedH, mutatedS, mutatedV);
|
||||
Scalar childRange = new Scalar(mutatedDivH, mutatedDivS, mutatedDivV);
|
||||
|
||||
|
||||
childParameters.add(new Pair<Scalar, Scalar>(childTarget, childRange));
|
||||
}
|
||||
|
||||
return new HSVParameters(childParameters);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user