diff --git a/.gitignore b/.gitignore index 21caefb..5a4b8af 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,6 @@ docs/site/ # It records a fixed state of all packages used by the project. As such, it should not be # committed for packages, but should be committed for applications that require a static # environment. -Manifest.toml \ No newline at end of file +Manifest.toml + +.vscode \ No newline at end of file diff --git a/Project.toml b/Project.toml index 3a31a2f..e32cc27 100644 --- a/Project.toml +++ b/Project.toml @@ -5,8 +5,10 @@ version = "4.6.0" [deps] CxxWrap = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" +FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" OpenCV_jll = "33b9d88c-85f9-5d73-bd91-4e2b95a9aa0b" [compat] +FileIO = "1.16" CxxWrap = "0.16" -julia = "1.10" +julia = "1.10" \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index 4e6d994..8b32e20 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,3 +1,4 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -OpenCV = "f878e3a2-a245-4720-8660-60795d644f2a" \ No newline at end of file +FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +OpenCV = "f878e3a2-a245-4720-8660-60795d644f2a" diff --git a/docs/make.jl b/docs/make.jl index f041dd4..79d7d6c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,6 +7,10 @@ DocMeta.setdocmeta!(OpenCV, :DocTestSetup, :(using OpenCV); recursive=true) makedocs(; modules=[OpenCV], sitename="OpenCV.jl", + pages = [ + "OpenCV.jl" => "index.md" + "Getting started with Images" => "Getting started with Images.md" + ] ) deploydocs(; diff --git a/docs/src/Getting started with Images.md b/docs/src/Getting started with Images.md new file mode 100644 index 0000000..db10e90 --- /dev/null +++ b/docs/src/Getting started with Images.md @@ -0,0 +1,106 @@ +# Reading and Writing of Images + +OpenCV provides a wide range of functions for image processing, including reading and writing images. + +OpenCV.jl provides 2 ways to read and write images +1. OpenCV native api +2. FileIO.jl api + +OpenCV provides `imread` and `imwrite`, while FileIO privides `load` and `save` for similar functionalities + + +!!! note + + FileIO api not suppport all image formats currently but it support major formats. + If FileIO api not works then use OpenCV native api + +## Reading Images + +```julia +using OpenCV + +img_path = "/path/to/image" +img = OpenCV.imread(img_path) +``` +with FileIO +```julia +using OpenCV, FileIO + +img_path = "/path/to/image" +img = load(img_path) +``` + +in both cases `img` has same data. Both methods accept same reading flags[^imreadflags]. + +```julia +using OpenCV + +img_path = "/path/to/image" +flag = OpenCV.IMREAD_UNCHANGED +img = OpenCV.imread(img_path, flag) +``` +with FileIO +```julia +using OpenCV, FileIO + +img_path = "/path/to/image" +flag = OpenCV.IMREAD_UNCHANGED +img = load(img_path, flag) +``` + +## Writing Images +```julia +using OpenCV + +img = rand(UInt8, 1000, 1000) |> OpenCV.Mat +img_path = "/path/to/image" +OpenCV.imwrite(img_path, img) +``` +with FileIO +```julia +using OpenCV, FileIO + +img = rand(UInt8, 1000, 1000) |> OpenCV.Mat +img_path = "/path/to/image" +save(img_path, img) +``` + +Both methods accept same writing flags[^imwriteflags]. + +```julia +using OpenCV + +img = rand(UInt8, 1000, 1000) |> OpenCV.Mat +img_path = "/path/to/image" +flag = Int32[OpenCV.IMWRITE_JPEG_QUALITY, 100] +OpenCV.imwrite(img_path, img, flag) +``` +with FileIO +```julia +using OpenCV, FileIO + +img = rand(UInt8, 1000, 1000) |> OpenCV.Mat +img_path = "/path/to/image" +flag = Int32[OpenCV.IMWRITE_JPEG_QUALITY, 100] +save(img_path, img, flag) +``` + +## Displaying Images + +When working with images, it's obviously helpful to be able to look at them. If you use Julia through Pluto, VSCode, or IJulia, images should display automatically. + +Preview from Pluto.jl + +![pluto preview](assets/pluto.png) + +`OpenCV.imshow` method display image on Qt window. + +![qt preview](assets/qt.png) + +`Plots.jl` To-do + +`Makie.jl` To-do + +## Notes +[^imreadflags]: https://docs.opencv.org/4.x/d8/d6a/group__imgcodecs__flags.html#ga61d9b0126a3e57d9277ac48327799c80 +[^imwriteflags]: https://docs.opencv.org/4.x/d8/d6a/group__imgcodecs__flags.html#ga292d81be8d76901bff7988d18d2b42ac \ No newline at end of file diff --git a/docs/src/assets/pluto.png b/docs/src/assets/pluto.png new file mode 100644 index 0000000..28acaf1 Binary files /dev/null and b/docs/src/assets/pluto.png differ diff --git a/docs/src/assets/qt.png b/docs/src/assets/qt.png new file mode 100644 index 0000000..fe6a083 Binary files /dev/null and b/docs/src/assets/qt.png differ diff --git a/src/OpenCV.jl b/src/OpenCV.jl index 825841a..9d9d48e 100644 --- a/src/OpenCV.jl +++ b/src/OpenCV.jl @@ -1,7 +1,12 @@ module OpenCV using OpenCV_jll +using FileIO +using FileIO: DataFormat, File, Stream, stream include(joinpath(OpenCV_jll.artifact_dir, "OpenCV", "src", "OpenCV.jl")) +include("fileio.jl") +include("show.jl") + end diff --git a/src/fileio.jl b/src/fileio.jl new file mode 100644 index 0000000..fb7b218 --- /dev/null +++ b/src/fileio.jl @@ -0,0 +1,58 @@ +const _IMAGE_DATA_FORMATS = Union{ + format"BMP", + format"JP2", + format"JPEG", + format"PNG", + format"TIFF", +} + +function _get_format_extension(fmt) + FileIO.info(fmt)[2] +end + +## Load Images + +function load(f::File{T}) where {T<:_IMAGE_DATA_FORMATS} + data = imread(f.filename) + return data +end + +function load(f::File{T}, flags::Int) where {T<:_IMAGE_DATA_FORMATS} + data = imread(f.filename, flags) + return data +end + +function load(s::Stream{T}) where {T<:_IMAGE_DATA_FORMATS} + data = read(stream(s)) + img = imdecode(reshape(data, 1, 1, :)) + return img +end + +function load(s::Stream{T}, flags::Int) where {T<:_IMAGE_DATA_FORMATS} + data = read(stream(s)) + img = imdecode(reshape(data, 1, 1, :), flags) + return img +end + +## Save Images + +function save(f::File{T}, image::InputArray) where {T<:_IMAGE_DATA_FORMATS} + imwrite(f.filename, image) +end + +function save(f::File{T}, image::InputArray, params::Array{Int32,1}) where {T<:_IMAGE_DATA_FORMATS} + imwrite(f.filename, image, params) +end + +function save(s::Stream{T}, image::InputArray) where {T<:_IMAGE_DATA_FORMATS} + ext = _get_format_extension(T) + enc_img = imencode(ext, image)[2] + Base.write(stream(s), enc_img) +end + +function save(s::Stream{T}, image::InputArray, params::Vector{Int32}) where {T<:_IMAGE_DATA_FORMATS} + ext = _get_format_extension(T) + enc_img = imencode(ext, image, params)[2] + Base.write(stream(s), enc_img) +end + diff --git a/src/show.jl b/src/show.jl new file mode 100644 index 0000000..9c72528 --- /dev/null +++ b/src/show.jl @@ -0,0 +1,5 @@ +_format_stream(format, io) = Stream{format}(io) + +function Base.show(io::IO, ::MIME"image/png", image::Mat{T}) where {T<:dtypes} + save(_format_stream(DataFormat{:PNG}, io), image) +end \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml index a165d83..47b270c 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,6 +1,7 @@ [deps] Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" OpenCV = "f878e3a2-a245-4720-8660-60795d644f2a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index d8286d9..08c1515 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,7 @@ using Artifacts using LazyArtifacts using OpenCV +using FileIO using Test if "OPENCV_TEST_DATA_PATH" in keys(ENV) @@ -16,4 +17,5 @@ end include("test_imgproc.jl") include("test_objdetect.jl") include("test_dnn.jl") + include("test_fileio.jl") end diff --git a/test/test_fileio.jl b/test/test_fileio.jl new file mode 100644 index 0000000..fbd8473 --- /dev/null +++ b/test/test_fileio.jl @@ -0,0 +1,58 @@ +tmpdir = mktempdir() + +images_path = joinpath(opencv_extra_path, "testdata", "python", "images") +images = readdir(images_path, join=true) + +bmp_images = filter(endswith(".bmp"), images) +# jp2_images = filter(endswith(".jp2"), images) # Not available +jpg_images = filter(endswith(".jpg"), images) +png_images = filter(endswith(".png"), images) +tiff_images = filter(endswith(".tiff"), images) + +@testset "BMP" begin + for (idx, img_path) in enumerate(bmp_images) + img1 = load(img_path) + f = joinpath(tmpdir, "img_$idx.bmp") + save(f, img1) + img2 = load(f) + @test img1 == img2 + end +end + +# @testset "JP2" begin +# img1 = rand(UInt8, 3, 1024, 1024) |> OpenCV.Mat +# f = joinpath(tmpdir, "img.jp2") +# save(f, img1) +# img2 = load(f) +# @test OpenCV.PSNR(img1, img2) > 15 +# end + +@testset "JPG" begin + for (idx, img_path) in enumerate(jpg_images) + img1 = load(img_path) + f = joinpath(tmpdir, "img_$idx.jpg") + save(f, img1) + img2 = load(f) + @test OpenCV.PSNR(img1, img2) > 30 + end +end + +@testset "PNG" begin + for (idx, img_path) in enumerate(png_images) + img1 = load(img_path) + f = joinpath(tmpdir, "img_$idx.png") + save(f, img1) + img2 = load(f) + @test img1 == img2 + end +end + +@testset "TIFF" begin + for (idx, img_path) in enumerate(tiff_images) + img1 = load(img_path) + f = joinpath(tmpdir, "img_$idx.tiff") + save(f, img1) + img2 = load(f) + @test img1 == img2 + end +end \ No newline at end of file