diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7326964 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: Build master + +on: + pull_request: + branches: + - 'master' + push: + branches: + - "master" + - "ci" + +env: + OPAMROOT: /home/opam/.opam + OPAMYES: true + OPAMCONFIRMLEVEL: unsafe-yes + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/shimat/opencvsharp/ubuntu22-dotnet6sdk-opencv4.7.0:20230114 + + steps: + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: look_around + run: ls + + - name: build + run: dotnet build + + - name: run_tests + run: | + cd ./Tests + dotnet run diff --git a/Bench/Bench.fs b/Bench/Bench.fs new file mode 100644 index 0000000..1f11afa --- /dev/null +++ b/Bench/Bench.fs @@ -0,0 +1,197 @@ +open Conv +open System.IO +open BenchmarkDotNet.Attributes +open BenchmarkDotNet.Running +open OpenCvSharp +open System.Runtime.CompilerServices +open System.Runtime.InteropServices +open ReadConvWrite + + +let kernel = new Mat([| 3; 3 |], MatType.CV_32FC1, Scalar -1.) +~~~ (ind_fl kernel).set_Item 1 1 @@ float32 8 + + +[] +type PartitionConvComparision() = + + [] + member val file = "" with get, set + + member self.getPath([] path: string) = + Path.GetDirectoryName path + + member self.Src = + let path = Path.Combine [| self.getPath (); self.file |] + let src = new Mat(path, ImreadModes.Grayscale) + src + + member self.Dest = new Mat(self.Src.Height, self.Src.Width, self.Src.Type()) + + + [] + member self.Seql() = conv self.Src self.Dest kernel Seql + + [] + member self.ByPixel() = conv self.Src self.Dest kernel ByPixel + + [] + member self.ByRow() = conv self.Src self.Dest kernel ByRow + + [] + member self.ByColumn() = conv self.Src self.Dest kernel ByColumn + + [] + member self.ByRect1x1() = + conv self.Src self.Dest kernel @@ ByRect(Limited 1, Limited 1) + + [] + member self.ByRect1ker() = + conv self.Src self.Dest kernel + @@ ByRect(Limited kernel.Width, Limited kernel.Height) + + [] + member self.ByRect5ker() = + conv self.Src self.Dest kernel + @@ ByRect(Limited @@ ( * ) 5 kernel.Width, Limited @@ ( * ) 5 kernel.Height) + + [] + member self.ByRect10ker() = + conv self.Src self.Dest kernel + @@ ByRect(Limited @@ ( * ) 10 kernel.Width, Limited @@ ( * ) 10 kernel.Height) + + [] + member self.ByRect15ker() = + conv self.Src self.Dest kernel + @@ ByRect(Limited @@ ( * ) 15 kernel.Width, Limited @@ ( * ) 15 kernel.Height) + + + [] + member self.By1KerHeightRows() = + conv self.Src self.Dest kernel @@ ByRect(UnLimited, Limited kernel.Height) + + [] + member self.By3KerHeightRows() = + conv self.Src self.Dest kernel + @@ ByRect(UnLimited, Limited @@ ( * ) 3 kernel.Height) + + [] + member self.By10KerHeightRows() = + conv self.Src self.Dest kernel + @@ ByRect(UnLimited, Limited @@ ( * ) 10 kernel.Height) + + [] + member self.By1KerWidthColomns() = + conv self.Src self.Dest kernel @@ ByRect(Limited kernel.Width, UnLimited) + + [] + member self.By3KerWidthColomns() = + conv self.Src self.Dest kernel + @@ ByRect(Limited @@ ( * ) 3 kernel.Width, UnLimited) + + [] + member self.By10KerWidthColomns() = + conv self.Src self.Dest kernel + @@ ByRect(Limited @@ ( * ) 10 kernel.Width, UnLimited) + + +let def_cnfg = + { source = ([ "/root/conv/pics" ], SearchOption.TopDirectoryOnly) + outdir = "./Out" + prefix = "Conv" + max_readers = Some 1 + max_writers = None + max_conv_performers = Some 4 + max_queue_size = Some 4 + conv_mode = ByRow } + + +[] +type Pipelining() = + + [] + member self.R1C1parW1Qub() = + full_cycle_paral_conv + { def_cnfg with + max_readers = Some 1 + max_writers = Some 1 + max_conv_performers = Some 1 + max_queue_size = None + conv_mode = ByRow } + kernel + + [] + member self.R1C1parW1Q1() = + full_cycle_paral_conv + { def_cnfg with + max_readers = Some 1 + max_writers = Some 1 + max_conv_performers = Some 1 + max_queue_size = Some 1 + conv_mode = ByRow } + kernel + + + [] + member self.R1C4parW1Q4() = + full_cycle_paral_conv + { def_cnfg with + max_readers = Some 1 + max_writers = Some 1 + max_conv_performers = Some 4 + max_queue_size = Some 4 + conv_mode = ByRow } + kernel + + [] + member self.R1C4parW1Q8() = + full_cycle_paral_conv + { def_cnfg with + max_readers = Some 1 + max_writers = Some 1 + max_conv_performers = Some 4 + max_queue_size = Some 8 + conv_mode = ByRow } + kernel + + [] + member self.R1C4parWubQ4() = + full_cycle_paral_conv + { def_cnfg with + max_readers = Some 1 + max_writers = None + max_conv_performers = Some 4 + max_queue_size = Some 4 + conv_mode = ByRow } + kernel + + [] + member self.R1CubseqlWubQ16() = + full_cycle_paral_conv + { def_cnfg with + max_readers = Some 1 + max_writers = None + max_conv_performers = None + max_queue_size = Some 16 + conv_mode = Seql } + kernel + + [] + member self.R1C2parWubQ2() = + full_cycle_paral_conv + { def_cnfg with + max_readers = Some 1 + max_writers = None + max_conv_performers = Some 2 + max_queue_size = Some 4 + conv_mode = ByRow } + kernel + + [] + member self.Cleanup() = + Directory.Delete (def_cnfg.outdir, recursive = true ) + +[] +let main _ = + BenchmarkRunner.Run typeof |> ignore + 0 diff --git a/Bench/Bench.fsproj b/Bench/Bench.fsproj new file mode 100644 index 0000000..23bf845 --- /dev/null +++ b/Bench/Bench.fsproj @@ -0,0 +1,23 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d6abdbc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Nikita Shchutskii + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Lib/Conv.fs b/Lib/Conv.fs new file mode 100644 index 0000000..c3a6262 --- /dev/null +++ b/Lib/Conv.fs @@ -0,0 +1,99 @@ +module Conv + +open OpenCvSharp +open System.Threading.Tasks + +let (@@) = (<|) + +let curry3 f a1 a2 a3 = f (a1, a2, a3) +let (~~~) = curry3 + +let ind (m: Mat) = m.GetGenericIndexer() +let ind_fl (m: Mat) = m.GetGenericIndexer() + +type rectSideSize = + | UnLimited + | Limited of int + +type paral_method = + | Seql + | ByPixel + | ByRow + | ByColumn + | ByRect of rectSideSize * rectSideSize + +let conv src dest kernel paral_method = + + let src_indr, dest_indr, kernel_indr = ind src, ind dest, ind_fl kernel + + let calc1 x y = + byte + @@ min (float32 255) + @@ max (float32 0) + @@ Seq.fold (+) (float32 0) + @@ seq { + for ky in 0 .. kernel.Height - 1 do + for kx in 0 .. kernel.Width - 1 -> + let m, n = (kernel.Height / 2) - ky, (kernel.Width / 2) - kx + let (-) x y upper = max 0 @@ min (upper - 1) @@ (-) x y + + (float32 @@ src_indr.get_Item ((-) y m src.Height, (-) x n src.Width)) + * (kernel_indr.get_Item (ky, kx)) + } + + match paral_method with + | Seql -> + for y in 0 .. src.Height - 1 do + for x in 0 .. src.Width - 1 do + ~~~ dest_indr.set_Item y x @@ calc1 x y + | ByPixel -> + for y in 0 .. src.Height - 1 do + Parallel.For( + 0, + src.Width, + fun x -> + + ~~~ dest_indr.set_Item y x @@ calc1 x y + + ) + |> ignore + | ByRow -> + Parallel.For( + 0, + src.Height, + fun y -> + for x in 0 .. src.Width - 1 do + ~~~ dest_indr.set_Item y x @@ calc1 x y + ) + |> ignore + | ByColumn -> + Parallel.For( + 0, + src.Width, + fun x -> + for y in 0 .. src.Height - 1 do + ~~~ dest_indr.set_Item y x @@ calc1 x y + ) + |> ignore + | ByRect(xS, yS) -> + + let xS, yS = + (fun f -> f src.Height xS, f src.Width yS) + @@ fun def -> + function + | Limited n when n > 0 && n < def -> n + | UnLimited -> def + | Limited n when n > 0 -> def + | _ -> failwith "side size shoudn't be less then 1 pixel" + + Parallel.ForEach( + seq { + for starty in 0..yS .. src.Height - 1 do + for startx in 0..xS .. src.Width - 1 -> (starty, startx) + }, + fun (starty, startx) -> + for y in starty .. min (src.Height - 1) (starty + yS) do + for x in startx .. min (src.Width - 1) (startx + xS) do + ~~~ dest_indr.set_Item y x @@ calc1 x y + ) + |> ignore diff --git a/Lib/Kernels.fs b/Lib/Kernels.fs new file mode 100644 index 0000000..1d78d00 --- /dev/null +++ b/Lib/Kernels.fs @@ -0,0 +1,104 @@ +module Kernels + +let (@@) = (<|) + +type kernel = + | Id + | Box3x3 + | Box5x5 + | Box7x7 + | Gaussian + | DoG // Difference of Gaussians + | Lapl8Neigh //Laplacian with 8 (eq-t) neighbours + | Lapl4Neigh + | Lapl5x5 + | Lapl5x5LoG //Laplacian Of Gaussians + | SobelN // Directional: from North to South + | SobelS + | SobelW + | SobelE + +let to_2d_array: kernel -> float32[,] = + + let distance_based_3x3 z o t = + Array2D.init 3 3 + @@ fun y x -> + float32 + @@ let dx, dy = abs (1 - x), abs (1 - y) in + + match dx + dy with + | 0 -> z + | 1 -> o + | 2 -> t + + let distance_based_5x5 z o t_eq t th f = + Array2D.init 5 5 + @@ fun y x -> + float32 + @@ let dx, dy = abs (2 - x), abs (2 - y) in + + match dx + dy with + | 0 -> z + | 1 -> o + | 2 when dx = dy -> t_eq + | 2 -> t + | 3 -> th + | 4 -> f + + function + | Id -> + let arr = Array2D.create 3 3 @@ float32 0 + arr[1, 1] <- float32 1 + arr + | Box3x3 -> Array2D.create 3 3 @@ float32 (1. / 9.) + | Box5x5 -> Array2D.create 5 5 @@ float32 (1. / 25.) + | Box7x7 -> Array2D.create 7 7 @@ float32 (1. / 49.) + | Gaussian -> distance_based_5x5 0.150342 0.094907 0.059912 0.023792 0.015019 0.003765 + | DoG -> distance_based_3x3 1. -0.155615 -0.0943852 + | Lapl8Neigh -> + let arr = Array2D.create 3 3 @@ float32 -1 + arr[1, 1] <- float32 8 + arr + | Lapl4Neigh -> distance_based_3x3 4. -1. 0. + | Lapl5x5 -> distance_based_5x5 4. 3. 2. 0. -1. -4. + | Lapl5x5LoG -> distance_based_5x5 16. -2. -1. -1. 0. 0. + | SobelN -> + Array2D.init 3 3 + @@ fun y x -> + float32 + @@ match y, x with + | 1, _ -> 0 + | 0, 1 -> 2 + | 2, 1 -> -2 + | 0, _ -> 1 + | 2, _ -> -1 + | SobelS -> + Array2D.init 3 3 + @@ fun y x -> + float32 + @@ match y, x with + | 1, _ -> 0 + | 0, 1 -> -2 + | 2, 1 -> 2 + | 0, _ -> -1 + | 2, _ -> 1 + | SobelW -> + Array2D.init 3 3 + @@ fun y x -> + float32 + @@ match x, y with + | 1, _ -> 0 + | 0, 1 -> 2 + | 2, 1 -> -2 + | 0, _ -> 1 + | 2, _ -> -1 + | SobelE -> + Array2D.init 3 3 + @@ fun y x -> + float32 + @@ match x, y with + | 1, _ -> 0 + | 0, 1 -> -2 + | 2, 1 -> 2 + | 0, _ -> -1 + | 2, _ -> 1 diff --git a/Lib/Lib.fsproj b/Lib/Lib.fsproj new file mode 100644 index 0000000..7ad0173 --- /dev/null +++ b/Lib/Lib.fsproj @@ -0,0 +1,20 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Lib/ReadConvWrite.fs b/Lib/ReadConvWrite.fs new file mode 100644 index 0000000..386dd0c --- /dev/null +++ b/Lib/ReadConvWrite.fs @@ -0,0 +1,93 @@ +open System.IO +open OpenCvSharp +open Conv +open System.Threading.Tasks.Dataflow + +type source = string list * SearchOption + + +type cnfg = + { source: source + outdir: string + prefix: string + max_readers: int option + max_writers: int option + max_conv_performers: int option + max_queue_size: int option + conv_mode: paral_method } + +let (~~~) = curry3 + + +let load name = + let src = new Mat(name, ImreadModes.Grayscale) + (src, name) + +let applyFilter conv_mode kernel (m: Mat, name) = + let dest = new Mat(m.Height, m.Width, m.Type()) + conv m dest kernel conv_mode + m.Dispose() + (dest, name) + +let save outdir prefix (m: Mat, name: string) = + Cv2.ImWrite(sprintf "./%s/%s-%s" outdir prefix @@ Path.GetFileName name, m) + |> ignore + + m.Dispose() + +let full_cycle_paral_conv cnfg kernel = + + let opts bounded_queue p_deg = + ExecutionDataflowBlockOptions( + MaxDegreeOfParallelism = Option.defaultValue DataflowBlockOptions.Unbounded p_deg, + BoundedCapacity = + match cnfg.max_queue_size with + | Some n when bounded_queue -> n + | _ -> DataflowBlockOptions.Unbounded + ) + + let link_opts = DataflowLinkOptions(PropagateCompletion = true) + + + let loadBlock = + TransformBlock( + load, + opts true cnfg.max_readers + + ) + + let processBlock = + TransformBlock( + applyFilter cnfg.conv_mode kernel, + opts true cnfg.max_conv_performers + ) + + let saveBlock = + ActionBlock(save cnfg.outdir cnfg.prefix, opts true cnfg.max_writers) + + let waitingBlock = BufferBlock() + + use _ = waitingBlock.LinkTo(loadBlock, link_opts) + use _ = loadBlock.LinkTo(processBlock, link_opts) + use _ = processBlock.LinkTo(saveBlock, link_opts) + Directory.CreateDirectory cnfg.outdir |> ignore + + let pathes, opts = cnfg.source + + List.iter (fun name -> waitingBlock.Post name |> ignore) + @@ List.fold + (fun files path -> + if Directory.Exists path then + List.append (List.ofArray @@ Directory.GetFiles(path, "*.jpg", opts)) files + elif File.Exists path then + path :: files + else + eprintfn "file %s wasn't found!" path + files) + [] + pathes + + waitingBlock.Complete() + + + saveBlock.Completion.Wait() diff --git a/Program.fs b/Program.fs new file mode 100644 index 0000000..94d7eb7 --- /dev/null +++ b/Program.fs @@ -0,0 +1,116 @@ +open System.IO +open OpenCvSharp +open Conv +open ReadConvWrite +open Argu +open System + + +type conv_mode = + | Seql + | ByPixel + | ByRow + | ByColumn + | ByRect + +type CliArgument = + | [] Source of pathes: string list + | [] Recursive + | [] Kernel of Kernels.kernel + | [] Out_Dir of path: string + | [] Prefix of str: string + | [] Conv_Mode of conv_mode + | [] Rect_Height of num: int option + | [] Rect_Width of num: int option + | Max_Readers of num: int + | Max_Conv_Workers of num: int + | Max_Writers of num: int + | Max_Queue_size of num: int + + interface IArgParserTemplate with + member s.Usage = + match s with + | Source _ -> "specify (list of) image/directory absolute pathes (Default: ./ )" + | Kernel _ -> "specify kernel to be applied (Default: id)" + | Recursive -> "search in nested directories too" + | Out_Dir _ -> "specify directory for output images (Default: ./Out )" + | Prefix _ -> "specify prefix for output image names (Default: Conv)" + | Conv_Mode _ -> "specify the rule for (inter-threading) data partition (Default: ByRow)" + | Rect_Height _ -> "specify rectangle height for ByRect partition (Default: 9). -1 stands for image height" + | Rect_Width _ -> "specify rectangle width for ByRect partition (Default: 9). -1 stands for image width" + | Max_Readers _ -> + "specify upper bound for image reading threads number (Default: 1). -1 stands for unbound" + | Max_Conv_Workers _ -> + "specify upper bound for image conv. threads number (Default: 4). -1 stands for unbound" + | Max_Writers _ -> + "specify upper bound for output image saving threads number (Default: -1). -1 stands for unbound" + | Max_Queue_size _ -> "specify upper bound for images in queue (Default: 4). -1 stands for unbound" + + +[] +let main args = + let parser = + ArgumentParser.Create( + errorHandler = + ProcessExiter( + colorizer = + function + | ErrorCode.HelpText -> None + | _ -> Some ConsoleColor.Red + ) + ) + + let args = parser.Parse args + + + let int_hndl def = + function + | n when n >= 0 -> Some n + | -1 -> None + | _ -> Some def + + let rec_flag_hndl = + function + | true -> SearchOption.AllDirectories + | false -> SearchOption.TopDirectoryOnly + + + let mode_hndl def mode rect_width rect_height = + match mode with + | ByRect -> + let size_hndl = + function + | Some -1 -> UnLimited + | Some n when n >= 0 -> Limited n + | _ -> Limited def + + paral_method.ByRect(size_hndl rect_width, size_hndl rect_height) + + | Seql -> paral_method.Seql + | ByPixel -> paral_method.ByPixel + | ByRow -> paral_method.ByRow + | ByColumn -> paral_method.ByColumn + + + let cnfg = + { source = args.GetResult(Source, defaultValue = [ "./" ]), args.Contains Recursive |> rec_flag_hndl + outdir = args.GetResult(Out_Dir, defaultValue = "./Out") + prefix = args.GetResult(Prefix, defaultValue = "Conv") + max_readers = int_hndl 1 @@ args.GetResult(Max_Readers, defaultValue = 1) + max_conv_performers = int_hndl 4 @@ args.GetResult(Max_Conv_Workers, defaultValue = 4) + max_writers = int_hndl (-1) @@ args.GetResult(Max_Writers, defaultValue = -1) + max_queue_size = int_hndl 4 @@ args.GetResult(Max_Queue_size, defaultValue = 4) + conv_mode = + mode_hndl + 9 + (args.GetResult(Conv_Mode, defaultValue = ByRow)) + (args.GetResult(Rect_Width, defaultValue = None)) + (args.GetResult(Rect_Height, defaultValue = None)) } + + + let kernel = + Mat.FromArray(Kernels.to_2d_array @@ args.GetResult(Kernel, defaultValue = Kernels.Id)) + + full_cycle_paral_conv cnfg kernel + kernel.Dispose() + 0 diff --git a/README.md b/README.md index 8a0f426..61c1016 100644 --- a/README.md +++ b/README.md @@ -1 +1,123 @@ -# conv \ No newline at end of file +# The tool for image convolution +## Usage: +Clone repo: + ```bash + $ git clone https://github.com/ns-58/conv + $ cd conv + ``` +Build app: + ```bash + $ dotnet publish -c release + $ ln -s ./bin/release/net6.0/publish/conv conv + ``` +Use --help for app usage guide: + ```bash + $ ./conv --help +USAGE: conv [--help] [--source [...]] [--recursive] + [--kernel ] + [--out-dir ] [--prefix ] [--conv-mode ] + [--rect-height []] [--rect-width []] [--max-readers ] [--max-conv-workers ] + [--max-writers ] [--max-queue-size ] + +OPTIONS: + + --source, -s [...] + specify (list of) image/directory pathes (Default: ./ ) + --recursive, -r search in nested directories too + --kernel, -k + specify kernel to be applied (Default: id) + --out-dir, -o specify directory for output images (Default: ./Out ) + --prefix, -p specify prefix for output image names (Default: Conv) + --conv-mode, -m + specify the rule for (inter-threading) data partition (Default: ByRow) + --rect-height, -rh [] + specify rectangle height for ByRect partition (Default: 9). -1 stands for image + height + --rect-width, -rw [] + specify rectangle width for ByRect partition (Default: 9). -1 stands for image + width + --max-readers specify upper bound for image reading threads number (Default: 1). -1 stands for + unbound + --max-conv-workers + specify upper bound for image conv. threads number (Default: 4). -1 stands for + unbound + --max-writers specify upper bound for output image saving threads number (Default: -1). -1 + stands for unbound + --max-queue-size + specify upper bound for images in queue (Default: 4). -1 stands for unbound + --help display this list of options. + ``` +## Additional requirements: + opencv4.7+ + consider use of container https://github.com/users/shimat/packages/container/package/opencvsharp%2Fubuntu22-dotnet6sdk-opencv4.7.0 + +## Benchmarking +BenchmarkDotNet v0.13.8, Ubuntu 22.04.1 LTS (Jammy Jellyfish) (container) +11th Gen Intel Core i7-11850H 2.50GHz, 1 CPU, 16 logical and 8 physical cores +.NET SDK 6.0.405 + [Host] : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2 DEBUG + DefaultJob : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2 +### Image partition +sizes: +small.jpg --- 750x750 (108KB) + big.jpg --- 11256x4877 (59MB) + +| Method | file | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|-------------------- |------------- |-----------:|---------:|---------:|-------------:|------------:|----------:|-----------:| +| Seql | ../small.jpg | 670.9 ms | 1.14 ms | 1.01 ms | 53000.0000 | - | - | 643.78 MB | +| ByPixel | ../small.jpg | 360.1 ms | 3.03 ms | 2.83 ms | 54000.0000 | 1500.0000 | - | 646.74 MB | +| ByRow | ../small.jpg | 273.4 ms | 3.12 ms | 2.43 ms | 53500.0000 | 1000.0000 | - | 643.8 MB | +| ByColumn | ../small.jpg | 286.2 ms | 3.31 ms | 3.09 ms | 53500.0000 | 1000.0000 | - | 643.8 MB | +| ByRect1x1 | ../small.jpg | 1,192.1 ms | 23.46 ms | 45.75 ms | 216000.0000 | 24000.0000 | 1000.0000 | 2580.61 MB | +| ByRect1ker | ../small.jpg | 482.6 ms | 5.95 ms | 5.28 ms | 95000.0000 | 16000.0000 | - | 1144.73 MB | +| ByRect5ker | ../small.jpg | 324.7 ms | 6.28 ms | 6.45 ms | 61000.0000 | 2000.0000 | - | 731.14 MB | +| ByRect10ker | ../small.jpg | 338.2 ms | 8.71 ms | 25.69 ms | 57500.0000 | 1500.0000 | - | 686.81 MB | +| ByRect15ker | ../small.jpg | 300.8 ms | 3.49 ms | 3.27 ms | 56000.0000 | 1000.0000 | - | 672.3 MB | +| By1KerHeightRows | ../small.jpg | 378.0 ms | 4.79 ms | 4.00 ms | 71000.0000 | 1000.0000 | - | 858.11 MB | +| By3KerHeightRows | ../small.jpg | 314.2 ms | 6.08 ms | 5.39 ms | 59000.0000 | 1000.0000 | - | 715.24 MB | +| By10KerHeightRows | ../small.jpg | 297.4 ms | 5.77 ms | 6.17 ms | 55500.0000 | 1000.0000 | - | 664.96 MB | +| By1KerWidthColomns | ../small.jpg | 394.5 ms | 7.42 ms | 6.94 ms | 71000.0000 | 1000.0000 | - | 858.09 MB | +| By3KerWidthColomns | ../small.jpg | 310.6 ms | 4.66 ms | 4.13 ms | 59000.0000 | 1000.0000 | - | 715.24 MB | +| By10KerWidthColomns | ../small.jpg | 296.1 ms | 5.83 ms | 6.48 ms | 55500.0000 | 1000.0000 | - | 664.95 MB | +| - |- |- |- |- |- |- |- |- | +| ByRow | ../big.jpg | 39.11 s | 0.323 s | 0.302 s | 7229000.0000 | 147000.0000 | - | 84.26 GB | +| ByColumn | ../big.jpg | 37.33 s | 0.166 s | 0.155 s | 7227000.0000 | 139000.0000 | - | 84.26 GB | +| ByRect15ker | ../big.jpg | 67.81 s | 1.350 s | 3.556 s | 7564000.0000 | 272000.0000 | 9000.0000 | 88.04 GB | +| By10KerHeightRows | ../big.jpg | 38.16 s | 0.295 s | 0.246 s | 7471000.0000 | 218000.0000 | 1000.0000 | 87.07 GB | +| By10KerWidthColomns | ../big.jpg | 37.44 s | 0.179 s | 0.150 s | 7468000.0000 | 142000.0000 | - | 87.07 GB | + + +// * Legends * + file : Value of the 'file' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Gen0 : GC Generation 0 collects per 1000 operations + Gen1 : GC Generation 1 collects per 1000 operations + Gen2 : GC Generation 2 collects per 1000 operations + Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) + +Итог: благодаря современным компиляторам/вычислителям способ разбиения изображения (по строчкам/столбцам/прямоугольникам) не оказывает существенного влияния на производительность свертки; на первую роль выходят размер разбиения и сложность опредения границ его участков + +### Pipelining + +input: 99 650x650 images + +| Method | Mean | Error | StdDev | Median | +|---------------- |--------:|--------:|--------:|--------:| +| R1C1parW1Qub | 35.75 s | 0.563 s | 1.199 s | 35.55 s | +| R1C1parW1Q1 | 35.74 s | 0.240 s | 0.225 s | 35.77 s | +| R1C4parW1Q4 | 48.96 s | 0.954 s | 1.240 s | 48.72 s | +| R1C4parW1Q8 | 50.66 s | 0.750 s | 0.702 s | 50.55 s | +| R1C4parWubQ4 | 51.31 s | 1.019 s | 2.279 s | 52.50 s | +| R1CubseqlWubQ16 | 27.83 s | 0.187 s | 0.175 s | 27.91 s | +| R1C2parWubQ2 | 40.61 s | 0.439 s | 0.411 s | 40.66 s | + +where Rn --- n readers, Cn\ --- n convolutions using partition mode \, Wn --- n writers, Qn --- queue with size n, ub --- unbound + +Наблюдения и итоги: + одно чтение и одна запись способны "обслуживать" несколько сверток; + параллелить (внутри) несколько одновременных сверток неэфективно (из-за синхронизации на двух уровнях?); + наиболее эффективны вариант с одной параллельной сверткой и вариант с большим кол-вом последовательных, он быстрее всех (синхронизация внутри свертки нужна чаще); + + diff --git a/Tests/PropBased.fs b/Tests/PropBased.fs new file mode 100644 index 0000000..0378522 --- /dev/null +++ b/Tests/PropBased.fs @@ -0,0 +1,93 @@ +open OpenCvSharp +open FsCheck.FSharp +open Conv + +let (@@) = (<|) + +// let print_byte_matrix m = +// let indr = ind m +// for y in 0 .. m.Height - 1 do +// for x in 0 .. m.Width do +// printf "%d " @@ indr.get_Item(y,x) +// printfn "" +// printfn "" + + +let eq_to_opencv (m: Mat) (kernel: Mat) mode = + use dest1 = new Mat(m.Height, m.Width, m.Type()) + use dest2 = new Mat(m.Height, m.Width, m.Type()) + + conv m dest1 kernel mode + Cv2.Filter2D(m, dest2, -1, kernel, borderType = BorderTypes.Replicate) + + let indr1, indr2 = ind dest1, ind dest2 + // print_byte_matrix dest1 + // print_byte_matrix dest2 + + Seq.fold (&&) true + @@ seq { + for y in 0 .. dest1.Height - 1 do + for x in 0 .. dest1.Width - 1 -> + (int @@ indr1.get_Item (y, x)) >= (int @@ indr2.get_Item (y, x)) - 1 + && (int @@ indr1.get_Item (y, x)) <= (int @@ indr2.get_Item (y, x)) + 1 + } + + +let byte_mat_gen = + Gen.sized (fun s -> + let [| rows; colomns |] = Gen.sampleWithSize s 2 @@ Gen.choose (10, s + 1) + + Gen.map (fun (data: byte[,]) -> Mat.FromArray(data)) + @@ Gen.array2DOfDim rows colomns + @@ Gen.map byte + @@ Gen.choose (0, 255)) + +let gen_float = + Gen.map (fun (m, n) -> float32 m / float32 n) + @@ Gen.two + @@ Gen.choose (System.Int32.MinValue / 3, System.Int32.MaxValue) + + +let float_mat_gen = + Gen.sized (fun s -> + let s = int @@ sqrt @@ float s + let [| rows; colomns |] = Gen.sampleWithSize s 2 @@ Gen.choose (1, s + 1) + + Gen.map (fun (data: float32[,]) -> Mat.FromArray(data)) + @@ Gen.array2DOfDim rows colomns gen_float) + +type MyGenerators = + static member ByteMap() = + { new FsCheck.Arbitrary>() with + override _.Generator = byte_mat_gen + override _.Shrinker _ = Seq.empty } + + static member FloatMap() = + { new FsCheck.Arbitrary>() with + override _.Generator = float_mat_gen + override _.Shrinker _ = Seq.empty } + +for mode in [ Seql; ByPixel; ByRow; ByColumn; ByRect(UnLimited, UnLimited) ] do + FsCheck.Check.One( + FsCheck.Config.Quick.WithArbitrary([ typeof ]), + fun (m: Mat) (k: Mat) -> eq_to_opencv m k mode + ) + +FsCheck.Check.One( + FsCheck.Config.Quick.WithArbitrary([ typeof ]), + fun (m: Mat) (k: Mat) (xS: uint16) -> + eq_to_opencv m k @@ ByRect(Limited @@ max 1 @@ int xS, UnLimited) +) + +FsCheck.Check.One( + FsCheck.Config.Quick.WithArbitrary([ typeof ]), + fun (m: Mat) (k: Mat) (xS: uint16) -> + eq_to_opencv m k @@ ByRect(Limited @@ max 1 @@ int xS, UnLimited) +) + +FsCheck.Check.One( + FsCheck.Config.Quick.WithArbitrary([ typeof ]), + fun (m: Mat) (k: Mat) (xS: uint16) (yS: uint16) -> + eq_to_opencv m k + @@ ByRect(Limited @@ max 1 @@ int xS, Limited @@ max 1 @@ int yS) +) diff --git a/Tests/Tests.fsproj b/Tests/Tests.fsproj new file mode 100644 index 0000000..8b7bfbf --- /dev/null +++ b/Tests/Tests.fsproj @@ -0,0 +1,22 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + + + + diff --git a/conv.fsproj b/conv.fsproj new file mode 100644 index 0000000..6743bd9 --- /dev/null +++ b/conv.fsproj @@ -0,0 +1,22 @@ + + + + Exe + net6.0 + + + + + + + + + + + + + + + + +