Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions .idea/runConfigurations/Image_conv__jmhQuick_.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

143 changes: 143 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Image_conv – Kotlin Image Convolution

## Project Overview
**Image_conv** is a Kotlin-based image convolution project that demonstrates and benchmarks multiple convolution strategies using OpenCV.
It applies various 3×3 and 5×5 filters (e.g., blur, Gaussian blur, sharpen, edge detection, etc.) to images using both sequential and parallel approaches.

## Quick Start

### Build the Project
Make sure you have **Java (JDK 8 or higher)** and Gradle installed.

```bash
git clone https://github.com/SecretPersona5/Image_conv.git
cd Image_conv
./gradlew build

```
I would recommend simply shift + f10 to launch the program

This will compile the Kotlin source and download the required OpenCV native libraries.

---

### Run Interactively
Launch the program in **interactive mode** (with a console menu):

```bash
./gradlew run
# or:
./gradlew run --args="-i"
```

In this mode, you will be prompted to:
- Select an input image
- Choose a convolution mode (`seq`, `row`, `col`, `grid`, `pix`)
- Pick a filter (e.g., `blur_3x3`, `sharpen`, etc.)
- Optionally process a whole directory (pipeline mode)

---

### Process a Single Image via CLI
Run a single image with a specific mode:

```bash
./gradlew run --args="<mode> <inputImagePath> [gridBlockSize]"
```

Example (row-parallel convolution):

```bash
./gradlew run --args="row path/to/image.jpg"
```

For **grid mode**, add a block size:

```bash
./gradlew run --args="grid path/to/image.jpg 64"
```

---

### Pipeline Mode (Batch Processing)
Process **all images in a directory** concurrently:

```bash
./gradlew run --args="pipe <inputDir> <outputDir> <mode> [blockSize] [convWorkers] [saveWorkers] [capacity]"
```

Example:

```bash
./gradlew run --args="pipe ./photos ./photos_out grid 128 8 1 8"
```

Output images will be saved in `<outputDir>`.

---

## Benchmarks

### Microbenchmark (JMH)
Run quick JMH benchmarks:

```bash
./gradlew jmhQuick
```

Results are saved to:

```
build/bench/jmh_quick.csv
```

It measures performance for different:
- Modes (`seq`, `row`, `col`, `grid`, `pix`)
- Filters (e.g., `gaussian_blur_3x3`)
- Image sizes
- Thread counts

---

### Macrobenchmark (Pipeline)
Run full pipeline benchmarks:

```bash
./gradlew run --args='bench src/main/resources/img out/benchmarks --modes=row,seq,pix,grid,col --filters=all --tag=try1 --repeats=1 --cleanup'
```

Results are saved to:

```
out/benchmarks/
```

Each CSV contains throughput and timing results for all strategies across multiple runs.

---

## Project Structure
```
src/main/kotlin/
Main.kt # Entry point
Sequential.kt # Sequential convolution
RowParallel.kt # Row-parallel convolution
ColParallel.kt # Column-parallel convolution
GridParallel.kt # Grid-based parallel convolution
PixelParallel.kt # Pixel-level parallel convolution
Pipeline.kt # Batch processing pipeline
Filters.kt # Filter definitions
Logger.kt # Timing logger
```

---

## Requirements
- **JDK 8+**
- **Gradle** (wrapper included)
- **OpenCV** (downloaded automatically via `nu.pattern.OpenCV`)

---

## License
MIT
98 changes: 94 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
plugins {
kotlin("jvm") version "2.2.0"
id("org.jetbrains.kotlinx.kover") version "0.8.3"
application
id("me.champeau.jmh") version "0.7.3"
}

group = "org.example"
Expand All @@ -9,19 +12,106 @@ repositories {
mavenCentral()
}

tasks.withType<Test> {
jvmArgs("--enable-native-access=ALL-UNNAMED")
}
tasks.withType<JavaExec> {
jvmArgs("--enable-native-access=ALL-UNNAMED")
}

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.openpnp:opencv:4.7.0-0")

testImplementation("org.jetbrains.kotlin:kotlin-test")

jmh("org.openjdk.jmh:jmh-core:1.37")
jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37")
jmh("org.openpnp:opencv:4.7.0-0")
jmh("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")

}

application {
mainClass.set("Benchmark.BenchMainKt")
}

jmh {
warmupIterations.set(2)
iterations.set(5)
fork.set(1)
resultFormat.set("CSV")
resultsFile.set(layout.buildDirectory.file("bench/jmh.csv").get().asFile)
includes.set(listOf("bench\\.ConvBench"))
}

tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
showStandardStreams = true
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
doFirst {
layout.buildDirectory.dir("tmp/test").get().asFile.mkdirs()
}
}


kotlin {
sourceSets.main {
kotlin.srcDirs("src/main/kotlin")
}
jvmToolchain(21)
}
}
kotlin {
sourceSets.main {
kotlin.srcDirs("src/main/kotlin")

java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } }

abstract class JmhQuickTask : DefaultTask() {

@get:javax.inject.Inject
abstract val execOps: ExecOperations

@org.gradle.api.tasks.TaskAction
fun run() {
val libsDir = project.layout.buildDirectory.dir("libs").get().asFile
val jmhJar = libsDir.listFiles { _, name -> name.endsWith("-jmh.jar") }
?.firstOrNull() ?: throw GradleException("JMH jar не найден в $libsDir")

val outCsv = project.layout.buildDirectory.file("bench/jmh_quick.csv").get().asFile
outCsv.parentFile.mkdirs()

val javaHome = System.getenv("JAVA_HOME")
val javaBin = if (javaHome.isNullOrBlank()) "java" else "$javaHome/bin/java"

execOps.exec {
commandLine(
javaBin, "-jar", jmhJar.absolutePath,
// быстрые окна
"-wi","1","-i","2","-f","0",
"-w","200ms","-r","200ms",
"-tu","ms","-bm","avgt",
"-p","mode=seq",
"-p","mode=row",
"-p","mode=col",
"-p","mode=grid",
"-p","mode=pix",
"-p","size=256",
"-p","filterName=gaussian_blur_3x3",
"-p","blockSize=64",
"-p","blockSize=128",
"-p","blockSize=256",
"-p","xWorkers=1",
"-p","xWorkers=2",
"-p","xWorkers=4",
"-p","xWorkers=8",
"-rf","csv","-rff", outCsv.absolutePath
)
}
println("JMH quick CSV -> ${outCsv.absolutePath}")
}
jvmToolchain(23)
}

tasks.register("jmhQuick", JmhQuickTask::class.java) {
dependsOn("jmhJar")
}
92 changes: 92 additions & 0 deletions src/jmh/java/bench/ConvBench.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package bench;
import conv.RowParallel;
import conv.ColParallel;
import conv.GridParallel;
import conv.PixelParallel;
import conv.Sequential;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.opencv.core.*;
import java.util.*;
import kotlin.Pair;
import filters.Filter;
import static filters.FilterExtKt.toCvKernel;
import static filters.FiltersKt.getFilterList;



@State(Scope.Benchmark)
public class ConvBench {
@Param({"row","grid","seq","pix","col"})
public String mode;


@Param({"1024"})
public int size; // image is size x size


@Param({"gaussian_blur_3x3"})
public String filterName;

@Param({"64","128","256"})
public int blockSize;

@Param({"1","2","4","8"})
public int xWorkers;

private Mat gray;
private Mat kernel;

private static void loadOpenCv() {
try {
Class<?> c = Class.forName("nu.pattern.OpenCV");
c.getMethod("loadLocally").invoke(null);
} catch (Throwable t) {
System.loadLibrary(org.opencv.core.Core.NATIVE_LIBRARY_NAME);
}
}
@Setup(Level.Trial)
public void setup() {
loadOpenCv();
gray = new Mat(size, size, CvType.CV_8UC1);
byte[] buf = new byte[size * size];
new Random(42).nextBytes(buf);
// put data row-by-row
int idx = 0;
for (int r = 0; r < size; r++) {
gray.put(r, 0, Arrays.copyOfRange(buf, idx, idx + size));
idx += size;
}
// pick kernel from your filters list
for (Pair<String, Filter> p : getFilterList()) {
if (p.getFirst().equals(filterName)) {
kernel = toCvKernel(p.getSecond());
break;
}
}
if (kernel == null) kernel = toCvKernel(getFilterList().get(0).getSecond());
}


@TearDown(Level.Trial)
public void tearDown() {
if (gray != null) gray.release();
if (kernel != null) kernel.release();
}


@Benchmark
public void bench(Blackhole bh) {
Mat res;
switch (mode) {
case "row": res = RowParallel.INSTANCE.apply(gray, kernel); break;
case "col": res = ColParallel.INSTANCE.apply(gray, kernel); break;
case "grid": res = GridParallel.INSTANCE.apply(gray, kernel, blockSize, xWorkers); break;
case "pix": res = PixelParallel.INSTANCE.apply(gray, kernel); break;
case "seq": res = Sequential.INSTANCE.apply(gray, kernel); break;
default: res = RowParallel.INSTANCE.apply(gray, kernel);
}
bh.consume(res);
res.release();
}
}
Loading