diff --git a/README.md b/README.md index d236672..4a33d16 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,77 @@ -# ipol-tools -IPOL image tools +% IPOL image tools. + +# ABOUT + +* Author : Nicola Pierazzo +* Author : Gabriele Facciolo +* Copyright : (C) 2017 IPOL Image Processing On Line http://www.ipol.im/ +* Licence : GPL v3+, see GPLv3.txt +* Based on the 2010 implementation of DCT denoising by: + Guoshen Yu and Guillermo Sapiro +* Latest version available at: https://github.com/zvezdochiot/ipol-tools + +# OVERVIEW + +This source code provides an algorithm described in the IPOL article: http://www.ipol.im/ + +# UNIX/LINUX/MAC USER GUIDE + +The code is compilable on Unix/Linux and Mac OS. + +- Compilation. +Automated compilation requires the Cmake and make. + +- Dependencies. +This code requires the libiio (https://github.com/zvezdochiot/libiio). + +- Image formats. +Only the PNG, JPEG, and TIFF (float) formats are supported. + +------------------------------------------------------------------------- +Usage: +1. Download the library code and extract it. Go to that directory. + +``` +git clone https://github.com/zvezdochiot/libiio +cd libiio +``` + +2. Compile the library (on Unix/Linux/Mac OS). + +``` +ccmake . +cmake . +make +``` + +3. Install library + +``` +sudo make install +``` + +4. Download the code package and extract it. Go to that directory. + +``` +git clone https://github.com/zvezdochiot/ipol-tools +cd ipol-tools +``` + +5. Go to utils directory. + +5. Compile the source code (on Unix/Linux/Mac OS). +``` +ccmake . +cmake . +make +``` + +To visualize tiff (float) images use PVFLIP (https://github.com/gfacciol/pvflip) +or ImageJ (https://imagej.nih.gov/ij/index.html) + + +# ABOUT THIS FILE +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without any warranty. diff --git a/denoise/bm3d/CMakeLists.txt b/denoise/bm3d/CMakeLists.txt new file mode 100644 index 0000000..195df50 --- /dev/null +++ b/denoise/bm3d/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 2.8) +project(da3d) + +# The build type "Release" adds some optimizations +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE "Release") +endif () + +# Are we using gcc? +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC on MacOs needs this option to use the clang assembler + if (APPLE) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wa,-q") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-q") + endif () + # Optimize to the current CPU and enable warnings + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -Wall -Wextra") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra") +endif () + +# Enable C99 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") +else () + set (CMAKE_C_STANDARD 99) +endif () + +# Enable OpenMP +find_package (OpenMP) +if(OPENMP_FOUND) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") +endif() +find_path (IIO_INCLUDE_DIR iio.h) +find_library (IIO_LIBRARIES NAMES iio) +include_directories (SYSTEM ${iio_INCLUDE_DIR}) +link_libraries (${IIO_LIBRARIES}) +if (NOT IIO_INCLUDE_DIR) + message (FATAL_ERROR "IIO not found.") +endif () +find_path (FFTW_INCLUDES fftw3.h) +find_library (FFTWF_LIBRARIES NAMES fftw3f) + + +include_directories (PUBLIC ${EIGEN3_INCLUDE_DIR} PUBLIC ${TIFF_INCLUDE_DIR} PUBLIC ${JPEG_INCLUDE_DIR} PUBLIC ${PNG_INCLUDE_DIRS} PUBLIC ${ALGLIB_INCLUDE_DIR} PUBLIC ${FFTW_INCLUDES}) +link_libraries (${TIFF_LIBRARIES} ${JPEG_LIBRARIES} ${PNG_LIBRARIES} ${FFTWF_LIBRARIES}) + +set(SOURCE_FILES + bm3d.h + bm3d.cpp + lib_transforms.h + lib_transforms.cpp + utilities.h + utilities.cpp + main.cpp + ) + +set_property(SOURCE iio.c PROPERTY COMPILE_FLAGS "-Wno-unused-variable -Wno-unused-parameter -Wno-pointer-sign -Wno-parentheses -Wno-deprecated-declarations -Wno-unused-function") + +add_executable(bm3d ${SOURCE_FILES}) diff --git a/denoise/bm3d/LICENSE b/denoise/bm3d/LICENSE new file mode 100644 index 0000000..733c072 --- /dev/null +++ b/denoise/bm3d/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/denoise/bm3d/README.txt b/denoise/bm3d/README.txt new file mode 100644 index 0000000..43634a4 --- /dev/null +++ b/denoise/bm3d/README.txt @@ -0,0 +1,76 @@ +% BM3D image denoising. + +# ABOUT + +* Author : Marc Lebrun +* Copyright : (C) 2011 IPOL Image Processing On Line http://www.ipol.im/ +* Licence : GPL v3+, see GPLv3.txt + +# OVERVIEW + +This source code provides an implementation of the BM3D image denoising. + +# UNIX/LINUX/MAC USER GUIDE + +The code is compilable on Unix/Linux and Mac OS. + +- Compilation. +Automated compilation requires the cmake program. + +- Libraries. +This code requires the libpng libtiff libjpeg and libfftw librarers. + +- Image formats. +PNG, JPEG, TIFF (including floating point) formatis is supported. + +------------------------------------------------------------------------- +Usage: +1. Download the code package and extract it. Go to that directory. + +2. Compile the source code (on Unix/Linux/Mac OS). +mkdir build; cd build; cmake ..; make + +3. Run BM3D image denoising. +./bm3d +The generic way to run the code is: + +./bm3d input.png sigma ImDenoised.png [ImBasic.png ] +-tau_2d_hard 2DtransformStep1 -useSD_hard +-tau_2d_wien 2DtransformStep2 -useSD_wien +-color_space ColorSpace + +with : +- cinput.png is a noisy image; +- sigma is the value of the noise; +- ImBasic.png will contain the result of the first step of the algorithm; +- ImDenoised.png will contain the final result of the algorithm; +- 2DtransformStep1: choice of the 2D transform which will be applied in the + second step of the algorithm. You can choose the DCT transform or the + Bior1.5 transform for the 2D transform in the step 1 (tau_2D_hard = dct or bior) + and/or the step 2. (tau_2d_wien = dct or bior). +- useSD_hard: for the first step, users can choose if they prefer to use + standard variation for the weighted aggregation (useSD1 = 1) +- 2DtransformStep2: choice of the 2D transform which will be applied in the + second step of the algorithm. You can choose the DCT transform or the + Bior1.5 transform for the 2D transform in the step 1 (tau_2D_hard = dct or bior) + and/or the step 2. (tau_2d_wien = dct or bior). +- useSD_wien: for the second step, users can choose if they prefer to use + standard variation for the weighted aggregation (useSD2 = 1) +- ColorSpace: choice of the color space on which the image will be applied. + you can choose the colorspace for both steps between : rgb, yuv, ycbcr and opp. +- patch_size: overrides the default patch size +- nb_threads: specifies the number of working threads +- verbose: print additional information + +Example, run +./BM3Ddenoising cinput.png 10 ImDenoised.png ImBasic.png -useSD_wien \ + -tau_2d_hard bior -tau_2d_wien dct -color_space opp + +# ABOUT THIS FILE + +Copyright 2011 IPOL Image Processing On Line http://www.ipol.im/ + +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without any warranty. diff --git a/denoise/bm3d/VERSION b/denoise/bm3d/VERSION new file mode 100644 index 0000000..0808d7b --- /dev/null +++ b/denoise/bm3d/VERSION @@ -0,0 +1 @@ +0.20180203 diff --git a/denoise/bm3d/bm3d.cpp b/denoise/bm3d/bm3d.cpp new file mode 100644 index 0000000..ba155d8 --- /dev/null +++ b/denoise/bm3d/bm3d.cpp @@ -0,0 +1,1432 @@ +/* + * Copyright (c) 2011, Marc Lebrun + * All rights reserved. + * + * This program is free software: you can use, modify and/or + * redistribute it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later + * version. You should have received a copy of this license along + * this program. If not, see . + */ + + +/** + * @file bm3d.cpp + * @brief BM3D denoising functions + * + * @author Marc Lebrun + **/ + +#include +#include +#include + +#include "bm3d.h" +#include "utilities.h" +#include "lib_transforms.h" + +#define SQRT2 1.414213562373095 +#define SQRT2_INV 0.7071067811865475 +#define YUV 0 +#define YCBCR 1 +#define OPP 2 +#define RGB 3 +#define DCT 4 +#define BIOR 5 +#define HADAMARD 6 + + +using namespace std; + +bool ComparaisonFirst(pair pair1, pair pair2) +{ + return pair1.first < pair2.first; +} + +/** ----------------- **/ +/** - Main function - **/ +/** ----------------- **/ +/** + * @brief run BM3D process. Depending on if OpenMP is used or not, + * and on the number of available threads, it divides the noisy + * image in sub_images, to process them in parallel. + * + * @param sigma: value of assumed noise of the noisy image; + * @param img_noisy: noisy image; + * @param img_basic: will be the basic estimation after the 1st step + * @param img_denoised: will be the denoised final image; + * @param width, height, chnls: size of the image; + * @param useSD_h (resp. useSD_w): if true, use weight based + * on the standard variation of the 3D group for the + * first (resp. second) step, otherwise use the number + * of non-zero coefficients after Hard Thresholding + * (resp. the norm of Wiener coefficients); + * @param tau_2D_hard (resp. tau_2D_wien): 2D transform to apply + * on every 3D group for the first (resp. second) part. + * Allowed values are DCT and BIOR; + * @param color_space: Transformation from RGB to YUV. Allowed + * values are RGB (do nothing), YUV, YCBCR and OPP. + * @param patch_size: overrides the default patch size selection. + * patch_size=0: use default behavior + * patch_size>0: size to be used + * @param verbose: if true, print additional information; + * + * @return EXIT_FAILURE if color_space has not expected + * type, otherwise return EXIT_SUCCESS. + **/ +int run_bm3d( + const float sigma +, vector &img_noisy +, vector &img_basic +, vector &img_denoised +, const unsigned width +, const unsigned height +, const unsigned chnls +, const bool useSD_h +, const bool useSD_w +, const unsigned tau_2D_hard +, const unsigned tau_2D_wien +, const unsigned color_space +, const unsigned patch_size +, const unsigned nb_threads +, const bool verbose +){ + //! Parameters + const unsigned nHard = 16; //! Half size of the search window + const unsigned nWien = 16; //! Half size of the search window + const unsigned NHard = 16; //! Must be a power of 2 + const unsigned NWien = 32; //! Must be a power of 2 + const unsigned pHard = 3; + const unsigned pWien = 3; + // patch_size must be larger than 0 + if (patch_size == 0) + { + if (verbose) cout << "Parameter patch_size is selected automatically (8 or 12)." << endl; + } else { + // patch_size must be a power of 2 if tau_2D_* == BIOR + if ((tau_2D_hard == BIOR || + tau_2D_wien == BIOR) && + (patch_size & (patch_size - 1)) != 0) + { + cout << "Parameter patch_size must be a power of 2 if tau_2D_* == BIOR" << endl; + return EXIT_FAILURE; + } + } + //! Overrides size if patch_size>0, else default behavior (8 or 12 depending on test) + const unsigned kHard = patch_size > 0 ? patch_size : + ((tau_2D_hard == BIOR || sigma < 40.f) ? 8 : 12); + const unsigned kWien = patch_size > 0 ? patch_size : + ((tau_2D_wien == BIOR || sigma < 40.f) ? 8 : 12); + + //! Check memory allocation + if (img_basic.size() != img_noisy.size()) + img_basic.resize(img_noisy.size()); + if (img_denoised.size() != img_noisy.size()) + img_denoised.resize(img_noisy.size()); + + //! Transformation to YUV color space + if (color_space_transform(img_noisy, color_space, width, height, chnls, true) + != EXIT_SUCCESS) return EXIT_FAILURE; + + //! Check if OpenMP is used or if number of cores of the computer is > 1 + unsigned _nb_threads = nb_threads; + if (verbose) + { + cout << "OpenMP multithreading is"; +#ifndef _OPENMP + cout << " not"; +#endif + cout << " available." << endl; + } + + // set _nb_threads +#ifdef _OPENMP + unsigned avail_nb_threads = omp_get_max_threads(); + unsigned avail_nb_cores = omp_get_num_procs(); + + // if specified number exceeds available threads of if not specified at all + // at least use all available threads + if (_nb_threads > avail_nb_threads || _nb_threads == 0) + { + // log if specified number of threads exeeds number of real cores + if (_nb_threads > avail_nb_cores) + cout << "Parameter nb_threads should not exceed the number of real cores." << endl; + _nb_threads = avail_nb_threads; + } + // In case the number of threads is not a power of 2 + if (!power_of_2(_nb_threads)) + _nb_threads = closest_power_of_2(_nb_threads); +#else + if (_nb_threads > 1) + { + cout << "Parameter nb_threads has no effect if OpenMP multithreading is not available." << endl; + } + _nb_threads = 1; +#endif + + if (verbose) + { + cout << "Working threads: " << _nb_threads; +#ifdef _OPENMP + cout << " (Must be 2^n) (Total available threads/real cores: " << avail_nb_threads << "/" << avail_nb_cores << ")"; +#endif + cout << endl; + } + + //! Allocate plan for FFTW library + fftwf_plan plan_2d_for_1[_nb_threads]; + fftwf_plan plan_2d_for_2[_nb_threads]; + fftwf_plan plan_2d_inv[_nb_threads]; + + //! In the simple case + if (_nb_threads == 1) + { + //! Add boundaries and symetrize them + const unsigned h_b = height + 2 * nHard; + const unsigned w_b = width + 2 * nHard; + vector img_sym_noisy, img_sym_basic, img_sym_denoised; + symetrize(img_noisy, img_sym_noisy, width, height, chnls, nHard); + + //! Allocating Plan for FFTW process + if (tau_2D_hard == DCT) + { + const unsigned nb_cols = ind_size(w_b - kHard + 1, nHard, pHard); + allocate_plan_2d(&plan_2d_for_1[0], kHard, FFTW_REDFT10, + w_b * (2 * nHard + 1) * chnls); + allocate_plan_2d(&plan_2d_for_2[0], kHard, FFTW_REDFT10, + w_b * pHard * chnls); + allocate_plan_2d(&plan_2d_inv [0], kHard, FFTW_REDFT01, + NHard * nb_cols * chnls); + } + + //! Denoising, 1st Step + if (verbose) cout << "BM3D 1st step..."; + bm3d_1st_step(sigma, img_sym_noisy, img_sym_basic, w_b, h_b, chnls, nHard, + kHard, NHard, pHard, useSD_h, color_space, tau_2D_hard, + &plan_2d_for_1[0], &plan_2d_for_2[0], &plan_2d_inv[0]); + if (verbose) cout << "is done." << endl; + + //! To avoid boundaries problem + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc_b = c * w_b * h_b + nHard * w_b + nHard; + unsigned dc = c * width * height; + for (unsigned i = 0; i < height; i++) + for (unsigned j = 0; j < width; j++, dc++) + img_basic[dc] = img_sym_basic[dc_b + i * w_b + j]; + } + symetrize(img_basic, img_sym_basic, width, height, chnls, nHard); + + //! Allocating Plan for FFTW process + if (tau_2D_wien == DCT) + { + const unsigned nb_cols = ind_size(w_b - kWien + 1, nWien, pWien); + allocate_plan_2d(&plan_2d_for_1[0], kWien, FFTW_REDFT10, + w_b * (2 * nWien + 1) * chnls); + allocate_plan_2d(&plan_2d_for_2[0], kWien, FFTW_REDFT10, + w_b * pWien * chnls); + allocate_plan_2d(&plan_2d_inv [0], kWien, FFTW_REDFT01, + NWien * nb_cols * chnls); + } + + //! Denoising, 2nd Step + if (verbose) cout << "BM3D 2nd step..."; + bm3d_2nd_step(sigma, img_sym_noisy, img_sym_basic, img_sym_denoised, + w_b, h_b, chnls, nWien, kWien, NWien, pWien, useSD_w, color_space, + tau_2D_wien, &plan_2d_for_1[0], &plan_2d_for_2[0], &plan_2d_inv[0]); + if (verbose) cout << "is done." << endl; + + //! Obtention of img_denoised + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc_b = c * w_b * h_b + nWien * w_b + nWien; + unsigned dc = c * width * height; + for (unsigned i = 0; i < height; i++) + for (unsigned j = 0; j < width; j++, dc++) + img_denoised[dc] = img_sym_denoised[dc_b + i * w_b + j]; + } + } + //! If more than 1 threads are used + else + { + //! Cut the image in _nb_threads parts + vector > sub_noisy(_nb_threads); + vector > sub_basic(_nb_threads); + vector > sub_denoised(_nb_threads); + vector h_table(_nb_threads); + vector w_table(_nb_threads); + sub_divide(img_noisy, sub_noisy, w_table, h_table, width, height, chnls, + 2 * nWien, true); + + //! Allocating Plan for FFTW process + if (tau_2D_hard == DCT) + for (unsigned n = 0; n < _nb_threads; n++) + { + const unsigned nb_cols = ind_size(w_table[n] - kHard + 1, nHard, pHard); + allocate_plan_2d(&plan_2d_for_1[n], kHard, FFTW_REDFT10, + w_table[n] * (2 * nHard + 1) * chnls); + allocate_plan_2d(&plan_2d_for_2[n], kHard, FFTW_REDFT10, + w_table[n] * pHard * chnls); + allocate_plan_2d(&plan_2d_inv [n], kHard, FFTW_REDFT01, + NHard * nb_cols * chnls); + } + + //! denoising : 1st Step + if (verbose) cout << "BM3D 1st step..."; + #pragma omp parallel shared(sub_noisy, sub_basic, w_table, h_table, \ + plan_2d_for_1, plan_2d_for_2, plan_2d_inv) + { + #pragma omp for schedule(dynamic) nowait + for (unsigned n = 0; n < _nb_threads; n++) + { + bm3d_1st_step(sigma, sub_noisy[n], sub_basic[n], w_table[n], + h_table[n], chnls, nHard, kHard, NHard, pHard, useSD_h, + color_space, tau_2D_hard, &plan_2d_for_1[n], + &plan_2d_for_2[n], &plan_2d_inv[n]); + } + } + if (verbose) cout << "is done." << endl; + + sub_divide(img_basic, sub_basic, w_table, h_table, + width, height, chnls, 2 * nHard, false); + + sub_divide(img_basic, sub_basic, w_table, h_table, width, height, chnls, + 2 * nHard, true); + + //! Allocating Plan for FFTW process + if (tau_2D_wien == DCT) + for (unsigned n = 0; n < _nb_threads; n++) + { + const unsigned nb_cols = ind_size(w_table[n] - kWien + 1, nWien, pWien); + allocate_plan_2d(&plan_2d_for_1[n], kWien, FFTW_REDFT10, + w_table[n] * (2 * nWien + 1) * chnls); + allocate_plan_2d(&plan_2d_for_2[n], kWien, FFTW_REDFT10, + w_table[n] * pWien * chnls); + allocate_plan_2d(&plan_2d_inv [n], kWien, FFTW_REDFT01, + NWien * nb_cols * chnls); + } + + //! Denoising: 2nd Step + if (verbose) cout << "BM3D 2nd step..."; + #pragma omp parallel shared(sub_noisy, sub_basic, sub_denoised, w_table, \ + h_table, plan_2d_for_1, plan_2d_for_2, \ + plan_2d_inv) + { + #pragma omp for schedule(dynamic) nowait + for (unsigned n = 0; n < _nb_threads; n++) + { + bm3d_2nd_step(sigma, sub_noisy[n], sub_basic[n], sub_denoised[n], + w_table[n], h_table[n], chnls, nWien, kWien, NWien, pWien, + useSD_w, color_space, tau_2D_wien, &plan_2d_for_1[n], + &plan_2d_for_2[n], &plan_2d_inv[n]); + } + } + if (verbose) cout << "is done." << endl; + + //! Reconstruction of the image + sub_divide(img_denoised, sub_denoised, w_table, h_table, + width, height, chnls, 2 * nWien, false); + } + + //! Inverse color space transform to RGB + if (color_space_transform(img_denoised, color_space, width, height, chnls, false) + != EXIT_SUCCESS) return EXIT_FAILURE; + if (color_space_transform(img_noisy, color_space, width, height, chnls, false) + != EXIT_SUCCESS) return EXIT_FAILURE; + if (color_space_transform(img_basic, color_space, width, height, chnls, false) + != EXIT_SUCCESS) return EXIT_FAILURE; + + //! Free Memory + if (tau_2D_hard == DCT || tau_2D_wien == DCT) + for (unsigned n = 0; n < _nb_threads; n++) + { + fftwf_destroy_plan(plan_2d_for_1[n]); + fftwf_destroy_plan(plan_2d_for_2[n]); + fftwf_destroy_plan(plan_2d_inv[n]); + } + fftwf_cleanup(); + + return EXIT_SUCCESS; +} + +/** + * @brief Run the basic process of BM3D (1st step). The result + * is contained in img_basic. The image has boundary, which + * are here only for block-matching and doesn't need to be + * denoised. + * + * @param sigma: value of assumed noise of the image to denoise; + * @param img_noisy: noisy image; + * @param img_basic: will contain the denoised image after the 1st step; + * @param width, height, chnls : size of img_noisy; + * @param nHard: size of the boundary around img_noisy; + * @param useSD: if true, use weight based on the standard variation + * of the 3D group for the first step, otherwise use the number + * of non-zero coefficients after Hard-thresholding; + * @param tau_2D: DCT or BIOR; + * @param plan_2d_for_1, plan_2d_for_2, plan_2d_inv : for convenience. Used + * by fftw. + * + * @return none. + **/ +void bm3d_1st_step( + const float sigma +, vector const& img_noisy +, vector &img_basic +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned nHard +, const unsigned kHard +, const unsigned NHard +, const unsigned pHard +, const bool useSD +, const unsigned color_space +, const unsigned tau_2D +, fftwf_plan * plan_2d_for_1 +, fftwf_plan * plan_2d_for_2 +, fftwf_plan * plan_2d_inv +){ + //! Estimatation of sigma on each channel + vector sigma_table(chnls); + if (estimate_sigma(sigma, sigma_table, chnls, color_space) != EXIT_SUCCESS) + return; + + //! Parameters initialization + const float lambdaHard3D = 2.7f; //! Threshold for Hard Thresholding + const float tauMatch = (chnls == 1 ? 3.f : 1.f) * (sigma_table[0] < 35.0f ? 2500 : 5000); //! threshold used to determinate similarity between patches + + //! Initialization for convenience + vector row_ind; + ind_initialize(row_ind, height - kHard + 1, nHard, pHard); + vector column_ind; + ind_initialize(column_ind, width - kHard + 1, nHard, pHard); + const unsigned kHard_2 = kHard * kHard; + vector group_3D_table(chnls * kHard_2 * NHard * column_ind.size()); + vector wx_r_table; + wx_r_table.reserve(chnls * column_ind.size()); + vector hadamard_tmp(NHard); + + //! Check allocation memory + if (img_basic.size() != img_noisy.size()) + img_basic.resize(img_noisy.size()); + + //! Preprocessing (KaiserWindow, Threshold, DCT normalization, ...) + vector kaiser_window(kHard_2); + vector coef_norm(kHard_2); + vector coef_norm_inv(kHard_2); + preProcess(kaiser_window, coef_norm, coef_norm_inv, kHard); + + //! Preprocessing of Bior table + vector lpd, hpd, lpr, hpr; + bior15_coef(lpd, hpd, lpr, hpr); + + //! For aggregation part + vector denominator(width * height * chnls, 0.0f); + vector numerator (width * height * chnls, 0.0f); + + //! Precompute Bloc-Matching + vector > patch_table; + precompute_BM(patch_table, img_noisy, width, height, kHard, NHard, nHard, pHard, tauMatch); + + //! table_2D[p * N + q + (i * width + j) * kHard_2 + c * (2 * nHard + 1) * width * kHard_2] + vector table_2D((2 * nHard + 1) * width * chnls * kHard_2, 0.0f); + + //! Loop on i_r + for (unsigned ind_i = 0; ind_i < row_ind.size(); ind_i++) + { + const unsigned i_r = row_ind[ind_i]; + + //! Update of table_2D + if (tau_2D == DCT) + dct_2d_process(table_2D, img_noisy, plan_2d_for_1, plan_2d_for_2, nHard, + width, height, chnls, kHard, i_r, pHard, coef_norm, + row_ind[0], row_ind.back()); + else if (tau_2D == BIOR) + bior_2d_process(table_2D, img_noisy, nHard, width, height, chnls, + kHard, i_r, pHard, row_ind[0], row_ind.back(), lpd, hpd); + + wx_r_table.clear(); + group_3D_table.clear(); + + //! Loop on j_r + for (unsigned ind_j = 0; ind_j < column_ind.size(); ind_j++) + { + //! Initialization + const unsigned j_r = column_ind[ind_j]; + const unsigned k_r = i_r * width + j_r; + + //! Number of similar patches + const unsigned nSx_r = patch_table[k_r].size(); + + //! Build of the 3D group + vector group_3D(chnls * nSx_r * kHard_2, 0.0f); + for (unsigned c = 0; c < chnls; c++) + for (unsigned n = 0; n < nSx_r; n++) + { + const unsigned ind = patch_table[k_r][n] + (nHard - i_r) * width; + for (unsigned k = 0; k < kHard_2; k++) + group_3D[n + k * nSx_r + c * kHard_2 * nSx_r] = + table_2D[k + ind * kHard_2 + c * kHard_2 * (2 * nHard + 1) * width]; + } + + //! HT filtering of the 3D group + vector weight_table(chnls); + ht_filtering_hadamard(group_3D, hadamard_tmp, nSx_r, kHard, chnls, sigma_table, + lambdaHard3D, weight_table, !useSD); + + //! 3D weighting using Standard Deviation + if (useSD) + sd_weighting(group_3D, nSx_r, kHard, chnls, weight_table); + + //! Save the 3D group. The DCT 2D inverse will be done after. + for (unsigned c = 0; c < chnls; c++) + for (unsigned n = 0; n < nSx_r; n++) + for (unsigned k = 0; k < kHard_2; k++) + group_3D_table.push_back(group_3D[n + k * nSx_r + + c * kHard_2 * nSx_r]); + + //! Save weighting + for (unsigned c = 0; c < chnls; c++) + wx_r_table.push_back(weight_table[c]); + + } //! End of loop on j_r + + //! Apply 2D inverse transform + if (tau_2D == DCT) + dct_2d_inverse(group_3D_table, kHard, NHard * chnls * column_ind.size(), + coef_norm_inv, plan_2d_inv); + else if (tau_2D == BIOR) + bior_2d_inverse(group_3D_table, kHard, lpr, hpr); + + //! Registration of the weighted estimation + unsigned dec = 0; + for (unsigned ind_j = 0; ind_j < column_ind.size(); ind_j++) + { + const unsigned j_r = column_ind[ind_j]; + const unsigned k_r = i_r * width + j_r; + const unsigned nSx_r = patch_table[k_r].size(); + for (unsigned c = 0; c < chnls; c++) + { + for (unsigned n = 0; n < nSx_r; n++) + { + const unsigned k = patch_table[k_r][n] + c * width * height; + for (unsigned p = 0; p < kHard; p++) + for (unsigned q = 0; q < kHard; q++) + { + const unsigned ind = k + p * width + q; + numerator[ind] += kaiser_window[p * kHard + q] + * wx_r_table[c + ind_j * chnls] + * group_3D_table[p * kHard + q + n * kHard_2 + + c * kHard_2 * nSx_r + dec]; + denominator[ind] += kaiser_window[p * kHard + q] + * wx_r_table[c + ind_j * chnls]; + } + } + } + dec += nSx_r * chnls * kHard_2; + } + + } //! End of loop on i_r + + //! Final reconstruction + for (unsigned k = 0; k < width * height * chnls; k++) + img_basic[k] = numerator[k] / denominator[k]; +} + +/** + * @brief Run the final process of BM3D (2nd step). The result + * is contained in img_denoised. The image has boundary, which + * are here only for block-matching and doesn't need to be + * denoised. + * + * @param sigma: value of assumed noise of the image to denoise; + * @param img_noisy: noisy image; + * @param img_basic: contains the denoised image after the 1st step; + * @param img_denoised: will contain the final estimate of the denoised + * image after the second step; + * @param width, height, chnls : size of img_noisy; + * @param nWien: size of the boundary around img_noisy; + * @param useSD: if true, use weight based on the standard variation + * of the 3D group for the second step, otherwise use the norm + * of Wiener coefficients of the 3D group; + * @param tau_2D: DCT or BIOR. + * + * @return none. + **/ +void bm3d_2nd_step( + const float sigma +, vector const& img_noisy +, vector const& img_basic +, vector &img_denoised +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned nWien +, const unsigned kWien +, const unsigned NWien +, const unsigned pWien +, const bool useSD +, const unsigned color_space +, const unsigned tau_2D +, fftwf_plan * plan_2d_for_1 +, fftwf_plan * plan_2d_for_2 +, fftwf_plan * plan_2d_inv +){ + //! Estimatation of sigma on each channel + vector sigma_table(chnls); + if (estimate_sigma(sigma, sigma_table, chnls, color_space) != EXIT_SUCCESS) + return; + + //! Parameters initialization + const float tauMatch = (sigma_table[0] < 35.0f ? 400 : 3500); //! threshold used to determinate similarity between patches + + //! Initialization for convenience + vector row_ind; + ind_initialize(row_ind, height - kWien + 1, nWien, pWien); + vector column_ind; + ind_initialize(column_ind, width - kWien + 1, nWien, pWien); + const unsigned kWien_2 = kWien * kWien; + vector group_3D_table(chnls * kWien_2 * NWien * column_ind.size()); + vector wx_r_table; + wx_r_table.reserve(chnls * column_ind.size()); + vector tmp(NWien); + + //! Check allocation memory + if (img_denoised.size() != img_noisy.size()) + img_denoised.resize(img_noisy.size()); + + //! Preprocessing (KaiserWindow, Threshold, DCT normalization, ...) + vector kaiser_window(kWien_2); + vector coef_norm(kWien_2); + vector coef_norm_inv(kWien_2); + preProcess(kaiser_window, coef_norm, coef_norm_inv, kWien); + + //! For aggregation part + vector denominator(width * height * chnls, 0.0f); + vector numerator (width * height * chnls, 0.0f); + + //! Precompute Bloc-Matching + vector > patch_table; + precompute_BM(patch_table, img_basic, width, height, kWien, NWien, nWien, pWien, tauMatch); + + //! Preprocessing of Bior table + vector lpd, hpd, lpr, hpr; + bior15_coef(lpd, hpd, lpr, hpr); + + //! DCT_table_2D[p * N + q + (i * width + j) * kWien_2 + c * (2 * ns + 1) * width * kWien_2] + vector table_2D_img((2 * nWien + 1) * width * chnls * kWien_2, 0.0f); + vector table_2D_est((2 * nWien + 1) * width * chnls * kWien_2, 0.0f); + + //! Loop on i_r + for (unsigned ind_i = 0; ind_i < row_ind.size(); ind_i++) + { + const unsigned i_r = row_ind[ind_i]; + + //! Update of DCT_table_2D + if (tau_2D == DCT) + { + dct_2d_process(table_2D_img, img_noisy, plan_2d_for_1, plan_2d_for_2, + nWien, width, height, chnls, kWien, i_r, pWien, coef_norm, + row_ind[0], row_ind.back()); + dct_2d_process(table_2D_est, img_basic, plan_2d_for_1, plan_2d_for_2, + nWien, width, height, chnls, kWien, i_r, pWien, coef_norm, + row_ind[0], row_ind.back()); + } + else if (tau_2D == BIOR) + { + bior_2d_process(table_2D_img, img_noisy, nWien, width, height, + chnls, kWien, i_r, pWien, row_ind[0], row_ind.back(), lpd, hpd); + bior_2d_process(table_2D_est, img_basic, nWien, width, height, + chnls, kWien, i_r, pWien, row_ind[0], row_ind.back(), lpd, hpd); + } + + wx_r_table.clear(); + group_3D_table.clear(); + + //! Loop on j_r + for (unsigned ind_j = 0; ind_j < column_ind.size(); ind_j++) + { + //! Initialization + const unsigned j_r = column_ind[ind_j]; + const unsigned k_r = i_r * width + j_r; + + //! Number of similar patches + const unsigned nSx_r = patch_table[k_r].size(); + + //! Build of the 3D group + vector group_3D_est(chnls * nSx_r * kWien_2, 0.0f); + vector group_3D_img(chnls * nSx_r * kWien_2, 0.0f); + for (unsigned c = 0; c < chnls; c++) + for (unsigned n = 0; n < nSx_r; n++) + { + const unsigned ind = patch_table[k_r][n] + (nWien - i_r) * width; + for (unsigned k = 0; k < kWien_2; k++) + { + group_3D_est[n + k * nSx_r + c * kWien_2 * nSx_r] = + table_2D_est[k + ind * kWien_2 + c * kWien_2 * (2 * nWien + 1) * width]; + group_3D_img[n + k * nSx_r + c * kWien_2 * nSx_r] = + table_2D_img[k + ind * kWien_2 + c * kWien_2 * (2 * nWien + 1) * width]; + } + } + + //! Wiener filtering of the 3D group + vector weight_table(chnls); + wiener_filtering_hadamard(group_3D_img, group_3D_est, tmp, nSx_r, kWien, + chnls, sigma_table, weight_table, !useSD); + + //! 3D weighting using Standard Deviation + if (useSD) + sd_weighting(group_3D_est, nSx_r, kWien, chnls, weight_table); + + //! Save the 3D group. The DCT 2D inverse will be done after. + for (unsigned c = 0; c < chnls; c++) + for (unsigned n = 0; n < nSx_r; n++) + for (unsigned k = 0; k < kWien_2; k++) + group_3D_table.push_back(group_3D_est[n + k * nSx_r + c * kWien_2 * nSx_r]); + + //! Save weighting + for (unsigned c = 0; c < chnls; c++) + wx_r_table.push_back(weight_table[c]); + + } //! End of loop on j_r + + //! Apply 2D dct inverse + if (tau_2D == DCT) + dct_2d_inverse(group_3D_table, kWien, NWien * chnls * column_ind.size(), + coef_norm_inv, plan_2d_inv); + else if (tau_2D == BIOR) + bior_2d_inverse(group_3D_table, kWien, lpr, hpr); + + //! Registration of the weighted estimation + unsigned dec = 0; + for (unsigned ind_j = 0; ind_j < column_ind.size(); ind_j++) + { + const unsigned j_r = column_ind[ind_j]; + const unsigned k_r = i_r * width + j_r; + const unsigned nSx_r = patch_table[k_r].size(); + for (unsigned c = 0; c < chnls; c++) + { + for (unsigned n = 0; n < nSx_r; n++) + { + const unsigned k = patch_table[k_r][n] + c * width * height; + for (unsigned p = 0; p < kWien; p++) + for (unsigned q = 0; q < kWien; q++) + { + const unsigned ind = k + p * width + q; + numerator[ind] += kaiser_window[p * kWien + q] + * wx_r_table[c + ind_j * chnls] + * group_3D_table[p * kWien + q + n * kWien_2 + + c * kWien_2 * nSx_r + dec]; + denominator[ind] += kaiser_window[p * kWien + q] + * wx_r_table[c + ind_j * chnls]; + } + } + } + dec += nSx_r * chnls * kWien_2; + } + + } //! End of loop on i_r + + //! Final reconstruction + for (unsigned k = 0; k < width * height * chnls; k++) + img_denoised[k] = numerator[k] / denominator[k]; +} + +/** + * @brief Precompute a 2D DCT transform on all patches contained in + * a part of the image. + * + * @param DCT_table_2D : will contain the 2d DCT transform for all + * chosen patches; + * @param img : image on which the 2d DCT will be processed; + * @param plan_1, plan_2 : for convenience. Used by fftw; + * @param nHW : size of the boundary around img; + * @param width, height, chnls: size of img; + * @param kHW : size of patches (kHW x kHW); + * @param i_r: current index of the reference patches; + * @param step: space in pixels between two references patches; + * @param coef_norm : normalization coefficients of the 2D DCT; + * @param i_min (resp. i_max) : minimum (resp. maximum) value + * for i_r. In this case the whole 2d transform is applied + * on every patches. Otherwise the precomputed 2d DCT is re-used + * without processing it. + **/ +void dct_2d_process( + vector &DCT_table_2D +, vector const& img +, fftwf_plan * plan_1 +, fftwf_plan * plan_2 +, const unsigned nHW +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned kHW +, const unsigned i_r +, const unsigned step +, vector const& coef_norm +, const unsigned i_min +, const unsigned i_max +){ + //! Declarations + const unsigned kHW_2 = kHW * kHW; + const unsigned size = chnls * kHW_2 * width * (2 * nHW + 1); + + //! If i_r == ns, then we have to process all DCT + if (i_r == i_min || i_r == i_max) + { + //! Allocating Memory + float* vec = (float*) fftwf_malloc(size * sizeof(float)); + float* dct = (float*) fftwf_malloc(size * sizeof(float)); + + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc = c * width * height; + const unsigned dc_p = c * kHW_2 * width * (2 * nHW + 1); + for (unsigned i = 0; i < 2 * nHW + 1; i++) + for (unsigned j = 0; j < width - kHW; j++) + for (unsigned p = 0; p < kHW; p++) + for (unsigned q = 0; q < kHW; q++) + vec[p * kHW + q + dc_p + (i * width + j) * kHW_2] = + img[dc + (i_r + i - nHW + p) * width + j + q]; + } + + //! Process of all DCTs + fftwf_execute_r2r(*plan_1, vec, dct); + fftwf_free(vec); + + //! Getting the result + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc = c * kHW_2 * width * (2 * nHW + 1); + const unsigned dc_p = c * kHW_2 * width * (2 * nHW + 1); + for (unsigned i = 0; i < 2 * nHW + 1; i++) + for (unsigned j = 0; j < width - kHW; j++) + for (unsigned k = 0; k < kHW_2; k++) + DCT_table_2D[dc + (i * width + j) * kHW_2 + k] = + dct[dc_p + (i * width + j) * kHW_2 + k] * coef_norm[k]; + } + fftwf_free(dct); + } + else + { + const unsigned ds = step * width * kHW_2; + + //! Re-use of DCT already processed + for (unsigned c = 0; c < chnls; c++) + { + unsigned dc = c * width * (2 * nHW + 1) * kHW_2; + for (unsigned i = 0; i < 2 * nHW + 1 - step; i++) + for (unsigned j = 0; j < width - kHW; j++) + for (unsigned k = 0; k < kHW_2; k++) + DCT_table_2D[k + (i * width + j) * kHW_2 + dc] = + DCT_table_2D[k + (i * width + j) * kHW_2 + dc + ds]; + } + + //! Compute the new DCT + float* vec = (float*) fftwf_malloc(chnls * kHW_2 * step * width * sizeof(float)); + float* dct = (float*) fftwf_malloc(chnls * kHW_2 * step * width * sizeof(float)); + + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc = c * width * height; + const unsigned dc_p = c * kHW_2 * width * step; + for (unsigned i = 0; i < step; i++) + for (unsigned j = 0; j < width - kHW; j++) + for (unsigned p = 0; p < kHW; p++) + for (unsigned q = 0; q < kHW; q++) + vec[p * kHW + q + dc_p + (i * width + j) * kHW_2] = + img[(p + i + 2 * nHW + 1 - step + i_r - nHW) + * width + j + q + dc]; + } + + //! Process of all DCTs + fftwf_execute_r2r(*plan_2, vec, dct); + fftwf_free(vec); + + //! Getting the result + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc = c * kHW_2 * width * (2 * nHW + 1); + const unsigned dc_p = c * kHW_2 * width * step; + for (unsigned i = 0; i < step; i++) + for (unsigned j = 0; j < width - kHW; j++) + for (unsigned k = 0; k < kHW_2; k++) + DCT_table_2D[dc + ((i + 2 * nHW + 1 - step) * width + j) * kHW_2 + k] = + dct[dc_p + (i * width + j) * kHW_2 + k] * coef_norm[k]; + } + fftwf_free(dct); + } +} + +/** + * @brief Precompute a 2D bior1.5 transform on all patches contained in + * a part of the image. + * + * @param bior_table_2D : will contain the 2d bior1.5 transform for all + * chosen patches; + * @param img : image on which the 2d transform will be processed; + * @param nHW : size of the boundary around img; + * @param width, height, chnls: size of img; + * @param kHW : size of patches (kHW x kHW). MUST BE A POWER OF 2 !!! + * @param i_r: current index of the reference patches; + * @param step: space in pixels between two references patches; + * @param i_min (resp. i_max) : minimum (resp. maximum) value + * for i_r. In this case the whole 2d transform is applied + * on every patches. Otherwise the precomputed 2d DCT is re-used + * without processing it; + * @param lpd : low pass filter of the forward bior1.5 2d transform; + * @param hpd : high pass filter of the forward bior1.5 2d transform. + **/ +void bior_2d_process( + vector &bior_table_2D +, vector const& img +, const unsigned nHW +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned kHW +, const unsigned i_r +, const unsigned step +, const unsigned i_min +, const unsigned i_max +, vector &lpd +, vector &hpd +){ + //! Declarations + const unsigned kHW_2 = kHW * kHW; + + //! If i_r == ns, then we have to process all Bior1.5 transforms + if (i_r == i_min || i_r == i_max) + { + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc = c * width * height; + const unsigned dc_p = c * kHW_2 * width * (2 * nHW + 1); + for (unsigned i = 0; i < 2 * nHW + 1; i++) + for (unsigned j = 0; j < width - kHW; j++) + { + bior_2d_forward(img, bior_table_2D, kHW, dc + + (i_r + i - nHW) * width + j, width, + dc_p + (i * width + j) * kHW_2, lpd, hpd); + } + } + } + else + { + const unsigned ds = step * width * kHW_2; + + //! Re-use of Bior1.5 already processed + for (unsigned c = 0; c < chnls; c++) + { + unsigned dc = c * width * (2 * nHW + 1) * kHW_2; + for (unsigned i = 0; i < 2 * nHW + 1 - step; i++) + for (unsigned j = 0; j < width - kHW; j++) + for (unsigned k = 0; k < kHW_2; k++) + bior_table_2D[k + (i * width + j) * kHW_2 + dc] = + bior_table_2D[k + (i * width + j) * kHW_2 + dc + ds]; + } + + //! Compute the new Bior + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc = c * width * height; + const unsigned dc_p = c * kHW_2 * width * (2 * nHW + 1); + for (unsigned i = 0; i < step; i++) + for (unsigned j = 0; j < width - kHW; j++) + { + bior_2d_forward(img, bior_table_2D, kHW, dc + + (i + 2 * nHW + 1 - step + i_r - nHW) * width + j, + width, dc_p + ((i + 2 * nHW + 1 - step) + * width + j) * kHW_2, lpd, hpd); + } + } + } +} + +/** + * @brief HT filtering using Welsh-Hadamard transform (do only third + * dimension transform, Hard Thresholding and inverse transform). + * + * @param group_3D : contains the 3D block for a reference patch; + * @param tmp: allocated vector used in Hadamard transform for convenience; + * @param nSx_r : number of similar patches to a reference one; + * @param kHW : size of patches (kHW x kHW); + * @param chnls : number of channels of the image; + * @param sigma_table : contains value of noise for each channel; + * @param lambdaHard3D : value of thresholding; + * @param weight_table: the weighting of this 3D group for each channel; + * @param doWeight: if true process the weighting, do nothing + * otherwise. + * + * @return none. + **/ +void ht_filtering_hadamard( + vector &group_3D +, vector &tmp +, const unsigned nSx_r +, const unsigned kHard +, const unsigned chnls +, vector const& sigma_table +, const float lambdaHard3D +, vector &weight_table +, const bool doWeight +){ + //! Declarations + const unsigned kHard_2 = kHard * kHard; + for (unsigned c = 0; c < chnls; c++) + weight_table[c] = 0.0f; + const float coef_norm = sqrtf((float) nSx_r); + const float coef = 1.0f / (float) nSx_r; + + //! Process the Welsh-Hadamard transform on the 3rd dimension + for (unsigned n = 0; n < kHard_2 * chnls; n++) + hadamard_transform(group_3D, tmp, nSx_r, n * nSx_r); + + //! Hard Thresholding + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc = c * nSx_r * kHard_2; + const float T = lambdaHard3D * sigma_table[c] * coef_norm; + for (unsigned k = 0; k < kHard_2 * nSx_r; k++) + { + if (fabs(group_3D[k + dc]) > T) + weight_table[c]++; + else + group_3D[k + dc] = 0.0f; + } + } + + //! Process of the Welsh-Hadamard inverse transform + for (unsigned n = 0; n < kHard_2 * chnls; n++) + hadamard_transform(group_3D, tmp, nSx_r, n * nSx_r); + + for (unsigned k = 0; k < group_3D.size(); k++) + group_3D[k] *= coef; + + //! Weight for aggregation + if (doWeight) + for (unsigned c = 0; c < chnls; c++) + weight_table[c] = (weight_table[c] > 0.0f ? 1.0f / (float) + (sigma_table[c] * sigma_table[c] * weight_table[c]) : 1.0f); +} + +/** + * @brief Wiener filtering using Hadamard transform. + * + * @param group_3D_img : contains the 3D block built on img_noisy; + * @param group_3D_est : contains the 3D block built on img_basic; + * @param tmp: allocated vector used in hadamard transform for convenience; + * @param nSx_r : number of similar patches to a reference one; + * @param kWien : size of patches (kWien x kWien); + * @param chnls : number of channels of the image; + * @param sigma_table : contains value of noise for each channel; + * @param weight_table: the weighting of this 3D group for each channel; + * @param doWeight: if true process the weighting, do nothing + * otherwise. + * + * @return none. + **/ +void wiener_filtering_hadamard( + vector &group_3D_img +, vector &group_3D_est +, vector &tmp +, const unsigned nSx_r +, const unsigned kWien +, const unsigned chnls +, vector const& sigma_table +, vector &weight_table +, const bool doWeight +){ + //! Declarations + const unsigned kWien_2 = kWien * kWien; + const float coef = 1.0f / (float) nSx_r; + + for (unsigned c = 0; c < chnls; c++) + weight_table[c] = 0.0f; + + //! Process the Welsh-Hadamard transform on the 3rd dimension + for (unsigned n = 0; n < kWien_2 * chnls; n++) + { + hadamard_transform(group_3D_img, tmp, nSx_r, n * nSx_r); + hadamard_transform(group_3D_est, tmp, nSx_r, n * nSx_r); + } + + //! Wiener Filtering + for (unsigned c = 0; c < chnls; c++) + { + const unsigned dc = c * nSx_r * kWien_2; + for (unsigned k = 0; k < kWien_2 * nSx_r; k++) + { + float value = group_3D_est[dc + k] * group_3D_est[dc + k] * coef; + value /= (value + sigma_table[c] * sigma_table[c]); + group_3D_est[k + dc] = group_3D_img[k + dc] * value * coef; + weight_table[c] += value; + } + } + + //! Process of the Welsh-Hadamard inverse transform + for (unsigned n = 0; n < kWien_2 * chnls; n++) + hadamard_transform(group_3D_est, tmp, nSx_r, n * nSx_r); + + //! Weight for aggregation + if (doWeight) + for (unsigned c = 0; c < chnls; c++) + weight_table[c] = (weight_table[c] > 0.0f ? 1.0f / (float) + (sigma_table[c] * sigma_table[c] * weight_table[c]) : 1.0f); +} + +/** + * @brief Apply 2D dct inverse to a lot of patches. + * + * @param group_3D_table: contains a huge number of patches; + * @param kHW : size of patch; + * @param coef_norm_inv: contains normalization coefficients; + * @param plan : for convenience. Used by fftw. + * + * @return none. + **/ +void dct_2d_inverse( + vector &group_3D_table +, const unsigned kHW +, const unsigned N +, vector const& coef_norm_inv +, fftwf_plan * plan +){ + //! Declarations + const unsigned kHW_2 = kHW * kHW; + const unsigned size = kHW_2 * N; + const unsigned Ns = group_3D_table.size() / kHW_2; + + //! Allocate Memory + float* vec = (float*) fftwf_malloc(size * sizeof(float)); + float* dct = (float*) fftwf_malloc(size * sizeof(float)); + + //! Normalization + for (unsigned n = 0; n < Ns; n++) + for (unsigned k = 0; k < kHW_2; k++) + dct[k + n * kHW_2] = group_3D_table[k + n * kHW_2] * coef_norm_inv[k]; + + //! 2D dct inverse + fftwf_execute_r2r(*plan, dct, vec); + fftwf_free(dct); + + //! Getting the result + normalization + const float coef = 1.0f / (float)(kHW * 2); + for (unsigned k = 0; k < group_3D_table.size(); k++) + group_3D_table[k] = coef * vec[k]; + + //! Free Memory + fftwf_free(vec); +} + +void bior_2d_inverse( + vector &group_3D_table +, const unsigned kHW +, vector const& lpr +, vector const& hpr +){ + //! Declarations + const unsigned kHW_2 = kHW * kHW; + const unsigned N = group_3D_table.size() / kHW_2; + + //! Bior process + for (unsigned n = 0; n < N; n++) + bior_2d_inverse(group_3D_table, kHW, n * kHW_2, lpr, hpr); +} + +/** ----------------- **/ +/** - Preprocessing - **/ +/** ----------------- **/ +/** + * @brief Preprocess + * + * @param kaiser_window[kHW * kHW]: Will contain values of a Kaiser Window; + * @param coef_norm: Will contain values used to normalize the 2D DCT; + * @param coef_norm_inv: Will contain values used to normalize the 2D DCT; + * @param bior1_5_for: will contain coefficients for the bior1.5 forward transform + * @param bior1_5_inv: will contain coefficients for the bior1.5 inverse transform + * @param kHW: size of patches (need to be 8 or 12). + * + * @return none. + **/ +void preProcess( + vector &kaiserWindow +, vector &coef_norm +, vector &coef_norm_inv +, const unsigned kHW +){ + //! Kaiser Window coefficients + if (kHW == 8) + { + //! First quarter of the matrix + kaiserWindow[0 + kHW * 0] = 0.1924f; kaiserWindow[0 + kHW * 1] = 0.2989f; kaiserWindow[0 + kHW * 2] = 0.3846f; kaiserWindow[0 + kHW * 3] = 0.4325f; + kaiserWindow[1 + kHW * 0] = 0.2989f; kaiserWindow[1 + kHW * 1] = 0.4642f; kaiserWindow[1 + kHW * 2] = 0.5974f; kaiserWindow[1 + kHW * 3] = 0.6717f; + kaiserWindow[2 + kHW * 0] = 0.3846f; kaiserWindow[2 + kHW * 1] = 0.5974f; kaiserWindow[2 + kHW * 2] = 0.7688f; kaiserWindow[2 + kHW * 3] = 0.8644f; + kaiserWindow[3 + kHW * 0] = 0.4325f; kaiserWindow[3 + kHW * 1] = 0.6717f; kaiserWindow[3 + kHW * 2] = 0.8644f; kaiserWindow[3 + kHW * 3] = 0.9718f; + + //! Completing the rest of the matrix by symmetry + for(unsigned i = 0; i < kHW / 2; i++) + for (unsigned j = kHW / 2; j < kHW; j++) + kaiserWindow[i + kHW * j] = kaiserWindow[i + kHW * (kHW - j - 1)]; + + for (unsigned i = kHW / 2; i < kHW; i++) + for (unsigned j = 0; j < kHW; j++) + kaiserWindow[i + kHW * j] = kaiserWindow[kHW - i - 1 + kHW * j]; + } + else if (kHW == 12) + { + //! First quarter of the matrix + kaiserWindow[0 + kHW * 0] = 0.1924f; kaiserWindow[0 + kHW * 1] = 0.2615f; kaiserWindow[0 + kHW * 2] = 0.3251f; kaiserWindow[0 + kHW * 3] = 0.3782f; kaiserWindow[0 + kHW * 4] = 0.4163f; kaiserWindow[0 + kHW * 5] = 0.4362f; + kaiserWindow[1 + kHW * 0] = 0.2615f; kaiserWindow[1 + kHW * 1] = 0.3554f; kaiserWindow[1 + kHW * 2] = 0.4419f; kaiserWindow[1 + kHW * 3] = 0.5139f; kaiserWindow[1 + kHW * 4] = 0.5657f; kaiserWindow[1 + kHW * 5] = 0.5927f; + kaiserWindow[2 + kHW * 0] = 0.3251f; kaiserWindow[2 + kHW * 1] = 0.4419f; kaiserWindow[2 + kHW * 2] = 0.5494f; kaiserWindow[2 + kHW * 3] = 0.6390f; kaiserWindow[2 + kHW * 4] = 0.7033f; kaiserWindow[2 + kHW * 5] = 0.7369f; + kaiserWindow[3 + kHW * 0] = 0.3782f; kaiserWindow[3 + kHW * 1] = 0.5139f; kaiserWindow[3 + kHW * 2] = 0.6390f; kaiserWindow[3 + kHW * 3] = 0.7433f; kaiserWindow[3 + kHW * 4] = 0.8181f; kaiserWindow[3 + kHW * 5] = 0.8572f; + kaiserWindow[4 + kHW * 0] = 0.4163f; kaiserWindow[4 + kHW * 1] = 0.5657f; kaiserWindow[4 + kHW * 2] = 0.7033f; kaiserWindow[4 + kHW * 3] = 0.8181f; kaiserWindow[4 + kHW * 4] = 0.9005f; kaiserWindow[4 + kHW * 5] = 0.9435f; + kaiserWindow[5 + kHW * 0] = 0.4362f; kaiserWindow[5 + kHW * 1] = 0.5927f; kaiserWindow[5 + kHW * 2] = 0.7369f; kaiserWindow[5 + kHW * 3] = 0.8572f; kaiserWindow[5 + kHW * 4] = 0.9435f; kaiserWindow[5 + kHW * 5] = 0.9885f; + + //! Completing the rest of the matrix by symmetry + for(unsigned i = 0; i < kHW / 2; i++) + for (unsigned j = kHW / 2; j < kHW; j++) + kaiserWindow[i + kHW * j] = kaiserWindow[i + kHW * (kHW - j - 1)]; + + for (unsigned i = kHW / 2; i < kHW; i++) + for (unsigned j = 0; j < kHW; j++) + kaiserWindow[i + kHW * j] = kaiserWindow[kHW - i - 1 + kHW * j]; + } + else + for (unsigned k = 0; k < kHW * kHW; k++) + kaiserWindow[k] = 1.0f; + + //! Coefficient of normalization for DCT II and DCT II inverse + const float coef = 0.5f / ((float) (kHW)); + for (unsigned i = 0; i < kHW; i++) + for (unsigned j = 0; j < kHW; j++) + { + if (i == 0 && j == 0) + { + coef_norm [i * kHW + j] = 0.5f * coef; + coef_norm_inv[i * kHW + j] = 2.0f; + } + else if (i * j == 0) + { + coef_norm [i * kHW + j] = SQRT2_INV * coef; + coef_norm_inv[i * kHW + j] = SQRT2; + } + else + { + coef_norm [i * kHW + j] = 1.0f * coef; + coef_norm_inv[i * kHW + j] = 1.0f; + } + } +} + +/** + * @brief Precompute Bloc Matching (distance inter-patches) + * + * @param patch_table: for each patch in the image, will contain + * all coordonnate of its similar patches + * @param img: noisy image on which the distance is computed + * @param width, height: size of img + * @param kHW: size of patch + * @param NHW: maximum similar patches wanted + * @param nHW: size of the boundary of img + * @param tauMatch: threshold used to determinate similarity between + * patches + * + * @return none. + **/ +void precompute_BM( + vector > &patch_table +, const vector &img +, const unsigned width +, const unsigned height +, const unsigned kHW +, const unsigned NHW +, const unsigned nHW +, const unsigned pHW +, const float tauMatch +){ + //! Declarations + const unsigned Ns = 2 * nHW + 1; + const float threshold = tauMatch * kHW * kHW; + vector diff_table(width * height); + vector > sum_table((nHW + 1) * Ns, vector (width * height, 2 * threshold)); + if (patch_table.size() != width * height) + patch_table.resize(width * height); + vector row_ind; + ind_initialize(row_ind, height - kHW + 1, nHW, pHW); + vector column_ind; + ind_initialize(column_ind, width - kHW + 1, nHW, pHW); + + //! For each possible distance, precompute inter-patches distance + for (unsigned di = 0; di <= nHW; di++) + for (unsigned dj = 0; dj < Ns; dj++) + { + const int dk = (int) (di * width + dj) - (int) nHW; + const unsigned ddk = di * Ns + dj; + + //! Process the image containing the square distance between pixels + for (unsigned i = nHW; i < height - nHW; i++) + { + unsigned k = i * width + nHW; + for (unsigned j = nHW; j < width - nHW; j++, k++) + diff_table[k] = (img[k + dk] - img[k]) * (img[k + dk] - img[k]); + } + + //! Compute the sum for each patches, using the method of the integral images + const unsigned dn = nHW * width + nHW; + //! 1st patch, top left corner + float value = 0.0f; + for (unsigned p = 0; p < kHW; p++) + { + unsigned pq = p * width + dn; + for (unsigned q = 0; q < kHW; q++, pq++) + value += diff_table[pq]; + } + sum_table[ddk][dn] = value; + + //! 1st row, top + for (unsigned j = nHW + 1; j < width - nHW; j++) + { + const unsigned ind = nHW * width + j - 1; + float sum = sum_table[ddk][ind]; + for (unsigned p = 0; p < kHW; p++) + sum += diff_table[ind + p * width + kHW] - diff_table[ind + p * width]; + sum_table[ddk][ind + 1] = sum; + } + + //! General case + for (unsigned i = nHW + 1; i < height - nHW; i++) + { + const unsigned ind = (i - 1) * width + nHW; + float sum = sum_table[ddk][ind]; + //! 1st column, left + for (unsigned q = 0; q < kHW; q++) + sum += diff_table[ind + kHW * width + q] - diff_table[ind + q]; + sum_table[ddk][ind + width] = sum; + + //! Other columns + unsigned k = i * width + nHW + 1; + unsigned pq = (i + kHW - 1) * width + kHW - 1 + nHW + 1; + for (unsigned j = nHW + 1; j < width - nHW; j++, k++, pq++) + { + sum_table[ddk][k] = + sum_table[ddk][k - 1] + + sum_table[ddk][k - width] + - sum_table[ddk][k - 1 - width] + + diff_table[pq] + - diff_table[pq - kHW] + - diff_table[pq - kHW * width] + + diff_table[pq - kHW - kHW * width]; + } + + } + } + + //! Precompute Bloc Matching + vector > table_distance; + //! To avoid reallocation + table_distance.reserve(Ns * Ns); + + for (unsigned ind_i = 0; ind_i < row_ind.size(); ind_i++) + { + for (unsigned ind_j = 0; ind_j < column_ind.size(); ind_j++) + { + //! Initialization + const unsigned k_r = row_ind[ind_i] * width + column_ind[ind_j]; + table_distance.clear(); + patch_table[k_r].clear(); + + //! Threshold distances in order to keep similar patches + for (int dj = -(int) nHW; dj <= (int) nHW; dj++) + { + for (int di = 0; di <= (int) nHW; di++) + if (sum_table[dj + nHW + di * Ns][k_r] < threshold) + table_distance.push_back(make_pair( + sum_table[dj + nHW + di * Ns][k_r] + , k_r + di * width + dj)); + + for (int di = - (int) nHW; di < 0; di++) + if (sum_table[-dj + nHW + (-di) * Ns][k_r] < threshold) + table_distance.push_back(make_pair( + sum_table[-dj + nHW + (-di) * Ns][k_r + di * width + dj] + , k_r + di * width + dj)); + } + + //! We need a power of 2 for the number of similar patches, + //! because of the Welsh-Hadamard transform on the third dimension. + //! We assume that NHW is already a power of 2 + const unsigned nSx_r = (NHW > table_distance.size() ? + closest_power_of_2(table_distance.size()) : NHW); + + //! To avoid problem + if (nSx_r == 1 && table_distance.size() == 0) + { +// cout << "problem size" << endl; + table_distance.push_back(make_pair(0, k_r)); + } + + //! Sort patches according to their distance to the reference one + partial_sort(table_distance.begin(), table_distance.begin() + nSx_r, + table_distance.end(), ComparaisonFirst); + + //! Keep a maximum of NHW similar patches + for (unsigned n = 0; n < nSx_r; n++) + patch_table[k_r].push_back(table_distance[n].second); + + //! To avoid problem + if (nSx_r == 1) + patch_table[k_r].push_back(table_distance[0].second); + } + } +} + +/** + * @brief Process of a weight dependent on the standard + * deviation, used during the weighted aggregation. + * + * @param group_3D : 3D group + * @param nSx_r : number of similar patches in the 3D group + * @param kHW: size of patches + * @param chnls: number of channels in the image + * @param weight_table: will contain the weighting for each + * channel. + * + * @return none. + **/ +void sd_weighting( + std::vector const& group_3D +, const unsigned nSx_r +, const unsigned kHW +, const unsigned chnls +, std::vector &weight_table +){ + const unsigned N = nSx_r * kHW * kHW; + + for (unsigned c = 0; c < chnls; c++) + { + //! Initialization + float mean = 0.0f; + float std = 0.0f; + + //! Compute the sum and the square sum + for (unsigned k = 0; k < N; k++) + { + mean += group_3D[k]; + std += group_3D[k] * group_3D[k]; + } + + //! Sample standard deviation (Bessel's correction) + float res = (std - mean * mean / (float) N) / (float) (N - 1); + + //! Return the weight as used in the aggregation + weight_table[c] = (res > 0.0f ? 1.0f / sqrtf(res) : 0.0f); + } +} + + + + + + diff --git a/denoise/bm3d/bm3d.h b/denoise/bm3d/bm3d.h new file mode 100644 index 0000000..c814d7a --- /dev/null +++ b/denoise/bm3d/bm3d.h @@ -0,0 +1,199 @@ +#ifndef BM3D_H_INCLUDED +#define BM3D_H_INCLUDED + +#include +#include + +#ifdef _OPENMP + #include + #define _NO_OPENMP 0 +#else + #define _NO_OPENMP 1 +#endif + +/** ------------------ **/ +/** - Main functions - **/ +/** ------------------ **/ +//! Main function +int run_bm3d( + const float sigma +, std::vector &img_noisy +, std::vector &img_basic +, std::vector &img_denoised +, const unsigned width +, const unsigned height +, const unsigned chnls +, const bool useSD_h +, const bool useSD_w +, const unsigned tau_2D_hard +, const unsigned tau_2D_wien +, const unsigned color_space +, const unsigned patch_size = 0 +, const unsigned num_threads = 0 +, const bool verbose = false +); + +//! 1st step of BM3D +void bm3d_1st_step( + const float sigma +, std::vector const& img_noisy +, std::vector &img_basic +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned nHard +, const unsigned kHard +, const unsigned NHard +, const unsigned pHard +, const bool useSD +, const unsigned color_space +, const unsigned tau_2D +, fftwf_plan * plan_2d_for_1 +, fftwf_plan * plan_2d_for_2 +, fftwf_plan * plan_2d_inv +); + +//! 2nd step of BM3D +void bm3d_2nd_step( + const float sigma +, std::vector const& img_noisy +, std::vector const& img_basic +, std::vector &img_denoised +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned nWien +, const unsigned kWien +, const unsigned NWien +, const unsigned pWien +, const bool useSD +, const unsigned color_space +, const unsigned tau_2D +, fftwf_plan * plan_2d_for_1 +, fftwf_plan * plan_2d_for_2 +, fftwf_plan * plan_2d_inv +); + +//! Process 2D dct of a group of patches +void dct_2d_process( + std::vector &DCT_table_2D +, std::vector const& img +, fftwf_plan * plan_1 +, fftwf_plan * plan_2 +, const unsigned nHW +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned kHW +, const unsigned i_r +, const unsigned step +, std::vector const& coef_norm +, const unsigned i_min +, const unsigned i_max +); + +//! Process 2D bior1.5 transform of a group of patches +void bior_2d_process( + std::vector &bior_table_2D +, std::vector const& img +, const unsigned nHW +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned kHW +, const unsigned i_r +, const unsigned step +, const unsigned i_min +, const unsigned i_max +, std::vector &lpd +, std::vector &hpd +); + +void dct_2d_inverse( + std::vector &group_3D_table +, const unsigned kHW +, const unsigned N +, std::vector const& coef_norm_inv +, fftwf_plan * plan +); + +void bior_2d_inverse( + std::vector &group_3D_table +, const unsigned kHW +, std::vector const& lpr +, std::vector const& hpr +); + +//! HT filtering using Welsh-Hadamard transform (do only +//! third dimension transform, Hard Thresholding +//! and inverse Hadamard transform) +void ht_filtering_hadamard( + std::vector &group_3D +, std::vector &tmp +, const unsigned nSx_r +, const unsigned kHard +, const unsigned chnls +, std::vector const& sigma_table +, const float lambdaThr3D +, std::vector &weight_table +, const bool doWeight +); + +//! Wiener filtering using Welsh-Hadamard transform +void wiener_filtering_hadamard( + std::vector &group_3D_img +, std::vector &group_3D_est +, std::vector &tmp +, const unsigned nSx_r +, const unsigned kWien +, const unsigned chnls +, std::vector const& sigma_table +, std::vector &weight_table +, const bool doWeight +); + +//! Compute weighting using Standard Deviation +void sd_weighting( + std::vector const& group_3D +, const unsigned nSx_r +, const unsigned kHW +, const unsigned chnls +, std::vector &weight_table +); + +//! Apply a bior1.5 spline wavelet on a vector of size N x N. +void bior1_5_transform( + std::vector const& input +, std::vector &output +, const unsigned N +, std::vector const& bior_table +, const unsigned d_i +, const unsigned d_o +, const unsigned N_i +, const unsigned N_o +); + +/** ---------------------------------- **/ +/** - Preprocessing / Postprocessing - **/ +/** ---------------------------------- **/ +//! Preprocess coefficients of the Kaiser window and normalization coef for the DCT +void preProcess( + std::vector &kaiserWindow +, std::vector &coef_norm +, std::vector &coef_norm_inv +, const unsigned kHW +); + +void precompute_BM( + std::vector > &patch_table +, const std::vector &img +, const unsigned width +, const unsigned height +, const unsigned kHW +, const unsigned NHW +, const unsigned n +, const unsigned pHW +, const float tauMatch +); + +#endif // BM3D_H_INCLUDED diff --git a/denoise/bm3d/lib_transforms.cpp b/denoise/bm3d/lib_transforms.cpp new file mode 100644 index 0000000..7426e12 --- /dev/null +++ b/denoise/bm3d/lib_transforms.cpp @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2011, Marc Lebrun + * All rights reserved. + * + * This program is free software: you can use, modify and/or + * redistribute it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later + * version. You should have received a copy of this license along + * this program. If not, see . + */ + + +/** + * @file lib_transforms.cpp + * @brief 1D and 2D wavelet transforms + * + * @author Marc Lebrun + **/ + +#include "lib_transforms.h" +#include + +#include + +using namespace std; + +/** + * @brief Compute a full 2D Bior 1.5 spline wavelet (normalized) + * + * @param input: vector on which the transform will be applied; + * @param output: will contain the result; + * @param N: size of the 2D patch (N x N) on which the 2D transform + * is applied. Must be a power of 2; + * @param d_i: for convenience. Shift for input to access to the patch; + * @param r_i: for convenience. input(i, j) = input[d_i + i * r_i + j]; + * @param d_o: for convenience. Shift for output; + * @param lpd: low frequencies coefficients for the forward Bior 1.5; + * @param hpd: high frequencies coefficients for the forward Bior 1.5. + * + * @return none. + **/ +void bior_2d_forward( + vector const& input +, vector &output +, const unsigned N +, const unsigned d_i +, const unsigned r_i +, const unsigned d_o +, vector const& lpd +, vector const& hpd +){ + //! Initializing output + for (unsigned i = 0; i < N; i++) + for (unsigned j = 0; j < N; j++) + output[i * N + j + d_o] = input[i * r_i + j + d_i]; + + const unsigned iter_max = log2(N); + unsigned N_1 = N; + unsigned N_2 = N / 2; + const unsigned S_1 = lpd.size(); + const unsigned S_2 = S_1 / 2 - 1; + + for (unsigned iter = 0; iter < iter_max; iter++) + { + //! Periodic extension index initialization + vector tmp(N_1 + 2 * S_2); + vector ind_per(N_1 + 2 * S_2); + per_ext_ind(ind_per, N_1, S_2); + + //! Implementing row filtering + for (unsigned i = 0; i < N_1; i++) + { + //! Periodic extension of the signal in row + for (unsigned j = 0; j < tmp.size(); j++) + tmp[j] = output[d_o + i * N + ind_per[j]]; + + //! Low and High frequencies filtering + for (unsigned j = 0; j < N_2; j++) + { + float v_l = 0.0f, v_h = 0.0f; + for (unsigned k = 0; k < S_1; k++) + { + v_l += tmp[k + j * 2] * lpd[k]; + v_h += tmp[k + j * 2] * hpd[k]; + } + output[d_o + i * N + j] = v_l; + output[d_o + i * N + j + N_2] = v_h; + } + } + + //! Implementing column filtering + for (unsigned j = 0; j < N_1; j++) + { + //! Periodic extension of the signal in column + for (unsigned i = 0; i < tmp.size(); i++) + tmp[i] = output[d_o + j + ind_per[i] * N]; + + //! Low and High frequencies filtering + for (unsigned i = 0; i < N_2; i++) + { + float v_l = 0.0f, v_h = 0.0f; + for (unsigned k = 0; k < S_1; k++) + { + v_l += tmp[k + i * 2] * lpd[k]; + v_h += tmp[k + i * 2] * hpd[k]; + } + output[d_o + j + i * N] = v_l; + output[d_o + j + (i + N_2) * N] = v_h; + } + } + + //! Sizes update + N_1 /= 2; + N_2 /= 2; + } +} + +void bior_2d_forward_test( + vector const& input +, vector &output +, const unsigned N +, const unsigned d_i +, const unsigned r_i +, const unsigned d_o +, vector const& lpd +, vector const& hpd +, vector &tmp +, vector &ind_per +){ + //! Initializing output + for (unsigned i = 0; i < N; i++) + for (unsigned j = 0; j < N; j++) + output[i * N + j + d_o] = input[i * r_i + j + d_i]; + + const unsigned iter_max = log2(N); + unsigned N_1 = N; + unsigned N_2 = N / 2; + const unsigned S_1 = lpd.size(); + const unsigned S_2 = S_1 / 2 - 1; + + for (unsigned iter = 0; iter < iter_max; iter++) + { + //! Periodic extension index initialization +// vector tmp(N_1 + 2 * S_2); +// vector ind_per(N_1 + 2 * S_2); + per_ext_ind(ind_per, N_1, S_2); + + //! Implementing row filtering + for (unsigned i = 0; i < N_1; i++) + { + //! Periodic extension of the signal in row + for (unsigned j = 0; j < tmp.size(); j++) + tmp[j] = output[d_o + i * N + ind_per[j]]; + + //! Low and High frequencies filtering + for (unsigned j = 0; j < N_2; j++) + { + float v_l = 0.0f, v_h = 0.0f; + for (unsigned k = 0; k < S_1; k++) + { + v_l += tmp[k + j * 2] * lpd[k]; + v_h += tmp[k + j * 2] * hpd[k]; + } + output[d_o + i * N + j] = v_l; + output[d_o + i * N + j + N_2] = v_h; +// output[d_o + i * N + j] = inner_product(tmp.begin() + j * 2, tmp.begin() + j * 2 + S_1, lpd.begin(), 0.f); +// output[d_o + i * N + j + N_2] = inner_product(tmp.begin() + j * 2, tmp.begin() + j * 2 + S_1, hpd.begin(), 0.f); + } + } + + //! Implementing column filtering + for (unsigned j = 0; j < N_1; j++) + { + //! Periodic extension of the signal in column + for (unsigned i = 0; i < tmp.size(); i++) + tmp[i] = output[d_o + j + ind_per[i] * N]; + + //! Low and High frequencies filtering + for (unsigned i = 0; i < N_2; i++) + { + float v_l = 0.0f, v_h = 0.0f; + for (unsigned k = 0; k < S_1; k++) + { + v_l += tmp[k + i * 2] * lpd[k]; + v_h += tmp[k + i * 2] * hpd[k]; + } + output[d_o + j + i * N] = v_l; + output[d_o + j + (i + N_2) * N] = v_h; +// output[d_o + j + i * N] = inner_product(tmp.begin() + i * 2, tmp.begin() + i * 2 + S_1, lpd.begin(), 0.f); +// output[d_o + j + (i + N_2) * N] = inner_product(tmp.begin() + i * 2, tmp.begin() + i * 2 + S_1, hpd.begin(), 0.f); + } + } + + //! Sizes update + N_1 /= 2; + N_2 /= 2; + } +} + +/** + * @brief Compute a full 2D Bior 1.5 spline wavelet inverse (normalized) + * + * @param signal: vector on which the transform will be applied; It + * will contain the result at the end; + * @param N: size of the 2D patch (N x N) on which the 2D transform + * is applied. Must be a power of 2; + * @param d_s: for convenience. Shift for signal to access to the patch; + * @param lpr: low frequencies coefficients for the inverse Bior 1.5; + * @param hpr: high frequencies coefficients for the inverse Bior 1.5. + * + * @return none. + **/ +void bior_2d_inverse( + vector &signal +, const unsigned N +, const unsigned d_s +, vector const& lpr +, vector const& hpr +){ + //! Initialization + const unsigned iter_max = log2(N); + unsigned N_1 = 2; + unsigned N_2 = 1; + const unsigned S_1 = lpr.size(); + const unsigned S_2 = S_1 / 2 - 1; + + for (unsigned iter = 0; iter < iter_max; iter++) + { + + vector tmp(N_1 + S_2 * N_1); + vector ind_per(N_1 + 2 * S_2 * N_2); + per_ext_ind(ind_per, N_1, S_2 * N_2); + + //! Implementing column filtering + for (unsigned j = 0; j < N_1; j++) + { + //! Periodic extension of the signal in column + for (unsigned i = 0; i < tmp.size(); i++) + tmp[i] = signal[d_s + j + ind_per[i] * N]; + + //! Low and High frequencies filtering + for (unsigned i = 0; i < N_2; i++) + { + float v_l = 0.0f, v_h = 0.0f; + for (unsigned k = 0; k < S_1; k++) + { + v_l += lpr[k] * tmp[k * N_2 + i]; + v_h += hpr[k] * tmp[k * N_2 + i]; + } + + signal[d_s + i * 2 * N + j] = v_h; + signal[d_s + (i * 2 + 1) * N + j] = v_l; + } + } + + //! Implementing row filtering + for (unsigned i = 0; i < N_1; i++) + { + //! Periodic extension of the signal in row + for (unsigned j = 0; j < tmp.size(); j++) + tmp[j] = signal[d_s + i * N + ind_per[j]]; + + //! Low and High frequencies filtering + for (unsigned j = 0; j < N_2; j++) + { + float v_l = 0.0f, v_h = 0.0f; + for (unsigned k = 0; k < S_1; k++) + { + v_l += lpr[k] * tmp[k * N_2 + j]; + v_h += hpr[k] * tmp[k * N_2 + j]; + } + + signal[d_s + i * N + j * 2] = v_h; + signal[d_s + i * N + j * 2 + 1] = v_l; + } + } + + //! Sizes update + N_1 *= 2; + N_2 *= 2; + } +} + +/** + * @brief Initialize forward and backward low and high filter + * for a Bior1.5 spline wavelet. + * + * @param lp1: low frequencies forward filter; + * @param hp1: high frequencies forward filter; + * @param lp2: low frequencies backward filter; + * @param hp2: high frequencies backward filter. + **/ +void bior15_coef( + vector &lp1 +, vector &hp1 +, vector &lp2 +, vector &hp2 +){ + const float coef_norm = 1.f / (sqrtf(2.f) * 128.f); + const float sqrt2_inv = 1.f / sqrtf(2.f); + + lp1.resize(10); + lp1[0] = 3.f ; + lp1[1] = -3.f ; + lp1[2] = -22.f ; + lp1[3] = 22.f ; + lp1[4] = 128.f; + lp1[5] = 128.f; + lp1[6] = 22.f ; + lp1[7] = -22.f ; + lp1[8] = -3.f ; + lp1[9] = 3.f ; + + hp1.resize(10); + hp1[0] = 0.f; + hp1[1] = 0.f; + hp1[2] = 0.f; + hp1[3] = 0.f; + hp1[4] = -sqrt2_inv; + hp1[5] = sqrt2_inv; + hp1[6] = 0.f; + hp1[7] = 0.f; + hp1[8] = 0.f; + hp1[9] = 0.f; + + lp2.resize(10); + lp2[0] = 0.f; + lp2[1] = 0.f; + lp2[2] = 0.f; + lp2[3] = 0.f; + lp2[4] = sqrt2_inv; + lp2[5] = sqrt2_inv; + lp2[6] = 0.f; + lp2[7] = 0.f; + lp2[8] = 0.f; + lp2[9] = 0.f; + + hp2.resize(10); + hp2[0] = 3.f ; + hp2[1] = 3.f ; + hp2[2] = -22.f ; + hp2[3] = -22.f ; + hp2[4] = 128.f; + hp2[5] = -128.f; + hp2[6] = 22.f ; + hp2[7] = 22.f ; + hp2[8] = -3.f ; + hp2[9] = -3.f ; + + for (unsigned k = 0; k < 10; k++) + { + lp1[k] *= coef_norm; + hp2[k] *= coef_norm; + } +} + +/** + * @brief Apply Welsh-Hadamard transform on vec (non normalized !!) + * + * @param vec: vector on which a Hadamard transform will be applied. + * It will contain the transform at the end; + * @param tmp: must have the same size as vec. Used for convenience; + * @param N, d: the Hadamard transform will be applied on vec[d] -> vec[d + N]. + * N must be a power of 2!!!! + * + * @return None. + **/ +void hadamard_transform( + vector &vec +, vector &tmp +, const unsigned N +, const unsigned D +){ + if (N == 1) + return; + else if (N == 2) + { + const float a = vec[D + 0]; + const float b = vec[D + 1]; + vec[D + 0] = a + b; + vec[D + 1] = a - b; + } + else + { + const unsigned n = N / 2; + for (unsigned k = 0; k < n; k++) + { + const float a = vec[D + 2 * k]; + const float b = vec[D + 2 * k + 1]; + vec[D + k] = a + b; + tmp[k] = a - b; + } + for (unsigned k = 0; k < n; k++) + vec[D + n + k] = tmp[k]; + + hadamard_transform(vec, tmp, n, D); + hadamard_transform(vec, tmp, n, D + n); + } +} + +/** + * @brief Obtain the ceil of log_2(N) + * + * @param N: in the case N = 2^n, return n. + * + * @return n; + **/ +unsigned log2( + const unsigned N +){ + unsigned k = 1; + unsigned n = 0; + while (k < N) + { + k *= 2; + n++; + } + return n; +} + +/** + * @brief Obtain index for periodic extension. + * + * @param ind_per: will contain index. Its size must be N + 2 * L; + * @param N: size of the original signal; + * @param L: size of boundaries to add on each side of the signal. + * + * @return none. + **/ +void per_ext_ind( + vector &ind_per +, const unsigned N +, const unsigned L +){ + for (unsigned k = 0; k < N; k++) + ind_per[k + L] = k; + + int ind1 = (N - L); + while (ind1 < 0) + ind1 += N; + unsigned ind2 = 0; + unsigned k = 0; + while(k < L) + { + ind_per[k] = (unsigned) ind1; + ind_per[k + L + N] = ind2; + ind1 = ((unsigned) ind1 < N - 1 ? (unsigned) ind1 + 1 : 0); + ind2 = (ind2 < N - 1 ? ind2 + 1 : 0); + k++; + } +} diff --git a/denoise/bm3d/lib_transforms.h b/denoise/bm3d/lib_transforms.h new file mode 100644 index 0000000..3283f35 --- /dev/null +++ b/denoise/bm3d/lib_transforms.h @@ -0,0 +1,68 @@ +#ifndef LIB_TRANSFORMS_INCLUDED +#define LIB_TRANSFORMS_INCLUDED + +#include + +//! Compute a Bior1.5 2D +void bior_2d_forward( + std::vector const& input +, std::vector &output +, const unsigned N +, const unsigned d_i +, const unsigned r_i +, const unsigned d_o +, std::vector const& lpd +, std::vector const& hpd +); + +void bior_2d_forward_test( + std::vector const& input +, std::vector &output +, const unsigned N +, const unsigned d_i +, const unsigned r_i +, const unsigned d_o +, std::vector const& lpd +, std::vector const& hpd +, std::vector &tmp +, std::vector &ind_per +); + +//! Compute a Bior1.5 2D inverse +void bior_2d_inverse( + std::vector &signal +, const unsigned N +, const unsigned d_s +, std::vector const& lpr +, std::vector const& hpr +); + +//! Precompute the Bior1.5 coefficients +void bior15_coef( + std::vector &lp1 +, std::vector &hp1 +, std::vector &lp2 +, std::vector &hp2 +); + +//! Apply Walsh-Hadamard transform (non normalized) on a vector of size N = 2^n +void hadamard_transform( + std::vector &vec +, std::vector &tmp +, const unsigned N +, const unsigned d +); + +//! Process the log2 of N +unsigned log2( + const unsigned N +); + +//! Obtain index for periodic extension +void per_ext_ind( + std::vector &ind_per +, const unsigned N +, const unsigned L +); + +#endif // LIB_TRANSFORMS_INCLUDED diff --git a/denoise/bm3d/main.cpp b/denoise/bm3d/main.cpp new file mode 100644 index 0000000..62e0cc8 --- /dev/null +++ b/denoise/bm3d/main.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "bm3d.h" +#include "utilities.h" + +#define YUV 0 +#define YCBCR 1 +#define OPP 2 +#define RGB 3 +#define DCT 4 +#define BIOR 5 +#define HADAMARD 6 +#define NONE 7 + +using namespace std; + +// c: pointer to original argc +// v: pointer to original argv +// o: option name after hyphen +// d: default value (if NULL, the option takes no argument) +const char *pick_option(int *c, char **v, const char *o, const char *d) { + int id = d ? 1 : 0; + for (int i = 0; i < *c - id; i++) { + if (v[i][0] == '-' && 0 == strcmp(v[i] + 1, o)) { + char *r = v[i + id] + 1 - id; + for (int j = i; j < *c - id; j++) + v[j] = v[j + id + 1]; + *c -= id + 1; + return r; + } + } + return d; +} + +/** + * @file main.cpp + * @brief Main executable file. Do not use lib_fftw to + * process DCT. + * + * @author MARC LEBRUN + */ +int main(int argc, char **argv) +{ + //! Variables initialization + const char *_tau_2D_hard = pick_option(&argc, argv, "tau_2d_hard", "bior"); + const char *_tau_2D_wien = pick_option(&argc, argv, "tau_2d_wien", "dct"); + const char *_color_space = pick_option(&argc, argv, "color_space", "opp"); + const char *_patch_size = pick_option(&argc, argv, "patch_size", "0"); // >0: overrides default + const char *_nb_threads = pick_option(&argc, argv, "nb_threads", "0"); + const bool useSD_1 = pick_option(&argc, argv, "useSD_hard", NULL) != NULL; + const bool useSD_2 = pick_option(&argc, argv, "useSD_wien", NULL) != NULL; + const bool verbose = pick_option(&argc, argv, "verbose", NULL) != NULL; + + //! Check parameters + const unsigned tau_2D_hard = (strcmp(_tau_2D_hard, "dct" ) == 0 ? DCT : + (strcmp(_tau_2D_hard, "bior") == 0 ? BIOR : NONE)); + if (tau_2D_hard == NONE) { + cout << "tau_2d_hard is not known." << endl; + argc = 0; //abort + } + const unsigned tau_2D_wien = (strcmp(_tau_2D_wien, "dct" ) == 0 ? DCT : + (strcmp(_tau_2D_wien, "bior") == 0 ? BIOR : NONE)); + if (tau_2D_wien == NONE) { + cout << "tau_2d_wien is not known." << endl; + argc = 0; //abort + }; + const unsigned color_space = (strcmp(_color_space, "rgb" ) == 0 ? RGB : + (strcmp(_color_space, "yuv" ) == 0 ? YUV : + (strcmp(_color_space, "ycbcr") == 0 ? YCBCR : + (strcmp(_color_space, "opp" ) == 0 ? OPP : NONE)))); + if (color_space == NONE) { + cout << "color_space is not known." << endl; + argc = 0; //abort + }; + + const int patch_size = atoi(_patch_size); + if (patch_size < 0) + { + cout << "The patch_size parameter must not be negative." << endl; + return EXIT_FAILURE; + } else { + const unsigned patch_size = (unsigned) patch_size; + } + const int nb_threads = atoi(_nb_threads); + if (nb_threads < 0) + { + cout << "The nb_threads parameter must not be negative." << endl; + return EXIT_FAILURE; + } else { + const unsigned nb_threads = (unsigned) nb_threads; + } + + //! Check if there is the right call for the algorithm + if (argc < 4) { + cerr << "usage: " << argv[0] << " input sigma output [basic]\n\ + [-tau_2d_hard {dct,bior} (default: bior)]\n\ + [-useSD_hard]\n\ + [-tau_2d_wien {dct,bior} (default: dct)]\n\ + [-useSD_wien]\n\ + [-color_space {rgb,yuv,opp,ycbcr} (default: opp)]\n\ + [-patch_size {0,8,...} (default: 0, auto size, 8 or 12 depending on sigma)]\n\ + [-nb_threads (default: 0, auto number)]\n\ + [-verbose]" << endl; + return EXIT_FAILURE; + } + + //! Declarations + vector img_noisy, img_basic, img_denoised; + unsigned width, height, chnls; + + //! Load image + if(load_image(argv[1], img_noisy, &width, &height, &chnls) != EXIT_SUCCESS) + return EXIT_FAILURE; + + float fSigma = atof(argv[2]); + + //! Denoising + if (run_bm3d(fSigma, img_noisy, img_basic, img_denoised, width, height, chnls, + useSD_1, useSD_2, tau_2D_hard, tau_2D_wien, color_space, patch_size, + nb_threads, verbose) + != EXIT_SUCCESS) + return EXIT_FAILURE; + + //! save noisy, denoised and differences images + cout << endl << "Save images..."; + + if (argc > 4) + if (save_image(argv[4], img_basic, width, height, chnls) != EXIT_SUCCESS) + return EXIT_FAILURE; + + if (save_image(argv[3], img_denoised, width, height, chnls) != EXIT_SUCCESS) + return EXIT_FAILURE; + + cout << "done." << endl; + + return EXIT_SUCCESS; +} diff --git a/denoise/bm3d/man/bm3d.1 b/denoise/bm3d/man/bm3d.1 new file mode 100644 index 0000000..1c45838 --- /dev/null +++ b/denoise/bm3d/man/bm3d.1 @@ -0,0 +1,78 @@ +.TH "IPOL tools User Manual" 1 "03 Feb 2018" "IPOL documentation" + +.SH NAME +bm3d + +.SH DESCRIPTION +BM3D image denoising. + +.SH SYNOPSIS +bm3d input sigma output [basic] + [-tau_2d_hard {dct,bior} (default: bior)] + [-useSD_hard] + [-tau_2d_wien {dct,bior} (default: dct)] + [-useSD_wien] + [-color_space {rgb,yuv,opp,ycbcr} (default: opp)] + [-patch_size {0,8,...} (default: 0, auto size, 8 or 12 depending on sigma)] + [-nb_threads (default: 0, auto number)] + [-verbose] + +.SH OPTIONS +.TP +input +name input graphics file (jpeg | png | tiff) +.TP +sigma +is the value of the noise (int, mandatory, recomended = 16) +.TP +output +name output graphics file (jpeg | png | tiff) +.TP +basic +will contain the result of the first step of the algorithm +.TP +-2DtransformStep1 +choice of the 2D transform which will be applied in the second step of the algorithm. You can choose the DCT transform or the Bior1.5 transform for the 2D transform in the step 1 (tau_2D_hard = dct or bior) and/or the step 2. (tau_2d_wien = dct or bior). +.TP +-useSD_hard +for the first step, users can choose if they prefer to use standard variation for the weighted aggregation (useSD1 = 1) +.TP +-2DtransformStep2 +choice of the 2D transform which will be applied in the second step of the algorithm. You can choose the DCT transform or the Bior1.5 transform for the 2D transform in the step 1 (tau_2D_hard = dct or bior) and/or the step 2. (tau_2d_wien = dct or bior). +.TP +-useSD_wien +for the second step, users can choose if they prefer to use standard variation for the weighted aggregation (useSD2 = 1) +.TP +-color_space +ColorSpace: choice of the color space on which the image will be applied. You can choose the colorspace for both steps between : rgb, yuv, ycbcr and opp. +.TP +-patch_size +overrides the default patch size +.TP +-nb_threads +specifies the number of working threads +.TP +-verbose +print additional information + +.SH EXAMPLE +bm3d input.png 10 output.png basic.png -useSD_wien -tau_2d_hard bior -tau_2d_wien dct -color_space opp + +.SH COPYRIGHT +Copyright : (C) 2017 IPOL Image Processing On Line http://www.ipol.im/ + Licence : GPL v3+, see GPLv3.txt + +.SH SEE ALSO + cjpeg (1), + djpeg (1) + +.SH CONTACTS +.TP +Author: +Nicola Pierazzo +.TP +Author: +Gabriele Facciolo +.TP +Latest version available at: +https://github.com/gfacciol/bm3d diff --git a/denoise/bm3d/test_dpkg_NEEDED_log.sh b/denoise/bm3d/test_dpkg_NEEDED_log.sh new file mode 100644 index 0000000..53dc2f8 --- /dev/null +++ b/denoise/bm3d/test_dpkg_NEEDED_log.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# test_dpkg_NEEDED.sh + +VER="1.0" +GREEN="\033[1;32m" +RED="\033[0;31m" +YELLOW="\033[1;33m" +ENDCOLOR="\033[0m" + +tproc=`basename $0` +echo -e $GREEN"$tproc version $VER"$ENDCOLOR +echo "" + +usage() +{ + tproc=`basename $0` + echo -e $YELLOW"usage:"$ENDCOLOR + echo -e $GREEN" bash $tproc elf-component"$ENDCOLOR +} + +testargs() +{ + if [ "+$1" = "+" -o "+$1" = "+-h" -o "+$1" = "+--help" ] + then + usage + exit 0 + fi +} + +testcomponent() +{ + tnocomp="" + tcomp="/usr/bin/objdump" + tdeb="binutils_*.deb" + if [ ! -f "$tcomp" ] + then + tnocomp="$tnocomp $tcomp($tdeb)" + fi + tcomp="/usr/bin/dpkg" + tdeb="dpkg_*.deb" + if [ ! -f "$tcomp" ] + then + tnocomp="$tnocomp $tcomp($tdeb)" + fi + if [ "+$tnocomp" != "+" ] + then + echo -e $RED"Not found $tnocomp !"$ENDCOLOR + echo "" + exit 0 + fi +} + +main() +{ + tlog="$1.depends" + echo "" > "$tlog" + echo "$1" >> "$tlog" + echo "" >> "$tlog" + + objdump -x $1 | grep -w NEEDED | awk '{print $2}' | while read tlib + do + echo "Library: $tlib" >> "$tlog" + dpkg -S $tlib >> "$tlog" + echo "" >> "$tlog" + done +} + +testargs $1 +testcomponent +main $1 + +#end diff --git a/denoise/bm3d/utilities.cpp b/denoise/bm3d/utilities.cpp new file mode 100644 index 0000000..a9228d9 --- /dev/null +++ b/denoise/bm3d/utilities.cpp @@ -0,0 +1,730 @@ +/* + * Copyright (c) 2011, Marc Lebrun + * All rights reserved. + * + * This program is free software: you can use, modify and/or + * redistribute it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later + * version. You should have received a copy of this license along + * this program. If not, see . + */ + + +/** + * @file utilities.cpp + * @brief Utilities functions + * + * @author Marc Lebrun + **/ + +#include +#include +#include +#include +#include + +#include "utilities.h" +extern "C"{ +#include +} + +#define YUV 0 +#define YCBCR 1 +#define OPP 2 +#define RGB 3 + + using namespace std; + + /** + * @brief Load image, check the number of channels + * + * @param name : name of the image to read + * @param img : vector which will contain the image : R, G and B concatenated + * @param width, height, chnls : size of the image + * + * @return EXIT_SUCCESS if the image has been loaded, EXIT_FAILURE otherwise + **/ +int load_image( + char* name +, vector &img +, unsigned * width +, unsigned * height +, unsigned * chnls +){ + //! read input image + cout << endl << "Read input image..."; + size_t h, w, c; + float *tmp = NULL; + int ih, iw, ic; + + tmp = iio_read_image_float_split(name, &iw, &ih, &ic); + w=iw; h=ih; c=ic; + if (!tmp) + { + cout << "error :: " << name << " not found or not a correct image" << endl; + return EXIT_FAILURE; + } + cout << "done." << endl; + + //! test if image is really a color image and exclude the alpha channel + if (c > 2) + { + unsigned k = 0; + while (k < w * h && tmp[k] == tmp[w * h + k] && tmp[k] == tmp[2 * w * h + k]) + k++; + c = (k == w * h ? 1 : 3); + } + + //! Some image informations + cout << "image size :" << endl; + cout << " - width = " << w << endl; + cout << " - height = " << h << endl; + cout << " - nb of channels = " << c << endl; + + //! Initializations + *width = w; + *height = h; + *chnls = c; + img.resize(w * h * c); + for (unsigned k = 0; k < w * h * c; k++) + img[k] = tmp[k]; + + return EXIT_SUCCESS; +} + +/** + * @brief write image + * + * @param name : path+name+extension of the image + * @param img : vector which contains the image + * @param width, height, chnls : size of the image + * + * @return EXIT_SUCCESS if the image has been saved, EXIT_FAILURE otherwise + **/ +int save_image( + char* name +, std::vector &img +, const unsigned width +, const unsigned height +, const unsigned chnls +){ + //! Allocate Memory + float* tmp = new float[width * height * chnls]; + + //! Check for boundary problems + for (unsigned k = 0; k < width * height * chnls; k++) + tmp[k] = img[k]; //(img[k] > 255.0f ? 255.0f : (img[k] < 0.0f ? 0.0f : img[k])); + + iio_save_image_float_split(name, tmp, width, height, chnls); + + + //! Free Memory + delete[] tmp; + + return EXIT_SUCCESS; +} + +/** + * @brief Check if a number is a power of 2 + **/ +bool power_of_2( + const unsigned n +){ + if (n == 0) + return false; + + if (n == 1) + return true; + + if (n % 2 == 0) + return power_of_2(n / 2); + else + return false; +} + +/** + * @brief Add boundaries by symetry + * + * @param img : image to symetrize + * @param img_sym : will contain img with symetrized boundaries + * @param width, height, chnls : size of img + * @param N : size of the boundary + * + * @return none. + **/ +void symetrize( + const std::vector &img +, std::vector &img_sym +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned N +){ + //! Declaration + const unsigned w = width + 2 * N; + const unsigned h = height + 2 * N; + + if (img_sym.size() != w * h * chnls) + img_sym.resize(w * h * chnls); + + for (unsigned c = 0; c < chnls; c++) + { + unsigned dc = c * width * height; + unsigned dc_2 = c * w * h + N * w + N; + + //! Center of the image + for (unsigned i = 0; i < height; i++) + for (unsigned j = 0; j < width; j++, dc++) + img_sym[dc_2 + i * w + j] = img[dc]; + + //! Top and bottom + dc_2 = c * w * h; + for (unsigned j = 0; j < w; j++, dc_2++) + for (unsigned i = 0; i < N; i++) + { + img_sym[dc_2 + i * w] = img_sym[dc_2 + (2 * N - i - 1) * w]; + img_sym[dc_2 + (h - i - 1) * w] = img_sym[dc_2 + (h - 2 * N + i) * w]; + } + + //! Right and left + dc_2 = c * w * h; + for (unsigned i = 0; i < h; i++) + { + const unsigned di = dc_2 + i * w; + for (unsigned j = 0; j < N; j++) + { + img_sym[di + j] = img_sym[di + 2 * N - j - 1]; + img_sym[di + w - j - 1] = img_sym[di + w - 2 * N + j]; + } + } + } + + return; +} + +/** + * @brief Subdivide an image into small sub-images + * + * @param img : image to subdivide + * @param sub_images: will contain all sub_images + * @param w_table, h_table : size of sub-images contained in sub_img + * @param width, height, chnls: size of img + * @param divide: if true, sub-divides img into sub_img, else rebuild + * img from sub_images + * + * @return none. + **/ +void sub_divide( + vector &img +, vector > &sub_img +, vector &w_table +, vector &h_table +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned N +, bool divide +){ + //! Add by symetry boundaries to the img + const unsigned h_b = height + 2 * N; + const unsigned w_b = width + 2 * N; + vector img_sym; + if (divide) + symetrize(img, img_sym, width, height, chnls, N); + + //! Obtain nb of sub_images in row and column + unsigned w_small = width; + unsigned h_small = height; + unsigned n = sub_img.size(); + unsigned nw = 1; + unsigned nh = 1; + while (n > 1) + { + if (w_small > h_small) + { + w_small = (unsigned) floor((float) w_small * 0.5f); + nw *= 2; + } + else + { + h_small = (unsigned) floor((float) h_small * 0.5f); + nh *=2; + } + n /= 2; + } + + //! As the image may don't have power of 2 dimensions, it may exist a boundary + const unsigned h_bound = (nh > 1 ? height - (nh - 1) * h_small : h_small); + const unsigned w_bound = (nw > 1 ? width - (nw - 1) * w_small : w_small); + + if (divide) //! Subdivides the image in small parts + { + for (unsigned i = 0; i < nh; i++) + for (unsigned j = 0; j < nw; j++) + { + const unsigned k = i * nw + j; + const unsigned h = (i == nh - 1 ? h_bound : h_small) + 2 * N; + const unsigned w = (j == nw - 1 ? w_bound : w_small) + 2 * N; + h_table[k] = h; + w_table [k] = w; + sub_img[k].resize(w * h * chnls); + + for (unsigned c = 0; c < chnls; c++) + { + unsigned dc_2 = c * w_b * h_b + i * h_small * w_b + j * w_small; + for (unsigned p = 0; p < h; p++) + { + unsigned dq = c * w * h + p * w; + for (unsigned q = 0; q < w; q++, dq++) + sub_img[k][dq] = img_sym[dc_2 + p * w_b + q]; + } + } + } + } + else //! Reconstruction of the image + { + for (unsigned i = 0; i < nh; i++) + for (unsigned j = 0; j < nw; j++) + { + const unsigned k = i * nw + j; + const unsigned h = (i == nh - 1 ? h_bound : h_small) + 2 * N; + const unsigned w = (j == nw - 1 ? w_bound : w_small) + 2 * N; + for (unsigned c = 0; c < chnls; c++) + { + unsigned dc = c * w * h + N * w + N; + unsigned dc_2 = c * width * height + i * h_small * width + j * w_small; + for (unsigned p = 0; p < h - 2 * N; p++) + { + unsigned dq = dc + p * w; + for (unsigned q = 0; q < w - 2 * N; q++, dq++) + img[dc_2 + p * width + q] = sub_img[k][dq]; + } + } + } + } +} + +/** + * @brief Compute PSNR and RMSE between img_1 and img_2 + * + * @param img_1 : pointer to an allocated array of pixels. + * @param img_2 : pointer to an allocated array of pixels. + * @param psnr : will contain the PSNR + * @param rmse : will contain the RMSE + * + * @return EXIT_FAILURE if both images haven't the same size. + **/ +int compute_psnr( + const vector &img_1 +, const vector &img_2 +, float *psnr +, float *rmse +) +{ + if (img_1.size() != img_2.size()) + { + cout << "Can't compute PSNR & RMSE: images have different sizes: " << endl; + cout << "img_1 : " << img_1.size() << endl; + cout << "img_2 : " << img_2.size() << endl; + return EXIT_FAILURE; + } + + float tmp = 0.0f; + for (unsigned k = 0; k < img_1.size(); k++) + tmp += (img_1[k] - img_2[k]) * (img_1[k] - img_2[k]); + + (*rmse) = sqrtf(tmp / (float) img_1.size()); + (*psnr) = 20.0f * log10f(255.0f / (*rmse)); + + return EXIT_SUCCESS; +} + +/** + * @brief Compute a difference image between img_1 and img_2 + **/ +int compute_diff( + const std::vector &img_1 +, const std::vector &img_2 +, std::vector &img_diff +, const float sigma +){ + if (img_1.size() != img_2.size()) + { + cout << "Can't compute difference, img_1 and img_2 don't have the same size" << endl; + cout << "img_1 : " << img_1.size() << endl; + cout << "img_2 : " << img_2.size() << endl; + return EXIT_FAILURE; + } + + const unsigned size = img_1.size(); + + if (img_diff.size() != size) + img_diff.resize(size); + + const float s = 4.0f * sigma; + + for (unsigned k = 0; k < size; k++) + { + float value = (img_1[k] - img_2[k] + s) * 255.0f / (2.0f * s); + img_diff[k] = (value < 0.0f ? 0.0f : (value > 255.0f ? 255.0f : value)); + } + + return EXIT_SUCCESS; +} + +/** + * @brief Transform the color space of the image + * + * @param img: image to transform + * @param color_space: choice between OPP, YUV, YCbCr, RGB + * @param width, height, chnls: size of img + * @param rgb2yuv: if true, transform the color space + * from RGB to YUV, otherwise do the inverse + * + * @return EXIT_FAILURE if color_space has not expected + * type, otherwise return EXIT_SUCCESS. + **/ +int color_space_transform( + vector &img +, const unsigned color_space +, const unsigned width +, const unsigned height +, const unsigned chnls +, const bool rgb2yuv +){ + if (chnls == 1 || color_space == RGB) + return EXIT_SUCCESS; + + //! Declarations + vector tmp; + tmp.resize(chnls * width * height); + const unsigned red = 0; + const unsigned green = width * height; + const unsigned blue = width * height * 2; + + //! Transformations depending on the mode + if (color_space == YUV) + { + if (rgb2yuv) + { + #pragma omp parallel for + for (unsigned k = 0; k < width * height; k++) + { + //! Y + tmp[k + red ] = 0.299f * img[k + red] + 0.587f * img[k + green] + 0.114f * img[k + blue]; + //! U + tmp[k + green] = -0.14713f * img[k + red] - 0.28886f * img[k + green] + 0.436f * img[k + blue]; + //! V + tmp[k + blue ] = 0.615f * img[k + red] - 0.51498f * img[k + green] - 0.10001f * img[k + blue]; + } + } + else + { + #pragma omp parallel for + for (unsigned k = 0; k < width * height; k++) + { + //! Red channel + tmp[k + red ] = img[k + red] + 1.13983f * img[k + blue]; + //! Green channel + tmp[k + green] = img[k + red] - 0.39465f * img[k + green] - 0.5806f * img[k + blue]; + //! Blue channel + tmp[k + blue ] = img[k + red] + 2.03211f * img[k + green]; + } + } + } + else if (color_space == YCBCR) + { + if (rgb2yuv) + { + #pragma omp parallel for + for (unsigned k = 0; k < width * height; k++) + { + //! Y + tmp[k + red ] = 0.299f * img[k + red] + 0.587f * img[k + green] + 0.114f * img[k + blue]; + //! U + tmp[k + green] = -0.169f * img[k + red] - 0.331f * img[k + green] + 0.500f * img[k + blue]; + //! V + tmp[k + blue ] = 0.500f * img[k + red] - 0.419f * img[k + green] - 0.081f * img[k + blue]; + } + } + else + { + #pragma omp parallel for + for (unsigned k = 0; k < width * height; k++) + { + //! Red channel + tmp[k + red ] = 1.000f * img[k + red] + 0.000f * img[k + green] + 1.402f * img[k + blue]; + //! Green channel + tmp[k + green] = 1.000f * img[k + red] - 0.344f * img[k + green] - 0.714f * img[k + blue]; + //! Blue channel + tmp[k + blue ] = 1.000f * img[k + red] + 1.772f * img[k + green] + 0.000f * img[k + blue]; + } + } + } + else if (color_space == OPP) + { + if (rgb2yuv) + { + #pragma omp parallel for + for (unsigned k = 0; k < width * height; k++) + { + //! Y + tmp[k + red ] = 0.333f * img[k + red] + 0.333f * img[k + green] + 0.333f * img[k + blue]; + //! U + tmp[k + green] = 0.500f * img[k + red] + 0.000f * img[k + green] - 0.500f * img[k + blue]; + //! V + tmp[k + blue ] = 0.250f * img[k + red] - 0.500f * img[k + green] + 0.250f * img[k + blue]; + } + } + else + { + #pragma omp parallel for + for (unsigned k = 0; k < width * height; k++) + { + //! Red channel + tmp[k + red ] = 1.0f * img[k + red] + 1.0f * img[k + green] + 0.666f * img[k + blue]; + //! Green cha + tmp[k + green] = 1.0f * img[k + red] + 0.0f * img[k + green] - 1.333f * img[k + blue]; + //! Blue cha + tmp[k + blue ] = 1.0f * img[k + red] - 1.0f * img[k + green] + 0.666f * img[k + blue]; + } + } + } + else + { + cout << "Wrong type of transform. Must be OPP, YUV, or YCbCr!!" << endl; + return EXIT_FAILURE; + } + + #pragma omp parallel for + for (unsigned k = 0; k < width * height * chnls; k++) + img[k] = tmp[k]; + + return EXIT_SUCCESS; +} + +/** + * @brief Look for the closest power of 2 number + * + * @param n: number + * + * @return the closest power of 2 lower or equal to n + **/ +int closest_power_of_2( + const unsigned n +){ + unsigned r = 1; + while (r * 2 <= n) + r *= 2; + + return r; +} + +/** + * @brief Estimate sigma on each channel according to + * the choice of the color_space. + * + * @param sigma: estimated standard deviation of the noise; + * @param sigma_Y : noise on the first channel; + * @param sigma_U : (if chnls > 1) noise on the second channel; + * @param sigma_V : (if chnls > 1) noise on the third channel; + * @param chnls : number of channels of the image; + * @param color_space : choice between OPP, YUV, YCbCr. If not + * then we assume that we're still in RGB space. + * + * @return EXIT_FAILURE if color_space has not expected + * type, otherwise return EXIT_SUCCESS. + **/ +int estimate_sigma( + const float sigma +, std::vector &sigma_table +, const unsigned chnls +, const unsigned color_space +){ + if (chnls == 1) + sigma_table[0] = sigma; + else + { + if (color_space == YUV) + { + //! Y + sigma_table[0] = sqrtf(0.299f * 0.299f + 0.587f * 0.587f + 0.114f * 0.114f) * sigma; + //! U + sigma_table[1] = sqrtf(0.14713f * 0.14713f + 0.28886f * 0.28886f + 0.436f * 0.436f) * sigma; + //! V + sigma_table[2] = sqrtf(0.615f * 0.615f + 0.51498f * 0.51498f + 0.10001f * 0.10001f) * sigma; + } + else if (color_space == YCBCR) + { + //! Y + sigma_table[0] = sqrtf(0.299f * 0.299f + 0.587f * 0.587f + 0.114f * 0.114f) * sigma; + //! U + sigma_table[1] = sqrtf(0.169f * 0.169f + 0.331f * 0.331f + 0.500f * 0.500f) * sigma; + //! V + sigma_table[2] = sqrtf(0.500f * 0.500f + 0.419f * 0.419f + 0.081f * 0.081f) * sigma; + } + else if (color_space == OPP) + { + //! Y + sigma_table[0] = sqrtf(0.333f * 0.333f + 0.333f * 0.333f + 0.333f * 0.333f) * sigma; + //! U + sigma_table[1] = sqrtf(0.5f * 0.5f + 0.0f * 0.0f + 0.5f * 0.5f) * sigma; + //! V + sigma_table[2] = sqrtf(0.25f * 0.25f + 0.5f * 0.5f + 0.25f * 0.25f) * sigma; + } + else if (color_space == RGB) + { + //! Y + sigma_table[0] = sigma; + //! U + sigma_table[1] = sigma; + //! V + sigma_table[2] = sigma; + } + else + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/** + * @brief Initialize a set of indices. + * + * @param ind_set: will contain the set of indices; + * @param max_size: indices can't go over this size; + * @param N : boundary; + * @param step: step between two indices. + * + * @return none. + **/ +void ind_initialize( + vector &ind_set +, const unsigned max_size +, const unsigned N +, const unsigned step +){ + ind_set.clear(); + unsigned ind = N; + while (ind < max_size - N) + { + ind_set.push_back(ind); + ind += step; + } + if (ind_set.back() < max_size - N - 1) + ind_set.push_back(max_size - N - 1); +} + +/** + * @brief For convenience. Estimate the size of the ind_set vector built + * with the function ind_initialize(). + * + * @return size of ind_set vector built in ind_initialize(). + **/ +unsigned ind_size( + const unsigned max_size +, const unsigned N +, const unsigned step +){ + unsigned ind = N; + unsigned k = 0; + while (ind < max_size - N) + { + k++; + ind += step; + } + if (ind - step < max_size - N - 1) + k++; + + return k; +} + +/** + * @brief Initialize a 2D fftwf_plan with some parameters + * + * @param plan: fftwf_plan to allocate; + * @param N: size of the patch to apply the 2D transform; + * @param kind: forward or backward; + * @param nb: number of 2D transform which will be processed. + * + * @return none. + **/ +void allocate_plan_2d( + fftwf_plan* plan +, const unsigned N +, const fftwf_r2r_kind kind +, const unsigned nb +){ + int nb_table[2] = {N, N}; + int nembed[2] = {N, N}; + fftwf_r2r_kind kind_table[2] = {kind, kind}; + + float* vec = (float*) fftwf_malloc(N * N * nb * sizeof(float)); + (*plan) = fftwf_plan_many_r2r(2, nb_table, nb, vec, nembed, 1, N * N, vec, + nembed, 1, N * N, kind_table, FFTW_ESTIMATE); + + fftwf_free(vec); +} + +/** + * @brief Initialize a 1D fftwf_plan with some parameters + * + * @param plan: fftwf_plan to allocate; + * @param N: size of the vector to apply the 1D transform; + * @param kind: forward or backward; + * @param nb: number of 1D transform which will be processed. + * + * @return none. + **/ +void allocate_plan_1d( + fftwf_plan* plan +, const unsigned N +, const fftwf_r2r_kind kind +, const unsigned nb +){ + int nb_table[1] = {N}; + int nembed[1] = {N * nb}; + fftwf_r2r_kind kind_table[1] = {kind}; + + float* vec = (float*) fftwf_malloc(N * nb * sizeof(float)); + (*plan) = fftwf_plan_many_r2r(1, nb_table, nb, vec, nembed, 1, N, vec, + nembed, 1, N, kind_table, FFTW_ESTIMATE); + fftwf_free(vec); +} + +/** + * @brief tabulated values of log2(N), where N = 2 ^ n. + * + * @param N : must be a power of 2 smaller than 64 + * + * @return n = log2(N) + **/ +unsigned ind_log2( + const unsigned N +){ + return (N == 1 ? 0 : + (N == 2 ? 1 : + (N == 4 ? 2 : + (N == 8 ? 3 : + (N == 16 ? 4 : + (N == 32 ? 5 : 6) ) ) ) ) ); +} + +/** + * @brief tabulated values of log2(N), where N = 2 ^ n. + * + * @param N : must be a power of 2 smaller than 64 + * + * @return n = 2 ^ N + **/ +unsigned ind_pow2( + const unsigned N +){ + return (N == 0 ? 1 : + (N == 1 ? 2 : + (N == 2 ? 4 : + (N == 3 ? 8 : + (N == 4 ? 16 : + (N == 5 ? 32 : 64) ) ) ) ) ); +} diff --git a/denoise/bm3d/utilities.h b/denoise/bm3d/utilities.h new file mode 100644 index 0000000..10d583b --- /dev/null +++ b/denoise/bm3d/utilities.h @@ -0,0 +1,134 @@ +#ifndef UTILITIES_H_INCLUDED +#define UTILITIES_H_INCLUDED + +#include +#include + +//! Read image and check number of channels +int load_image( + char* name +, std::vector &img +, unsigned * width +, unsigned * height +, unsigned * chnls +); + +//! Write image +int save_image( + char* name +, std::vector &img +, const unsigned width +, const unsigned height +, const unsigned chnls +); + +//! Check if a number is a power of 2 +bool power_of_2( + const unsigned n +); + +//! Add boundaries by symetry +void symetrize( + const std::vector &img +, std::vector &img_sym +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned N +); + +//! Subdivide an image into small sub-images +void sub_divide( + std::vector &img +, std::vector > &sub_img +, std::vector &w_table +, std::vector &h_table +, const unsigned width +, const unsigned height +, const unsigned chnls +, const unsigned N +, bool divide +); + +//! Compute the PSNR and RMSE between img_1 and img_2 +int compute_psnr( + const std::vector &img_1 +, const std::vector &img_2 +, float *psnr +, float *rmse +); + +//! Compute the difference images between img_1 and img_2 +int compute_diff( + const std::vector &img_1 +, const std::vector &img_2 +, std::vector &img_diff +, const float sigma +); + +//! Transform the color space of the image +int color_space_transform( + std::vector &img +, const unsigned color_space +, const unsigned width +, const unsigned height +, const unsigned chnls +, const bool rgb2yuv +); + +//! Look for the closest power of 2 number +int closest_power_of_2( + const unsigned n +); + +//! Estimate sigma on each channel according to the choice of the color_space +int estimate_sigma( + const float sigma +, std::vector &sigma_table +, const unsigned chnls +, const unsigned color_space +); + +//! Initialize a set of indices +void ind_initialize( + std::vector &ind_set +, const unsigned max_size +, const unsigned N +, const unsigned step +); + +//! For convenience +unsigned ind_size( + const unsigned max_size +, const unsigned N +, const unsigned step +); + +//! Initialize a 2D fftwf_plan with some parameters +void allocate_plan_2d( + fftwf_plan* plan +, const unsigned N +, const fftwf_r2r_kind kind +, const unsigned nb +); + +//! Initialize a 1D fftwf_plan with some parameters +void allocate_plan_1d( + fftwf_plan* plan +, const unsigned N +, const fftwf_r2r_kind kind +, const unsigned nb +); + +//! Tabulated values of log2(2^n) +unsigned ind_log2( + const unsigned N +); + +//! Tabulated values of 2^N +unsigned ind_pow2( + const unsigned N +); + + +#endif // UTILITIES_H_INCLUDED diff --git a/denoise/cartoontexture/Makefile b/denoise/cartoontexture/Makefile new file mode 100644 index 0000000..dd443e9 --- /dev/null +++ b/denoise/cartoontexture/Makefile @@ -0,0 +1,64 @@ +# Copyright 2010 Nicolas Limare +# Jose-Luis Lisani +# +# Copying and distribution of this file, with or without +# modification, are permitted in any medium without royalty provided +# the copyright notice and this notice are preserved. This file is +# offered as-is, without any warranty. + + +## +# Set this line to compile with OpenMP multithreading. Comment the +# line to disable OpenMP. +OPENMP=-fopenmp + +# C++ source code +CXXSRC = main.cpp cartoon.cpp + +# all source code +SRC = $(CXXSRC) + +# C++ objects +CXXOBJ = $(CXXSRC:.cpp=.o) +# all objects +OBJ = $(CXXOBJ) +# binary target +BIN = cartoontexture + +default : $(BIN) + + +# C optimization flags +COPT = -O3 -ftree-vectorize -funroll-loops \ + -fomit-frame-pointer -fno-tree-pre -falign-loops -ffast-math + +# C++ optimization flags +CXXOPT = $(COPT) + +# C compilation flags +CFLAGS = $(COPT) -Wall -Wextra -std=c99 \ + -Wno-write-strings +# C++ compilation flags +CXXFLAGS = $(CXXOPT) -Wall -Wextra \ + -Wno-write-strings -Wno-deprecated -ansi \ + -Weffc++ -pedantic $(OPENMP) +# link flags +LDFLAGS = -liio $(OPENMP) + + +# partial compilation of C source code +%.o: %.c %.h + $(CC) -I/opt/local/include/ -I/usr/local/include/ -c -o $@ $< $(CFLAGS) +# partial compilation of C++ source code +%.o: %.cpp %.h + $(CXX) -I/opt/local/include/ -I/usr/local/include/ -c -o $@ $< $(CXXFLAGS) + +# link all the object code +$(BIN): $(OBJ) $(LIBDEPS) + $(CXX) -I/opt/local/include/ -I/usr/local/include/ -L/opt/local/lib/ -L/usr/local/lib/ -o $@ $(OBJ) $(LDFLAGS) + +clean: + rm -f *.o core + + + diff --git a/denoise/cartoontexture/README.txt b/denoise/cartoontexture/README.txt new file mode 100644 index 0000000..0d54e55 --- /dev/null +++ b/denoise/cartoontexture/README.txt @@ -0,0 +1,50 @@ +% Cartoon+texture decomposition by the TV-L1 model + +# ABOUT + +* Author : Vincent Le Guen +* Copyright : (C) 2013 IPOL Image Processing On Line http://www.ipol.im/ +* Licence : GPL v3+, see GPLv3.txt + +# OVERVIEW + +This source code provides an implementation of the TV-L1 +cartoon+texture decomposition, as described in IPOL. + +This program reads and writes PNG images, but can be easily +adapted to any other file format. + +Only 8bit RGB PNG images are handled. Other TIFF files are implicitly +converted to 8bit color RGB. + +# REQUIREMENTS + +The code is written in ANSI C and C++, and should compile on any +system with an ANSI C/C++ compiler. + +The libpng header and libraries are required on the system for +compilation and execution. The program is parallelized with OPENMP. + +# COMPILATION + +Simply use the provided makefile, with the command `make`. + + +# USAGE + +`cartoonTexture` takes 4 parameters: `cartoonTexture in.png lambda cartoon.png texture.png` +* `lambda` : the regularization parameter +* `in.png` : input image +* `cartoon.png` : output image without the textures +* `texture.png` : textures (out - in, bounded to [-20, 20]) + +# ABOUT THIS FILE + +Copyright 2013 IPOL Image Processing On Line http://www.ipol.im/ +Author: Vincent Le Guen + +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without any warranty. + diff --git a/denoise/cartoontexture/VERSION b/denoise/cartoontexture/VERSION new file mode 100644 index 0000000..0808d7b --- /dev/null +++ b/denoise/cartoontexture/VERSION @@ -0,0 +1 @@ +0.20180203 diff --git a/denoise/cartoontexture/cartoon.cpp b/denoise/cartoontexture/cartoon.cpp new file mode 100644 index 0000000..cf5c44e --- /dev/null +++ b/denoise/cartoontexture/cartoon.cpp @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2013, Vincent Le Guen + * All rights reserved. + * + * + * + * License: + * + * This program is provided for scientific and educational only: + * you can use and/or modify it for these purposes, but you are + * not allowed to redistribute this work or derivative works in + * source or executable form. A license must be obtained from the + * patent right holders for any other use. + * + * + */ + +#include "cartoon.h" +using namespace std; + +/** + * @file cartoon.cpp + * @brief Cartoon + texture functions + * + * + * + * @author Vincent Le Guen + */ + + +/** + * \brief Main function to perform the TV-L1 cartoon texture decomposition + * + * + * @param[in] image : input image + * @param[in] nb_iter_max : number of iterations of the algorithm + * @param[in] tau, sigma, lambda, theta : parameters of the algorithm + * @param[in] Width, Height : size of the image + * @param[out] cartoon image + */ +float *Chambolle_TV_L1(float* input_image, int nb_iter_max, float tau, float sigma, float lambda, float theta, int Width, int Height) { + // Initialization + float *u = new float[Width*Height]; + float *u_old = new float[Width*Height]; + for(int k=0; k < Width*Height; k++) u[k] = input_image[k]; + + float *p1 = new float[Width*Height]; + float *p2 = new float[Width*Height]; + + float *div = new float[Width*Height]; + float *im1 = new float[Width*Height]; + float *im2 = new float[Width*Height]; + + float *GradX = new float[Width*Height]; + float *GradY = new float[Width*Height]; + + float *temp1 = new float[Width*Height]; + float *temp2 = new float[Width*Height]; + float E = numeric_limits::infinity(), E_old; + int it; + + for(it=1 ; it <= nb_iter_max ; it++) { + + for(int k=0; k < Width*Height ; k++) u_old[k] = u[k]; + // First step : apply ProximalF + ComputeImageGradient(u, GradX, GradY, Width, Height); + imageAddition(im1,p1,GradX,1,sigma,Width,Height); // im1 <- p1+sigma*GradX + imageAddition(im2,p2,GradY,1,sigma,Width,Height); // im2 <- p2+sigma*GradY + ProximalF_star(im1, im2, p1, p2, Width, Height); + + // Second step : apply ProximalG + ComputeDivergence(div, p1, p2, Width,Height); // div <- divergence(p1,p2) + imageAddition(im1,u,div,1,tau,Width,Height); // im1 <- u + tau*div + ProximalG(u, im1, input_image, lambda, tau, Width,Height); + + // Third step : u <- u + theta*(u-u_old) + imageAddition(u,u,u_old,1+theta,-theta,Width,Height); + + // Energy calculation and stopping criterion + if(it%10==0) { + E_old = E; + E = 0; + ComputeImageGradient(u, GradX, GradY, Width, Height); + for(int k=0; k < Width*Height ; k++) E += lambda * fabs(u[k]-input_image[k]) + sqrtf(GradX[k]*GradX[k] + GradY[k]*GradY[k]); + E = E/(Width*Height); // normalized energy + if(fabs(E-E_old) < 0.001) break; + //cout << "iteration " << it << " E = " << E << endl; + } + } + + //cout << "iterations : " << it << endl; + delete[] div; + delete[] im1; + delete[] im2; + delete[] p1; + delete[] p2; + delete[] temp1; + delete[] temp2; + delete[] u_old; + delete[] GradX; + delete[] GradY; + return u; +} + +/** + * \brief Compute the gradient of an image + * + * + * @param[in] image : input image + * @param[in] Width, Height : size of the image + * @param[out] GradX, GradY : coordinates X and Y of the gradient + * + * This function computes the discrete gradient of an image. The output gradient coordinates + * gradX and gradY are given by reference. + */ +void ComputeImageGradient(float*& image, float*& GradX, float*& GradY,int Width, int Height) { + if (!image) { + printf("Null input image (ComputeImageGradient)"); + exit(-1); + } +#ifdef _OPENMP +#pragma omp parallel for +#endif + // Boundary conditions + for(int j=1 ; j<= Width ; j++) GradX[(Height-1)*Width+j-1] = 0; + for(int i=1 ; i<= Height ; i++) GradY[(i-1)*Width+ Height-1] = 0; + + for (int i = 1; i < Height ; i++) { + for (int j = 1; j < Width ; j++) { + GradX[(i-1)*Width + j-1] = image[i * Width + j-1] - image[(i-1) * Width + j-1]; + GradY[(i-1)*Width + j-1] = image[(i-1) * Width + j] - image[(i-1) * Width + j-1]; + } + } +} + +/** + * \brief Compute the discrete version of the divergence of a vector field + * + * + * @param[in] p1, p2 : the 2 input coordinates of the vector field + * @param[in] Width, Height : size of the image + * @param[out] out_image : the output image given by reference + * + */ +void ComputeDivergence(float*& out_image, float*& p1, float*& p2, int Width, int Height) { + if ((!p1)||(!p2)) { + printf("Null input (ComputeDivergence)"); + exit(-1); + } + float term_p1 = 0, term_p2 = 0; + +#ifdef _OPENMP +#pragma omp parallel for private(term_p1) private(term_p2) +#endif + for (int i = 1; i <= Height ; i++) { + for (int j = 1; j <= Width ; j++) { + + if(i==1) term_p1 = p1[(i-1)*Width+j-1]; + else if(i==Height) term_p1 = - p1[(i-2)*Width+j-1]; + else term_p1 = p1[(i-1)*Width+j-1] - p1[(i-2)*Width+j-1]; + + if(j==1) term_p2 = p2[(i-1)*Width+j-1]; + else if(j==Width) term_p2 = - p1[(i-1)*Width+j-2]; + else term_p2 = p2[(i-1)*Width+j-1] - p2[(i-1)*Width+j-2]; + + out_image[(i-1)*Width+j-1] = term_p1 + term_p2; + } + } +} + +/** + * \brief Compute the ProximalG operator + * + * + * @param[in] u : input image for the proximal operator + * @param[in] g : the original input image of the cartoon texture algorithm + * @param[in] lambda, tau : parameters + * @param[in] Width, Height : size of the image + * @param[out] out_image : the output image given by reference + * + */ +void ProximalG(float*& image_out, float*& u, float* g, float lambda, float tau, int Width, int Height) { +#ifdef _OPENMP +#pragma omp parallel for +#endif + for (int i = 1; i <= Height ; i++) { + for (int j = 1; j <= Width ; j++) { + if(u[(i-1)*Width+j-1] - g[(i-1)*Width+j-1] > tau*lambda) image_out[(i-1)*Width+j-1] = u[(i-1)*Width+j-1] - tau*lambda; + if(u[(i-1)*Width+j-1] - g[(i-1)*Width+j-1] < - tau*lambda) image_out[(i-1)*Width+j-1] = u[(i-1)*Width+j-1] + tau*lambda; + if(abs(u[(i-1)*Width+j-1] - g[(i-1)*Width+j-1]) <= tau*lambda) image_out[(i-1)*Width+j-1] = g[(i-1)*Width+j-1]; + } + } +} + +/** + * \brief Compute the ProximalF operator + * + * + * @param[in] p1,p2 : input vector field for the proximal operator + * @param[in] Width, Height : size of the image + * @param[out] out1,out2 : output vector field + * + */ +void ProximalF_star(float*& p1, float*& p2, float*& out1, float*& out2, int Width, int Height) { + float norm_p = 0; + +#ifdef _OPENMP +#pragma omp parallel for private(norm_p) +#endif + for (int i = 1; i <= Height ; i++) { + for (int j = 1; j <= Width ; j++) { + norm_p = sqrtf ( p1[(i-1)*Width+j-1]*p1[(i-1)*Width+j-1] + p2[(i-1)*Width+j-1]*p2[(i-1)*Width+j-1] ); + out1[(i-1)*Width+j-1] = p1[(i-1)*Width+j-1] / fmax(1,norm_p); + out2[(i-1)*Width+j-1] = p2[(i-1)*Width+j-1] / fmax(1,norm_p); + } + } +} + +/** + * + *\brief Compute the image addition : image <- lambda1*a + lambda2*b + * @param[in] a,b : input images + * @param[in] lambda1, lambda2: parameters + * @param[in] Width, Height : size of the image + * @param[out] out_image : the output image given by reference + * + */ +void imageAddition(float* &out_image, float*& a, float*& b, float lambda1, float lambda2, int Width, int Height) { +float value; +#ifdef _OPENMP +#pragma omp parallel for private(value) +#endif + for (int i = 1; i <= Height ; i++) { + for (int j = 1; j <= Width ; j++) { + value = lambda1*a[(i-1)*Width+j-1] + lambda2*b[(i-1)*Width+j-1]; + out_image[(i-1)*Width+j-1] = value; + + } + } +} diff --git a/denoise/cartoontexture/cartoon.h b/denoise/cartoontexture/cartoon.h new file mode 100644 index 0000000..dfc4e27 --- /dev/null +++ b/denoise/cartoontexture/cartoon.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013, Vincent Le Guen + * All rights reserved. + * + * + * + * License: + * + * This program is provided for scientific and educational only: + * you can use and/or modify it for these purposes, but you are + * not allowed to redistribute this work or derivative works in + * source or executable form. A license must be obtained from the + * patent right holders for any other use. + * + * + */ + + +/** + * @file cartoon.cpp + * @brief Cartoon + texture functions + * + * + * + * @author Vincent Le Guen + */ + +#ifndef CARTOON_H +#define CARTOON_H + +#include +#include +#include +#include +#include +#include +#include +#ifdef _OPENMP +#include +#endif + +float * Chambolle_TV_L1(float * input_image, int nb_iter, float tau, float sigma, float lambda, float theta, int Width, int Height); +void ComputeImageGradient(float *&image, float*& GradX, float*& GradY, int Width, int Height); +void ComputeDivergence(float*& out_image, float*& p1, float*& p2, int Width, int Height); +void ProximalG(float*& image_out, float*& u, float* g, float lambda, float tau, int Width, int Height); +void ProximalF_star(float *&p1, float *&p2, float*& out1, float*& out2, int Width, int Height); +void imageAddition(float* &out_image, float*& a, float*& b, float lambda1, float lambda2, int Width, int Height); + +#endif diff --git a/denoise/cartoontexture/main.cpp b/denoise/cartoontexture/main.cpp new file mode 100644 index 0000000..2c0f584 --- /dev/null +++ b/denoise/cartoontexture/main.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2013, Vincent Le Guen + * All rights reserved. + * + * + * + * License: + * + * This program is provided for scientific and educational only: + * you can use and/or modify it for these purposes, but you are + * not allowed to redistribute this work or derivative works in + * source or executable form. A license must be obtained from the + * patent right holders for any other use. + * + * + */ + +/** + * @mainpage Cartoon + Texture decomposition + * + * README.txt: + * @verbinclude README.txt + */ + + +/** + * @file main.cpp + * @brief Main executable file + * + * + * + * @author Vincent Le Guen + */ + +#include "cartoon.h" + +extern "C" { +#include +} + +using namespace std; + +int main(int argc, char **argv) { + + if (argc < 5) { + printf("usage: cartoonTexture image lambda cartoon texture \n"); + return EXIT_FAILURE; + } + + int Width, Height, nb_channels; + float *input_image = iio_read_image_float_split(argv[1], &Width, &Height, &nb_channels); + + if (nb_channels == 2) { nb_channels = 1; } // we do not use the alpha channel + if (nb_channels > 3) { nb_channels = 3; } // we do not use the alpha channel + + int size_image = Width * Height; + + // parameters: input + float lambda = atof(argv[2]); + + // build cartoon + float *cartoon; + + // We fix the parameters + int nb_iter_max = 1000; + float tau = 0.35; + float sigma = 0.35; + float theta = 1; + + float initial_time = clock(); + + // Perform the TV-L1 algorithm on the 3 channels for a color image + float *cartoon1; + float *cartoon2; + float *cartoon3; + + if (nb_channels == 1) { + cartoon = Chambolle_TV_L1(input_image, nb_iter_max, tau, sigma,lambda, theta, Width, Height); + } + else { + cartoon = new float[nb_channels * size_image]; + cartoon1 = Chambolle_TV_L1(input_image, nb_iter_max, tau, sigma,lambda, theta, Width, Height); + for(int j=0; j < size_image; j++) cartoon[j] = cartoon1[j]; + + cartoon2 = Chambolle_TV_L1(&input_image[size_image], nb_iter_max, tau, sigma,lambda, theta, Width, Height); + for(int j=0; j < size_image; j++) cartoon[size_image+j] = cartoon2[j]; + + cartoon3 = Chambolle_TV_L1(&input_image[2*size_image], nb_iter_max, tau, sigma,lambda, theta, Width, Height); + for(int j=0; j < size_image; j++) cartoon[2*size_image+j] = cartoon3[j]; + } + + float final_time = clock(); + float cpu_time = (final_time - initial_time) / CLOCKS_PER_SEC; + cout << "time: " << cpu_time << "s lambda = " << lambda << endl; + + // BV norm + float * GradX = new float[Width*Height]; + float * GradY = new float[Width*Height]; + + float BV_norm = 0; + if(nb_channels == 1) { // graylevel image + ComputeImageGradient(cartoon, GradX, GradY, Width, Height); + for(int i=0;i +.TP +Author: +Gabriele Facciolo +.TP +Latest version available at: +https://github.com/npd/cartoon_texture diff --git a/denoise/da3d/CMakeLists.txt b/denoise/da3d/CMakeLists.txt new file mode 100644 index 0000000..49d69cf --- /dev/null +++ b/denoise/da3d/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required (VERSION 2.8) +project (da3d) + +# The build type "Release" adds some optimizations +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE "Release") +endif () + +# GCC on MacOs needs this option to use the clang assembler +if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (APPLE)) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wa,-q") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-q") +endif () + +# Optimize to the current CPU and enable warnings +if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR + (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR + (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -Wall -Wextra") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra") +endif () + +# Enable C99 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") +else () + set (CMAKE_C_STANDARD 99) +endif () + +# Enable C++11 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") +else () + set (CMAKE_CXX_STANDARD 11) +endif () + +# Enable OpenMP +find_package (OpenMP) +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + +# Link LibFFTW +find_path (FFTW_INCLUDE_DIR fftw3.h) +find_library (FFTWF_LIBRARIES NAMES fftw3f) +include_directories (PUBLIC ${FFTW_INCLUDE_DIR}) +link_libraries (${FFTWF_LIBRARIES}) +if (NOT FFTW_INCLUDE_DIR) + message (FATAL_ERROR "FFTW3 not found.") +endif () +find_path (IIO_INCLUDE_DIR iio.h) +find_library (IIO_LIBRARIES NAMES iio) +include_directories (SYSTEM ${iio_INCLUDE_DIR}) +link_libraries (${IIO_LIBRARIES}) +if (NOT IIO_INCLUDE_DIR) + message (FATAL_ERROR "IIO not found.") +endif () + +add_executable(da3d DA3D.cpp DA3D.hpp DftPatch.hpp Image.hpp WeightMap.cpp WeightMap.hpp Utils.cpp Utils.hpp DemoUtils.cpp main.cpp) diff --git a/denoise/da3d/DA3D.cpp b/denoise/da3d/DA3D.cpp new file mode 100644 index 0000000..8433614 --- /dev/null +++ b/denoise/da3d/DA3D.cpp @@ -0,0 +1,350 @@ +/* + * DA3D.cpp + * + * Created on: 24/mar/2015 + * Author: nicola pierazzo + */ + +#include +#include +#include +#include +#include +#include "Image.hpp" +#include "DA3D.hpp" +#include "WeightMap.hpp" +#include "Utils.hpp" +#include "DftPatch.hpp" + +#ifdef _OPENMP +#include +#endif // OPENMP + +using std::max; +using std::min; +using std::vector; +using std::pair; +using std::tie; +using std::move; +using std::sqrt; +using std::log2; +using std::floor; +using std::modf; +using std::abs; +using std::accumulate; +using std::norm; +using utils::fastexp; +using utils::NextPowerOf2; +using utils::ComputeTiling; +using utils::SplitTiles; +using utils::MergeTiles; + +namespace da3d { + +namespace { + +Image ColorTransform(Image&& src) { + Image img = move(src); + if (img.channels() == 3) { + for (int row = 0; row < img.rows(); ++row) { + for (int col = 0; col < img.columns(); ++col) { + float r, g, b; + r = img.val(col, row, 0); + g = img.val(col, row, 1); + b = img.val(col, row, 2); + img.val(col, row, 0) = (r + g + b) / sqrt(3.f); + img.val(col, row, 1) = (r - b) / sqrt(2.f); + img.val(col, row, 2) = (r - 2 * g + b) / sqrt(6.f); + } + } + } + return img; +} + +Image ColorTransformInverse(Image&& src) { + Image img = move(src); + if (img.channels() == 3) { + for (int row = 0; row < img.rows(); ++row) { + for (int col = 0; col < img.columns(); ++col) { + float y, u, v; + y = img.val(col, row, 0); + u = img.val(col, row, 1); + v = img.val(col, row, 2); + img.val(col, row, 0) = (sqrt(2.f) * y + sqrt(3.f) * u + v) / sqrt(6.f); + img.val(col, row, 1) = (y - sqrt(2.f) * v) / sqrt(3.f); + img.val(col, row, 2) = (sqrt(2.f) * y - sqrt(3.f) * u + v) / sqrt(6.f); + } + } + } + return img; +} + +void ExtractPatch(const Image &src, int pr, int pc, Image *dst) { + // src is padded, so (pr, pc) becomes the upper left pixel + int i = 0, j = (pr * src.columns() + pc) * src.channels(); + for (int row = 0; row < dst->rows(); ++row) { + for (int el = 0; el < dst->columns() * dst->channels(); ++el) { + dst->val(i) = src.val(j); + ++i; + ++j; + } + j += (src.columns() - dst->columns()) * src.channels(); + } +} + +void BilateralWeight(const Image &g, Image *k, int r, float gamma_r_sigma2, + float sigma_s2) { + for (int row = 0; row < g.rows(); ++row) { + for (int col = 0; col < g.columns(); ++col) { + float x = 0.f; + for (int chan = 0; chan < g.channels(); ++chan) { + float y = g.val(col, row, chan) - g.val(r, r, chan); + x += y * y; + } + x /= gamma_r_sigma2; + x += ((row - r) * (row - r) + (col - r) * (col - r)) / (2 * sigma_s2); + k->val(col, row) = utils::fastexp(-x); + } + } +} + +/* void ComputeRegressionPlaneIRLS(const Image &y, + const Image &g, + const Image &k, + int r, + vector> *reg_plane, + int iterations = 2) { + constexpr float delta = 0.0001f; + for (pair &v : *reg_plane) v = {0.f, 0.f}; + for (int chan = 0; chan < y.channels(); ++chan) { + float x1 = 0.f, x2 = 0.f; + for (int t = 0; t < iterations; ++t) { + float a = 0.f, b = 0.f, c = 0.f, d = 0.f, e = 0.f; + float central = g.val(r, r, chan); + for (int row = 0; row < y.rows(); ++row) { + for (int col = 0; col < y.columns(); ++col) { + int cc = col - r, rr = row - r; + float w = k.val(col, row) + / max(delta, (y.val(col, row, chan) - central - x1 * rr - x2 * cc)); + a += rr * rr * w; + b += rr * cc * w; + c += cc * cc * w; + d += rr * (y.val(col, row, chan) - central) * w; + e += cc * (y.val(col, row, chan) - central) * w; + } + } + float det = a * c - b * b; + if (abs(det) < delta) break; + + // Solves the system + // |a b| |x1| |d| + // | | | | = | | + // |b c| |x2| |e| + x1 = (c * d - b * e) / det; + x2 = (a * e - b * d) / det; + } + (*reg_plane)[chan] = {x1, x2}; + } +} */ + +void ComputeRegressionPlane(const Image &y, const Image &g, const Image &k, + int r, vector> *reg_plane) { + constexpr float epsilon = 0.0001f; + float a = 0.f, b = 0.f, c = 0.f; + for (int row = 0; row < y.rows(); ++row) { + for (int col = 0; col < y.columns(); ++col) { + a += (row - r) * (row - r) * k.val(col, row); + b += (row - r) * (col - r) * k.val(col, row); + c += (col - r) * (col - r) * k.val(col, row); + } + } + float det = a * c - b * b; + if (abs(det) < epsilon) { + for (int chan = 0; chan < y.channels(); ++chan) { + (*reg_plane)[chan] = {0.f, 0.f}; + } + } else { + for (int chan = 0; chan < y.channels(); ++chan) { + float d = 0.f, e = 0.f; + float central = g.val(r, r, chan); + for (int row = 0; row < y.rows(); ++row) { + for (int col = 0; col < y.columns(); ++col) { + d += (row - r) * (y.val(col, row, chan) - central) * k.val(col, row); + e += (col - r) * (y.val(col, row, chan) - central) * k.val(col, row); + } + } + // Solves the system + // |a b| |x1| |d| + // | | | | = | | + // |b c| |x2| |e| + (*reg_plane)[chan] = {(c * d - b * e) / det, (a * e - b * d) / det}; + } + } +} + +void SubtractPlane(int r, vector> reg_plane, Image *y) { + for (int row = 0; row < y->rows(); ++row) { + for (int col = 0; col < y->columns(); ++col) { + for (int chan = 0; chan < y->channels(); ++chan) { + y->val(col, row, chan) -= reg_plane[chan].first * (row - r) + + reg_plane[chan].second * (col - r); + } + } + } +} + +void ModifyPatch(const Image &patch, const Image &k, DftPatch *modified, + float *average = nullptr) { + // compute the total weight of the mask + float weight = accumulate(k.begin(), k.end(), 0.f); + + for (int chan = 0; chan < patch.channels(); ++chan) { + float avg = 0.f; + for (int row = 0; row < patch.rows(); ++row) { + for (int col = 0; col < patch.columns(); ++col) { + avg += k.val(col, row) * patch.val(col, row, chan); + } + } + avg /= weight; + for (int row = 0; row < patch.rows(); ++row) { + for (int col = 0; col < patch.columns(); ++col) { + modified->space(col, row, chan) = + k.val(col, row) * patch.val(col, row, chan) + + (1.f - k.val(col, row)) * avg; + } + } + if (average) average[chan] = avg; + } +} + +pair DA3D_block(const Image &noisy, const Image &guide, + float sigma, int r, float sigma_s, + float gamma_r, float threshold) { + // useful values + const int s = utils::NextPowerOf2(2 * r + 1); + const float sigma2 = sigma * sigma; + const float gamma_r_sigma2 = gamma_r * sigma2; + const float sigma_s2 = sigma_s * sigma_s; + + // regression parameters + const float gamma_rr_sigma2 = gamma_r_sigma2 * 10.f; + const float sigma_sr2 = sigma_s2 * 2.f; + + // declaration of internal variables + Image y(s, s, guide.channels()); + Image g(s, s, guide.channels()); + Image k_reg(s, s); + Image k(s, s); + DftPatch y_m(s, s, guide.channels()); + DftPatch g_m(s, s, guide.channels()); + int pr, pc; // coordinates of the central pixel + vector> reg_plane(guide.channels()); // parameters of the regression plane + float yt[guide.channels()]; // weighted average of the patch + WeightMap agg_weights(guide.rows() - s + 1, guide.columns() - s + 1); // line 1 + + Image output(guide.rows(), guide.columns(), guide.channels()); + Image weights(guide.rows(), guide.columns()); + + // main loop + while (agg_weights.Minimum() < threshold) { // line 4 + tie(pr, pc) = agg_weights.FindMinimum(); // line 5 + ExtractPatch(noisy, pr, pc, &y); // line 6 + ExtractPatch(guide, pr, pc, &g); // line 7 + BilateralWeight(g, &k_reg, r, gamma_rr_sigma2, sigma_sr2); // line 8 + ComputeRegressionPlane(y, g, k_reg, r, ®_plane); // line 9 + SubtractPlane(r, reg_plane, &y); // line 10 + SubtractPlane(r, reg_plane, &g); // line 11 + BilateralWeight(g, &k, r, gamma_r_sigma2, sigma_s2); // line 12 + if (accumulate(k.begin(), k.end(), 0.f) < 10.f) { // line 13 + for (float& v : k) v *= v; // Square the weights // line 14 + for (int row = 0; row < s; ++row) { // line 15-16 + for (int col = 0; col < s; ++col) { + for (int chan = 0; chan < output.channels(); ++chan) { + output.val(col + pc, row + pr, chan) += + (g.val(col, row, chan) + reg_plane[chan].first * (row - r) + + reg_plane[chan].second * (col - r)) * k.val(col, row); + } + weights.val(col + pc, row + pr) += k.val(col, row); + } + } + } else { + ModifyPatch(y, k, &y_m, yt); // line 18 + ModifyPatch(g, k, &g_m); // line 19 + y_m.ToFreq(); // line 20 + g_m.ToFreq(); // line 21 + float sigma_f2 = 0.f; + for (int row = 0; row < s; ++row) { + for (int col = 0; col < s; ++col) { + sigma_f2 += k.val(col, row) * k.val(col, row); + } + } + sigma_f2 *= sigma2; // line 22 + for (int row = 0; row < y_m.frows(); ++row) { + for (int col = 0; col < y_m.fcolumns(); ++col) { + for (int chan = 0; chan < y_m.channels(); ++chan) { + if (row || col) { + float x = norm(g_m.freq(col, row, chan)) / sigma_f2; + float K; + K = utils::fastexp(-.8f / x); // line 23 + y_m.freq(col, row, chan) *= K; + } + } + } + } + y_m.ToSpace(); // line 24 + + // lines 25,26,30 + // col and row are the "internal" indexes (with respect to the patch). + for (int row = 0; row < s; ++row) { + for (int col = 0; col < s; ++col) { + for (int chan = 0; chan < output.channels(); ++chan) { + float pij = (row - r) * reg_plane[chan].first + + (col - r) * reg_plane[chan].second; + float kij = k.val(col, row); + output.val(col + pc, row + pr, chan) += + (y_m.space(col, row, chan) - (1.f - kij) * yt[chan] + + pij * kij) * kij; + } + k.val(col, row) *= k.val(col, row); // line 27 + weights.val(col + pc, row + pr) += k.val(col, row); + } + } + } + agg_weights.IncreaseWeights(k, pr - r, pc - r); // line 29 + } + + return {move(output), move(weights)}; +} + +} // namespace + +Image DA3D(const Image &noisy, const Image &guide, float sigma, + int nthreads, int r, float sigma_s, float gamma_r, + float threshold) { + // padding and color transformation + const int s = utils::NextPowerOf2(2 * r + 1); + +#ifdef _OPENMP + if (!nthreads) nthreads = omp_get_max_threads(); // number of threads +#else + nthreads = 1; +#endif // _OPENMP + + pair tiling = ComputeTiling(guide.rows(), guide.columns(), + nthreads); + vector noisy_tiles = SplitTiles(ColorTransform(noisy.copy()), r, + s - r - 1, tiling); + vector guide_tiles = SplitTiles(ColorTransform(guide.copy()), r, + s - r - 1, tiling); + vector> result_tiles(nthreads); + +#pragma omp parallel for num_threads(nthreads) + for (int i = 0; i < nthreads; ++i) { + result_tiles[i] = DA3D_block(noisy_tiles[i], guide_tiles[i], sigma, + r, sigma_s, gamma_r, threshold); + } + return ColorTransformInverse(MergeTiles(result_tiles, guide.shape(), r, + s - r - 1, tiling)); +} + +} // namespace da3d diff --git a/denoise/da3d/DA3D.hpp b/denoise/da3d/DA3D.hpp new file mode 100644 index 0000000..b560c0d --- /dev/null +++ b/denoise/da3d/DA3D.hpp @@ -0,0 +1,21 @@ +/* + * da3d.hpp + * + * Created on: 24/mar/2015 + * Author: nicola pierazzo + */ + +#ifndef DA3D_DA3D_HPP_ +#define DA3D_DA3D_HPP_ + +#include "Image.hpp" + +namespace da3d { + +Image DA3D(const Image &noisy, const Image &guide, float sigma, + int nthreads = 0, int r = 31, float sigma_s = 14.f, + float gamma_r = .7f, float threshold = 2.f); + +} // namespace da3d + +#endif // DA3D_DA3D_HPP_ diff --git a/denoise/da3d/DemoUtils.cpp b/denoise/da3d/DemoUtils.cpp new file mode 100644 index 0000000..7e2ecc3 --- /dev/null +++ b/denoise/da3d/DemoUtils.cpp @@ -0,0 +1,54 @@ +/* + * DemoUtils.cpp + * + * Created on: 10/apr/2017 + * Author: gabriele facciolo + */ + +#include +#include +#include +#include +#include "DemoUtils.hpp" + +extern "C" { +#include +} + +using std::string; +using std::free; +using std::strcmp; +using da3d::Image; + +namespace utils { + +const char *pick_option(int *c, char **v, const char *o, const char *d) { + int id = d ? 1 : 0; + for (int i = 0; i < *c - id; i++) { + if (v[i][0] == '-' && 0 == strcmp(v[i] + 1, o)) { + char *r = v[i + id] + 1 - id; + for (int j = i; j < *c - id; j++) + v[j] = v[j + id + 1]; + *c -= id + 1; + return r; + } + } + return d; +} + +Image read_image(const string &filename) { + int w, h, c; + float *data = iio_read_image_float_vec(filename.c_str(), &w, &h, &c); + Image im(data, h, w, c); + free(data); + return im; +} + +void save_image(const Image &image, const string &filename) { + iio_save_image_float_vec(const_cast(filename.c_str()), + const_cast(image.data()), + image.columns(), image.rows(), image.channels()); +} + + +} // namespace utils diff --git a/denoise/da3d/DemoUtils.hpp b/denoise/da3d/DemoUtils.hpp new file mode 100644 index 0000000..40c381e --- /dev/null +++ b/denoise/da3d/DemoUtils.hpp @@ -0,0 +1,22 @@ +/* + * DemoUtils.cpp + * + * Created on: 10/apr/2017 + * Author: gabriele facciolo + */ + +#ifndef UTILS_DEMOUTILS_HPP +#define UTILS_DEMOUTILS_HPP + +#include "Image.hpp" +#include + +namespace utils { + +const char *pick_option(int *c, char **v, const char *o, const char *d); +da3d::Image read_image(const std::string& filename); +void save_image(const da3d::Image& image, const std::string& filename); + +} // namespace utils + +#endif //UTILS_DEMOUTILS_HPP diff --git a/denoise/da3d/DftPatch.hpp b/denoise/da3d/DftPatch.hpp new file mode 100644 index 0000000..7e6f53b --- /dev/null +++ b/denoise/da3d/DftPatch.hpp @@ -0,0 +1,94 @@ +/* + * DftPatch.hpp + * + * Created on: 11/feb/2015 + * Author: nicola pierazzo + */ + +#ifndef DA3D_DFTPATCH_HPP_ +#define DA3D_DFTPATCH_HPP_ + +#include +#include +#include + +namespace da3d { + +class DftPatch { + public: + DftPatch(int rows, int columns, int channels = 1); + ~DftPatch(); + void ToFreq(); + void ToSpace(); + int rows() const { return rows_; } + int columns() const { return columns_; } + int frows() const { return rows_; } + int fcolumns() const { return fcolumns_; } + int channels() const { return channels_; } + float& space(int col, int row, int chan = 0); + std::complex& freq(int col, int row, int chan = 0); + + private: + float *space_; + std::complex *freq_; + fftwf_plan plan_forward_; + fftwf_plan plan_backward_; + int rows_, columns_, fcolumns_, channels_; +}; + +inline float& DftPatch::space(int col, int row, int chan) { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return space_[row * columns_ * channels_ + col * channels_ + chan]; +} + +inline std::complex& DftPatch::freq(int col, int row, int chan) { + assert(0 <= col && col < fcolumns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return freq_[row * fcolumns_ * channels_ + col * channels_ + chan]; +} + +inline DftPatch::DftPatch(int rows, int columns, int channels) + : rows_(rows), columns_(columns), fcolumns_(columns / 2 + 1), channels_(channels) { + int N = rows * columns * channels; + int N_half = rows * fcolumns_ * channels; + space_ = reinterpret_cast(fftwf_malloc(sizeof(float) * N)); + freq_ = reinterpret_cast *>(fftwf_malloc( + sizeof(fftwf_complex) * N_half)); + int n[] = {rows, columns}; +#pragma omp critical + { + plan_forward_ = fftwf_plan_many_dft_r2c(2, n, channels, space_, NULL, + channels, 1, + reinterpret_cast(freq_), + NULL, channels, 1, FFTW_MEASURE); + plan_backward_ = fftwf_plan_many_dft_c2r(2, n, channels, + reinterpret_cast(freq_), + NULL, channels, 1, space_, NULL, + channels, 1, FFTW_MEASURE); + } +} + +inline DftPatch::~DftPatch() { + fftwf_free(space_); + fftwf_free(freq_); + fftwf_destroy_plan(plan_forward_); + fftwf_destroy_plan(plan_backward_); +} + +inline void DftPatch::ToFreq() { + fftwf_execute(plan_forward_); +} + +inline void DftPatch::ToSpace() { + fftwf_execute(plan_backward_); + for (int i = 0; i < rows_ * columns_ * channels_; ++i) { + space_[i] /= rows_ * columns_; + } +} + +} /* namespace da3d */ + +#endif // DA3D_DFTPATCH_HPP_ diff --git a/denoise/da3d/Image.hpp b/denoise/da3d/Image.hpp new file mode 100644 index 0000000..7a630ae --- /dev/null +++ b/denoise/da3d/Image.hpp @@ -0,0 +1,99 @@ +/* + * Image.hpp + * + * Created on: 14/gen/2015 + * Author: nicola pierazzo + */ + +#ifndef DA3D_IMAGE_HPP_ +#define DA3D_IMAGE_HPP_ + +#include +#include +#include + +namespace da3d { + +class Image { + public: + Image() = default; + Image(int rows, int columns, int channels = 1, float val = 0.f); + // construct from C array + Image(const float *data, int rows, int columns, int channels = 1); + + // disable copy constructor + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + // instead of the copy constructor, we want explicit copy + Image copy() const; + + // default move constructor + Image(Image&&) = default; + Image& operator=(Image&&) = default; + + ~Image() = default; + + void Clear(float val = 0.f) { std::fill(data_.begin(), data_.end(), val); } + + float val(int col, int row, int chan = 0) const; + float& val(int col, int row, int chan = 0); + float val(int pos) const; + float& val(int pos); + + int channels() const { return channels_; } + int columns() const { return columns_; } + int rows() const { return rows_; } + int pixels() const { return columns_ * rows_; } + int samples() const { return channels_ * columns_ * rows_; } + float* data() { return data_.data(); } + const float* data() const { return data_.data(); } + std::pair shape() const { return {rows_, columns_}; } + std::vector::iterator begin() { return data_.begin(); } + std::vector::const_iterator begin() const { return data_.begin(); } + std::vector::iterator end() { return data_.end(); } + std::vector::const_iterator end() const { return data_.end(); } + + protected: + int rows_{0}; + int columns_{0}; + int channels_{0}; + std::vector data_{}; +}; + +inline Image::Image(int rows, int columns, int channels, float val) + : rows_(rows), columns_(columns), channels_(channels), + data_(rows * columns * channels, val) {} + +inline Image::Image(const float *data, int rows, int columns, int channels) + : rows_(rows), columns_(columns), channels_(channels), + data_(data, data + rows * columns * channels) {} + +inline Image Image::copy() const { + return Image(data(), rows(), columns(), channels()); +} + +inline float Image::val(int col, int row, int chan) const { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return data_[row * columns_ * channels_ + col * channels_ + chan]; +} + +inline float& Image::val(int col, int row, int chan) { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return data_[row * columns_ * channels_ + col * channels_ + chan]; +} + +inline float Image::val(int pos) const { + return data_[pos]; +} + +inline float& Image::val(int pos) { + return data_[pos]; +} + +} // namespace da3d + +#endif // DA3D_IMAGE_HPP_ diff --git a/denoise/da3d/LICENSE b/denoise/da3d/LICENSE new file mode 100644 index 0000000..6c847f4 --- /dev/null +++ b/denoise/da3d/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 2017, Nicola Pierazzo +Copyright (C) 2017, Gabriele Facciolo +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/denoise/da3d/README.md b/denoise/da3d/README.md new file mode 100644 index 0000000..cbb81e9 --- /dev/null +++ b/denoise/da3d/README.md @@ -0,0 +1,57 @@ +DA3D: Data Adaptive Dual Domain Denoising +========================================= + +Nicola Pierazzo, Jean-Michel Morel, and Gabriele Facciolo +<{nicola.pierazzo,morel,facciolo}@cmla.ens-cachan.fr>, CMLA, ENS Paris-Saclay, France +Complete IPOL article available at: http://www.ipol.im/pub/art/2016/203 +For future releases of the code visit: https://github.com/gfacciol/da3d + +DA3D (Data Adaptive Dual Domain Denoising) is a last step denoising method +that takes as input a noisy image and as a guide the result of any +state-of-the-art denoising algorithm. The method performs frequency domain +shrinkage on shape and data-adaptive patches. Unlike other dual denoising +methods, DA3D doesn’t process all the image samples, which allows it to use +large patches (64×64 pixels). The shape and data-adaptive patches are +dynamically selected, effectively concentrating the computations on areas with +more details, thus accelerating the process considerably. DA3D also reduces the +staircasing artifacts sometimes present in smooth parts of the guide images. + +The effectiveness of DA3D is confirmed by extensive experimentation. +DA3D improves the result of almost all state-of-the-art methods, and this +improvement requires little additional computation time. + + +Building +-------- + +To compile, use + + $ mkdir build + $ cd build + $ cmake .. [-DCMAKE_CXX_COMPILER=/path/of/c++/compiler -DCMAKE_C_COMPILER=/path/of/c/compiler] [-DCMAKE_BUILD_TYPE=Debug] + $ make + +To rebuild, e.g. when the code is modified, use + + $ cd build + $ make + + +This code only supports the PNG, JPEG, and TIFF (float) image formats +and requires the libpng, libtiff, libjpeg, and libfftw libraries. + + +Usage +----- + +Running the program without parameters prints its usage instructions: + + $ da3d + + usage: ./da3d noisy guide sigma output + +The guide image must be computed from the noisy image using another denoising algorithm for instance: +* [BM3D](http://www.ipol.im/pub/art/2012/l-bm3d/) -- https://github.com/gfacciol/bm3d +* [NL-Bayes](http://www.ipol.im/pub/art/2013/16/) -- https://github.com/npd/nl-bayes +* [NL-Means](http://www.ipol.im/pub/art/2011/bcm_nlm/) -- https://github.com/npd/nlmeans +* [Multiscale DCT Denoising](http://www.ipol.im/pub/pre/201/) -- https://github.com/gfacciol/DCTdenoising diff --git a/denoise/da3d/Utils.cpp b/denoise/da3d/Utils.cpp new file mode 100644 index 0000000..9b86084 --- /dev/null +++ b/denoise/da3d/Utils.cpp @@ -0,0 +1,175 @@ +/* + * Utils.cpp + * + * Created on: 12/feb/2015 + * Author: nicola pierazzo + */ + +#include +#include "Utils.hpp" + +using std::vector; +using std::pair; +using std::move; +using std::max; +using std::min; +using da3d::Image; +using da3d::WeightMap; + +namespace utils { + +/*! \brief Compute best tiling of image in at most ntiles + * + * Returns the pair: rows, columns + */ +pair ComputeTiling(int rows, int columns, int ntiles) { + // The objective is extract ntiles square-ish tiles + // For a square image the optimal number of rows is sqrt(ntiles) + // The ratio rows/columns permits to handle rectangular images + float best_r = sqrt(static_cast(ntiles * rows) / columns); + int r_low = static_cast(best_r); + int r_up = r_low + 1; + if (r_low < 1) return {1, ntiles}; // single row + if (r_up > ntiles) return {ntiles, 1}; // single column + // look for the nearest integer divisors of ntiles + while (ntiles % r_low != 0) --r_low; + while (ntiles % r_up != 0) ++r_up; + // At this point there are two possible tilings: + // {r_low, ntiles / r_low} and {r_up, ntiles / r_up}. + // We need to select the best. + // To do that, we consider the shape of the tiles. + // In the first case, the tiles are roughly + // {rows / r_low, columns * r_low / ntiles} pixels. + // In the second case, the tiles are + // {rows / r_up, columns * r_up / ntiles} pixels. + // Since r_low <= best_r <= r_up the first tile will have i + // more rows than columns and vice-versa. + // + // To select the best case we consider the ratio between the + // lengths of the longer and the shorter edge of a tile. + // The closer this ratio is to 1, the "squarer" the tile will be. + // In other words, we select the first tiling if + // (rows / r_low) / (columns * r_low / ntiles) < + // (columns * r_up / ntiles) / (rows / r_up) + // That is equivalent to (all values are > 0): + // rows * ntiles < r_up * r_low * columns + if (r_up * r_low * columns > ntiles * rows) { + return {r_low, ntiles / r_low}; + } else { + return {r_up, ntiles / r_up}; + } +} + +/*! \brief Split image in tiles + * + * Returns a vector containing tiling.first x tiling.sencond images + * each padded by pad_* pixels. Tiles are stored in lexicographic order. + * Padding outside the image is done by symmetrization + */ +vector SplitTiles(const Image &src, + int pad_before, + int pad_after, + pair tiling) { + vector result; + for (int tr = 0; tr < tiling.first; ++tr) { + int rstart = src.rows() * tr / tiling.first - pad_before; + int rend = src.rows() * (tr + 1) / tiling.first + pad_after; + for (int tc = 0; tc < tiling.second; ++tc) { + int cstart = src.columns() * tc / tiling.second - pad_before; + int cend = src.columns() * (tc + 1) / tiling.second + pad_after; + // copy image to tile using the above computed limits + Image tile(rend - rstart, cend - cstart, src.channels()); + for (int row = rstart; row < rend; ++row) { + for (int col = cstart; col < cend; ++col) { + for (int ch = 0; ch < src.channels(); ++ch) { + tile.val(col - cstart, row - rstart, ch) = src.val( + SymmetricCoordinate(col, src.columns()), + SymmetricCoordinate(row, src.rows()), + ch); + } + } + } + result.push_back(move(tile)); + } + } + return result; +} + +/*! \brief Recompose tiles produced by SplitTiles + * + * Returns an image resulting of recomposing the tiling + * padded margins are averaged in the result + */ +Image MergeTiles(const vector> &src, + pair shape, + int pad_before, + int pad_after, + pair tiling) { + int channels = src[0].first.channels(); + Image result(shape.first, shape.second, channels); + Image weights(shape.first, shape.second); + auto tile = src.begin(); + for (int tr = 0; tr < tiling.first; ++tr) { + int rstart = shape.first * tr / tiling.first - pad_before; + int rend = shape.first * (tr + 1) / tiling.first + pad_after; + for (int tc = 0; tc < tiling.second; ++tc) { + int cstart = shape.second * tc / tiling.second - pad_before; + int cend = shape.second * (tc + 1) / tiling.second + pad_after; + // copy tile to image using the above computed limits + for (int row = max(0, rstart); row < min(shape.first, rend); ++row) { + for (int col = max(0, cstart); col < min(shape.second, cend); ++col) { + for (int ch = 0; ch < channels; ++ch) { + result.val(col, row, ch) += + tile->first.val(col - cstart, row - rstart, ch); + } + weights.val(col, row) += tile->second.val(col - cstart, row - rstart); + } + } + ++tile; + } + } + // normalize by the weight + for (int row = 0; row < shape.first; ++row) { + for (int col = 0; col < shape.second; ++col) { + for (int ch = 0; ch < channels; ++ch) { + result.val(col, row, ch) /= weights.val(col, row); + } + } + } + return result; +} + +/*! \brief Test if a color image is actually monochrome */ +bool isMonochrome (const Image &u) { + for (int row = 0; row < u.rows(); ++row) { + for (int col = 0; col < u.columns(); ++col) { + float v = u.val(col, row, 0); + for (int ch = 1; ch < u.channels(); ++ch) { + if (u.val(col, row, ch) != v) { + return false; + } + } + } + } + return true; +} + +/*! \brief Convert image to monochrome + * + * Returns a monochrome image + */ +Image makeMonochrome (const Image &u) { + Image result(u.rows(), u.columns()); + for (int row = 0; row < u.rows(); ++row) { + for (int col = 0; col < u.columns(); ++col) { + double v = 0; + for (int ch = 0; ch < u.channels(); ++ch) { + v += u.val(col, row, ch); + } + result.val(col,row) = v / u.channels(); + } + } + return result; +} + +} // namespace utils diff --git a/denoise/da3d/Utils.hpp b/denoise/da3d/Utils.hpp new file mode 100644 index 0000000..24113ea --- /dev/null +++ b/denoise/da3d/Utils.hpp @@ -0,0 +1,68 @@ +/* + * Utils.hpp + * + * Created on: 23/mar/2015 + * Author: nicola pierazzo + */ + +#ifndef DA3D_UTILS_HPP_ +#define DA3D_UTILS_HPP_ + +#include +#include +#include +#include +#include "Image.hpp" +#include "WeightMap.hpp" + +namespace utils { + +// number of bits used to represent n +inline int NumberOfBits(int n) { + int ans = 0; + while (n) { + ans += 1; + n >>= 1; + } + return ans; +} + +// http://graphics.stanford.edu/~seander/bithacks.html +inline int NextPowerOf2(int n) { + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + ++n; + return n; +} + +inline float fastexp(float x) { + int result = static_cast(12102203 * x) + 1065353216; + result *= result > 0; + std::memcpy(&x, &result, sizeof(result)); + return x; +} + +inline int SymmetricCoordinate(int pos, int size) { + if (pos < 0) pos = -pos - 1; + if (pos >= 2 * size) pos %= 2 * size; + if (pos >= size) pos = 2 * size - 1 - pos; + return pos; +} + +bool isMonochrome (const da3d::Image &u); + +da3d::Image makeMonochrome (const da3d::Image &u); + +std::pair ComputeTiling(int rows, int columns, int tiles); +std::vector SplitTiles(const da3d::Image &src, int pad_before, + int pad_after, std::pair tiling); +da3d::Image MergeTiles(const std::vector> &src, + std::pair shape, int pad_before, int pad_after, + std::pair tiling); +} // namespace utils + +#endif // DA3D_UTILS_HPP_ diff --git a/denoise/da3d/VERSION b/denoise/da3d/VERSION new file mode 100644 index 0000000..0808d7b --- /dev/null +++ b/denoise/da3d/VERSION @@ -0,0 +1 @@ +0.20180203 diff --git a/denoise/da3d/WeightMap.cpp b/denoise/da3d/WeightMap.cpp new file mode 100644 index 0000000..b7ab6e6 --- /dev/null +++ b/denoise/da3d/WeightMap.cpp @@ -0,0 +1,119 @@ +/* + * WeightMap.cpp + * + * Created on: 12/feb/2015 + * Author: nicola pierazzo + */ + +#include +#include +#include +#include +#include +#include +#include "WeightMap.hpp" +#include "Image.hpp" +#include "Utils.hpp" + +using std::max; +using std::min; +using std::pair; +using std::make_pair; +using std::tie; + +namespace da3d { + +WeightMap::WeightMap(int rows, int columns) { + Init(rows, columns); +} + +void WeightMap::Init(int rows, int columns) { + assert (rows > 0 && columns > 0); + int height_rows = utils::NumberOfBits(rows - 1) + 1; + int height_columns = utils::NumberOfBits(columns - 1) + 1; + num_levels_ = max(height_rows, height_columns); + width_ = columns; + height_ = rows; + rows_.resize(num_levels_); + columns_.resize(num_levels_); + data_.resize(num_levels_); + + // initialization of layers + int rows_rounded = utils::NextPowerOf2(rows); + int cols_rounded = utils::NextPowerOf2(columns); + for (int l = 0; l < num_levels_; ++l) { + rows_[l] = rows_rounded; + columns_[l] = cols_rounded; + data_[l].resize(rows_rounded * cols_rounded); + // zeros in the good area, MAXFLT elsewhere + for (int row = 0; row < rows; ++row) { + for (int col = 0; col < columns; ++col) + val(col, row, l) = 0.f; + for (int col = columns; col < cols_rounded; ++col) { + val(col, row, l) = std::numeric_limits::infinity(); + } + } + for (int row = rows; row < rows_rounded; ++row) { + for (int col = 0; col < cols_rounded; ++col) { + val(col, row, l) = std::numeric_limits::infinity(); + } + } + rows = (rows + 1) >> 1; // it stays at least 1 + columns = (columns + 1) >> 1; + rows_rounded = ((rows_rounded + 3) >> 2) << 1; // it stays at least 2 + cols_rounded = ((cols_rounded + 3) >> 2) << 1; + } + assert(rows == 1); + assert(columns == 1); +} + +float WeightMap::Minimum() const { + return val(0, 0, num_levels_ - 1); +} + +pair WeightMap::FindMinimum() const { + int row = 0; + int col = 0; + for (int l = num_levels_ - 2; l >= 0; --l) { + row <<= 1; + col <<= 1; + // This finds the position of the minimum in the 2x2 square + tie(row, col) = min({make_pair(row, col), make_pair(row + 1, col), + make_pair(row, col + 1), make_pair(row + 1, col + 1)}, + [this, &l](const pair &a, + const pair &b) { + return val(a.second, a.first, l) + < val(b.second, b.first, l); + }); + } + return {row, col}; +} + +void WeightMap::IncreaseWeights(const Image &weights, int row0, int col0) { + assert(weights.channels() == 1); + int firstrow = max(0, row0); + int lastrow = min(height(), row0 + weights.rows()) - 1; + int firstcol = max(0, col0); + int lastcol = min(width(), col0 + weights.columns()) - 1; + + // Updates the level zero + for (int row = firstrow; row <= lastrow; ++row) { + for (int col = firstcol; col <= lastcol; ++col) { + val(col, row) += weights.val(col - col0, row - row0); + } + } + // Updates the other levels + for (int l = 1; l < num_levels_; ++l) { + for (int row = firstrow >> l; row <= lastrow >> l; ++row) { + for (int col = firstcol >> l; col <= lastcol >> l; ++col) { + int dc = col << 1, dr = row << 1; + val(col, row, l) = min({val(dc, dr, l - 1), + val(dc + 1, dr, l - 1), + val(dc, dr + 1, l - 1), + val(dc + 1, dr + 1, l - 1)}); + } + } + } +} + +} // namespace da3d diff --git a/denoise/da3d/WeightMap.hpp b/denoise/da3d/WeightMap.hpp new file mode 100644 index 0000000..44e46fb --- /dev/null +++ b/denoise/da3d/WeightMap.hpp @@ -0,0 +1,55 @@ +/* + * WeightMap.hpp + * + * Created on: 12/feb/2015 + * Author: nicola pierazzo + */ + +#ifndef DA3D_WEIGHTMAP_HPP_ +#define DA3D_WEIGHTMAP_HPP_ + +#include +#include + +namespace da3d { + +class Image; + +class WeightMap { + public: + WeightMap() = default; + WeightMap(int rows, int columns); + ~WeightMap() = default; + void Init(int rows, int columns); + float Minimum() const; + std::pair FindMinimum() const; + void IncreaseWeights(const Image &weights, int row0, int col0); + int width() const { return width_; } + int height() const { return height_; } + int num_levels() const { return num_levels_; } + float val(int col, int row, int level = 0) const; + float &val(int col, int row, int level = 0); + const float* data() const { return data_[0].data(); } + private: + int num_levels_{0}, width_{0}, height_{0}; + std::vector rows_, columns_; + std::vector> data_; +}; + +inline float WeightMap::val(int col, int row, int level) const { + assert (0 <= level && level < num_levels_); + assert (0 <= col && col < columns_[level]); + assert (0 <= row && row < rows_[level]); + return data_[level][columns_[level] * row + col]; +} + +inline float &WeightMap::val(int col, int row, int level) { + assert (0 <= level && level < num_levels_); + assert (0 <= col && col < columns_[level]); + assert (0 <= row && row < rows_[level]); + return data_[level][columns_[level] * row + col]; +} + +} // namespace da3d + +#endif // DA3D_WEIGHTMAP_HPP_ diff --git a/denoise/da3d/main.cpp b/denoise/da3d/main.cpp new file mode 100644 index 0000000..f5d03a9 --- /dev/null +++ b/denoise/da3d/main.cpp @@ -0,0 +1,59 @@ +/* + * main.cpp + * + * Created on: 24/mar/2015 + * Author: nicola pierazzo + */ + +#include +#include +#include +#include +#include +#include +#include +#include "Image.hpp" +#include "Utils.hpp" +#include "DemoUtils.hpp" +#include "DA3D.hpp" + +using std::cerr; +using std::endl; + +using utils::pick_option; +using utils::read_image; +using utils::save_image; +using utils::isMonochrome; +using utils::makeMonochrome; + +using da3d::Image; +using da3d::DA3D; + + +int main(int argc, char **argv) { + bool usage = static_cast(pick_option(&argc, argv, "h", nullptr)); + if (usage || argc < 4) { + cerr << "usage: " << argv[0] << " noisy guide sigma output" << + endl; + return EXIT_FAILURE; + } + +#ifndef _OPENMP + cerr << "Warning: OpenMP not available. The algorithm will run in a single" << + " thread." << endl; +#endif + + Image input = read_image(argv[1]); + Image guide = read_image(argv[2]); + float sigma = atof(argv[3]); + // DA3D doesn't work if a color image has monochromatic noise + if (input.channels()>1 && isMonochrome(input)) { + cerr << "Warning: input color image has monochromatic noise! " << + "Converting to monochrome." << endl; + input = makeMonochrome(input); + guide = makeMonochrome(guide); + } + Image output = DA3D(input, guide, sigma); + save_image(output, argc > 4 ? argv[4] : "-"); + return EXIT_SUCCESS; +} diff --git a/denoise/da3d/man/da3d.1 b/denoise/da3d/man/da3d.1 new file mode 100644 index 0000000..8c5e0a7 --- /dev/null +++ b/denoise/da3d/man/da3d.1 @@ -0,0 +1,47 @@ +.TH "IPOL tools User Manual" 1 "03 Feb 2018" "IPOL documentation" + +.SH NAME +da3d + +.SH DESCRIPTION +Data Adaptive Dual Domain Denoising + +.SH SYNOPSIS +da3d noisy guide sigma output + +.SH OPTIONS +.TP +noisy +name input graphics file (jpeg | png | tiff) +.TP +guide +image must be computed from the noisy image using another denoising algorithm for instance: bm3d, nl-bayer, nlmeans, dctdenoising (jpeg | png | tiff) +.TP +sigma +noise std (int, mandatory, recomended = 16) +.TP +output +name output graphics file (jpeg | png | tiff) + +.SH EXAMPLE +nl-bayer 16 noisy.tiff guide.png +da3d noisy.tiff guide.png 16 denoised.png + +.SH COPYRIGHT +Copyright : (C) 2017 IPOL Image Processing On Line http://www.ipol.im/ + Licence : GPL v3+, see GPLv3.txt + +.SH SEE ALSO + cjpeg (1), + djpeg (1) + +.SH CONTACTS +.TP +Author: +Nicola Pierazzo +.TP +Author: +Gabriele Facciolo +.TP +Latest version available at: +https://github.com/gfacciol/da3d diff --git a/denoise/dctdenoising/CHANGELOG b/denoise/dctdenoising/CHANGELOG new file mode 100644 index 0000000..7a99e98 --- /dev/null +++ b/denoise/dctdenoising/CHANGELOG @@ -0,0 +1,11 @@ +0.20180203 + + libiio version + +0.20180127 + + update documentation + +0.20171011 + + work version diff --git a/denoise/dctdenoising/CMakeLists.txt b/denoise/dctdenoising/CMakeLists.txt new file mode 100644 index 0000000..89564eb --- /dev/null +++ b/denoise/dctdenoising/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 2.8) +project(dctdenoising) + +# The build type "Release" adds some optimizations +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE "Release") +endif () + +# GCC on MacOs needs this option to use the clang assembler +if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (APPLE)) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wa,-q") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-q") + set (CMAKE_INCLUDE_SYSTEM_FLAG_C "-isystem") + set (CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem") +endif () + +# Optimize to the current CPU and enable warnings +if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR +(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR +(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -Wall -Wextra") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra") +endif () + +# Enable C99 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") +else () + set (CMAKE_C_STANDARD 99) +endif () + +# Enable C++11 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") +else () + set (CMAKE_CXX_STANDARD 11) +endif () + +# Enable OpenMP +find_package (OpenMP) +if(OPENMP_FOUND) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") +endif() + +# Link LibFFTW +find_path (FFTW_INCLUDE_DIR fftw3.h) +find_library (FFTWF_LIBRARIES NAMES fftw3f) +include_directories (SYSTEM ${FFTW_INCLUDE_DIR}) +link_libraries (${FFTWF_LIBRARIES}) +if (NOT FFTW_INCLUDE_DIR) + message (FATAL_ERROR "FFTW3 not found.") +endif () +find_path (IIO_INCLUDE_DIR iio.h) +find_library (IIO_LIBRARIES NAMES iio) +include_directories (SYSTEM ${iio_INCLUDE_DIR}) +link_libraries (${IIO_LIBRARIES}) +if (NOT IIO_INCLUDE_DIR) + message (FATAL_ERROR "IIO not found.") +endif () + +set(SOURCE_FILES + main.cpp + DCTdenoising.cpp + DCTdenoising.h + DCTPatch.hpp + Image.hpp + utils.cpp + utils.hpp + demoutils.cpp + demoutils.hpp +) + +add_executable(dctdenoising ${SOURCE_FILES}) diff --git a/denoise/dctdenoising/DCTPatch.hpp b/denoise/dctdenoising/DCTPatch.hpp new file mode 100644 index 0000000..d69b644 --- /dev/null +++ b/denoise/dctdenoising/DCTPatch.hpp @@ -0,0 +1,144 @@ +/* + * DCTPatch.hpp + * + * Created on: 11/feb/2015 + * Author: nicola + */ + +#ifndef DCTDENOISING_DFTPATCH_HPP_ +#define DCTDENOISING_DFTPATCH_HPP_ + +#include +#include +#include + +namespace imgutils { + +/*! \brief DCTPatch object + * + * DCTPatch contains a patch and its DCT transform, + * and allows to convert back and forth between the two + */ +class DCTPatch { + public: + DCTPatch(int rows, int columns, int channels = 1); + + // disable copy constructor + DCTPatch(const DCTPatch&) = delete; + DCTPatch& operator=(const DCTPatch&) = delete; + + // default move constructor + DCTPatch(DCTPatch&&) = default; + DCTPatch& operator=(DCTPatch&&) = default; + + ~DCTPatch(); + + void ToFreq(); + void ToSpace(); + int rows() const { return rows_; } + int columns() const { return columns_; } + int channels() const { return channels_; } + float& space(int col, int row, int chan = 0); + float& freq(int col, int row, int chan = 0); + + private: + float *space_; + float *freq_; + fftwf_plan plan_forward_; + fftwf_plan plan_backward_; + const int rows_, columns_, channels_; + const float norm_factor_; +}; + + +/*! \brief Access the spatial representation of the patch + * + * Returns pixel value + */ +inline float& DCTPatch::space(int col, int row, int chan) { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return space_[chan * rows_ * columns_ + row * columns_ + col]; +} + +/*! \brief Access the DCT representation of the patch + * + * Returns freq coefficient + */ +inline float& DCTPatch::freq(int col, int row, int chan) { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return freq_[chan * rows_ * columns_ + row * columns_ + col]; +} + +inline DCTPatch::DCTPatch(int rows, int columns, int channels) + : rows_(rows), columns_(columns), channels_(channels), + norm_factor_(std::sqrt(.25f / (rows * columns))) { + int N = rows * columns * channels; + space_ = reinterpret_cast(fftwf_malloc(sizeof(float) * N)); + freq_ = reinterpret_cast(fftwf_malloc(sizeof(float) * N)); + int n[] = {rows, columns}; + fftwf_r2r_kind dct2[] = {FFTW_REDFT10, FFTW_REDFT10}; + fftwf_r2r_kind idct2[] = {FFTW_REDFT01, FFTW_REDFT01}; +#pragma omp critical + { + plan_forward_ = fftwf_plan_many_r2r(2, n, channels, space_, NULL, 1, + rows * columns, freq_, NULL, 1, + rows * columns, dct2, + FFTW_MEASURE | FFTW_DESTROY_INPUT); + plan_backward_ = fftwf_plan_many_r2r(2, n, channels, freq_, NULL, 1, + rows * columns, space_, NULL, 1, + rows * columns, idct2, + FFTW_MEASURE | FFTW_DESTROY_INPUT); + } +} + +inline DCTPatch::~DCTPatch() { + fftwf_free(space_); + fftwf_free(freq_); + fftwf_destroy_plan(plan_forward_); + fftwf_destroy_plan(plan_backward_); +} + + +/*! \brief Computes the isometric DCT(Type2) of space, stores result in freq + */ +inline void DCTPatch::ToFreq() { + fftwf_execute(plan_forward_); + // normalize coefficients + for (int ch = 0; ch < channels_; ++ch) { + for (int row = 0; row < rows_; ++row) { + freq(0, row, ch) /= sqrt(2.f); + for (int col = 0; col < columns_; ++col) { + freq(col, row, ch) *= norm_factor_; + } + } + for (int col = 0; col < columns_; ++col) { + freq(col, 0, ch) /= sqrt(2.f); + } + } +} + +/*! \brief Computes the isometric iDCT(Type3) of freq, stores result in space + */ +inline void DCTPatch::ToSpace() { + // normalize coefficients + for (int ch = 0; ch < channels_; ++ch) { + for (int row = 0; row < rows_; ++row) { + freq(0, row, ch) *= sqrt(2.f); + for (int col = 0; col < columns_; ++col) { + freq(col, row, ch) *= norm_factor_; + } + } + for (int col = 0; col < columns_; ++col) { + freq(col, 0, ch) *= sqrt(2.f); + } + } + fftwf_execute(plan_backward_); +} + +} /* namespace imgutils */ + +#endif // DCTDENOISING_DFTPATCH_HPP_ diff --git a/denoise/dctdenoising/DCTdenoising.cpp b/denoise/dctdenoising/DCTdenoising.cpp new file mode 100644 index 0000000..4ffb58f --- /dev/null +++ b/denoise/dctdenoising/DCTdenoising.cpp @@ -0,0 +1,280 @@ +/* + * Code Copyright (c) 2017, Nicola Pierazzo , + * Gabriele Facciolo + * Based on the 2010 article by Guoshen Yu , + * Guillermo Sapiro + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +/*----------------------- Multiscale DCTdenoising -------------------------*/ +// This code implements "Multiscale DCT denoising". +// http://www.ipol.im/pub/art/2017/201 +// Copyright, Nicola Pierazzo, Gabriele Facciolo, 2017. +// Please report bugs and/or send comments to G. Facciolo gfacciol@gmail.com +/*---------------------------------------------------------------------------*/ + + +#include +#include +#include +#include + +#include "Image.hpp" +#include "DCTPatch.hpp" +#include "utils.hpp" +#include "DCTdenoising.h" + +#ifdef _OPENMP +#include +#endif + +using imgutils::Image; +using imgutils::DCTPatch; +using imgutils::ComputeTiling; +using imgutils::SplitTiles; +using imgutils::MergeTiles; +using std::move; +using std::sqrt; +using std::pair; +using std::abs; +using std::vector; +using std::copy; + +constexpr float HARD_THRESHOLD = 3.f; + +Image ColorTransform(Image &&src) { + Image img = move(src); + if (img.channels() == 3) { + for (int row = 0; row < img.rows(); ++row) { + for (int col = 0; col < img.columns(); ++col) { + float r, g, b; + r = img.val(col, row, 0); + g = img.val(col, row, 1); + b = img.val(col, row, 2); + img.val(col, row, 0) = (r + g + b) / sqrt(3.f); + img.val(col, row, 1) = (r - b) / sqrt(2.f); + img.val(col, row, 2) = (r - 2 * g + b) / sqrt(6.f); + } + } + } + return img; +} + +Image ColorTransformInverse(Image &&src) { + Image img = move(src); + if (img.channels() == 3) { + for (int row = 0; row < img.rows(); ++row) { + for (int col = 0; col < img.columns(); ++col) { + float y, u, v; + y = img.val(col, row, 0); + u = img.val(col, row, 1); + v = img.val(col, row, 2); + img.val(col, row, 0) = (sqrt(2.f) * y + sqrt(3.f) * u + v) / sqrt(6.f); + img.val(col, row, 1) = (y - sqrt(2.f) * v) / sqrt(3.f); + img.val(col, row, 2) = (sqrt(2.f) * y - sqrt(3.f) * u + v) / sqrt(6.f); + } + } + } + return img; +} + +inline void ExtractPatch(const Image &src, int pr, int pc, DCTPatch *dst) { + // src is padded, so (pr, pc) becomes the upper left pixel + for (int chan = 0; chan < dst->channels(); ++chan) { + for (int row = 0; row < dst->rows(); ++row) { + // // the following line copies a line interval to the patch and + // // is equivalent toi (but faster): + // for (int col = 0; col < dst->columns(); ++col) { + // dst->space(col, row, chan) = src.val(pc + col, pr + row, chan); + // } + copy(&(src.val(pc, pr + row, chan)), + &src.val(pc + dst->columns(), pr + row, chan), + &dst->space(0, row, chan)); + } + } +} + +/*! \brief DCT denoising steps: Hard thresholding and Wiener + * + * This funciton implemets both steps, when guided==false the hard + * thresholding step is applied and the guide image is ignored. + * Otherwise the Wiener filtering step is applied using guide as oracle. + * Returns a pair containing the aggregated patches and weights + * + * Functions step1, and step2 provide an alternative interface to this one. + */ +inline pair DCTsteps(const Image &noisy, const Image &guide, + const float sigma, const int dct_sz, + bool adaptive_aggregation, const bool guided) { + Image result(noisy.rows(), noisy.columns(), noisy.channels()); + Image weights(noisy.rows(), noisy.columns()); + + DCTPatch patch(dct_sz, dct_sz, noisy.channels()); + DCTPatch gpatch(dct_sz, dct_sz, noisy.channels()); // unused if !guided + for (int pr = 0; pr <= noisy.rows() - dct_sz; ++pr) { + for (int pc = 0; pc <= noisy.columns() - dct_sz; ++pc) { + // starts processing of a single patch + float wP = 0; // adaptive aggregation weight + ExtractPatch(noisy, pr, pc, &patch); + patch.ToFreq(); + + if (guided) { + ExtractPatch(guide, pr, pc, &gpatch); + gpatch.ToFreq(); + } + + for (int chan = 0; chan < noisy.channels(); ++chan) { + for (int row = 0; row < dct_sz; ++row) { + for (int col = 0; col < dct_sz; ++col) { + + if (guided) { // Wiener filtering with oracle guide + if (row || col) { + float G = gpatch.freq(col, row, chan); + float w = (G * G) / (G * G + sigma * sigma); + patch.freq(col, row, chan) *= w; + // add to weights excluding DC + wP += w*w; + } + } else { // Hard thresholding + if (row || col) { + if (abs(patch.freq(col, row, chan)) < HARD_THRESHOLD * sigma) { + patch.freq(col, row, chan) = 0.f; + } else { // count ALL nonzero frequencies excluding DC + wP++; + } + } + + } + + } + } + } + + patch.ToSpace(); + + wP = 1.f/(1.f + wP); + if (!adaptive_aggregation) + wP = 1.f; + + // Aggregation of the patch + for (int ch = 0; ch < noisy.channels(); ++ch) { + for (int row = 0; row < dct_sz; ++row) { + for (int col = 0; col < dct_sz; ++col) { + result.val(col + pc, row + pr, ch) += patch.space(col, row, ch)*wP; + } + } + } + for (int row = 0; row < dct_sz; ++row) { + for (int col = 0; col < dct_sz; ++col) { + weights.val(col + pc, row + pr) += 1.f*wP; + } + } + } + } + return {move(result), move(weights)}; +} + +/*! \brief wrapper for DCTsteps in case of Hard thresholding + * + * Returns a pair containing the aggregated patches and weights + */ +inline pair step1(const Image &noisy, + const float sigma, const int dct_sz, + bool adaptive_aggregation) { + Image dummyguide; // dummy guide + return DCTsteps(noisy, dummyguide, sigma, dct_sz, adaptive_aggregation, false); +} + +/*! \brief wrapper for DCTsteps in case of Wiener filtering + * + * Returns a pair containing the aggregated patches and weights + */ +inline pair step2(const Image &noisy, const Image &guide, + const float sigma, const int dct_sz, + bool adaptive_aggregation) { + return DCTsteps(noisy, guide, sigma, dct_sz, adaptive_aggregation, true); +} + + + +/*! \brief Denoise an image with sliding window DCT denoising + * + * If guided==false, then guide is ignored and hard thresholding is applied, + * otherwise Wiener filtering is applied using guide as oracle. + * Returns a denoised image + */ +inline Image DCTdenoisingBoth(const Image &noisy, const Image &guide, float sigma, + int dct_sz, bool adaptive_aggregation, + const bool guided, int nthreads) { +#ifdef _OPENMP + if (!nthreads) nthreads = omp_get_max_threads(); // number of threads +#else + nthreads = 1; +#endif // _OPENMP + + int r = dct_sz / 2; // half patch size for the padding + // compute tiling parameters + pair tiling = ComputeTiling(noisy.rows(), noisy.columns(), + nthreads); + // prepare image tiles + vector noisy_tiles, guide_tiles; + noisy_tiles = SplitTiles(ColorTransform(noisy.copy()), + r, dct_sz - r, tiling); + if (guided) { + guide_tiles = SplitTiles(ColorTransform(guide.copy()), + r, dct_sz - r, tiling); + } + vector> result_tiles(nthreads); + + // parallel-process the tiles + #pragma omp parallel for num_threads(nthreads) + for (int i = 0; i < nthreads; ++i) { + if (guided) { // Wiener filtering + result_tiles[i] = step2(noisy_tiles[i], guide_tiles[i], + sigma, dct_sz, adaptive_aggregation); + } else { // Hard thresholding + result_tiles[i] = step1(noisy_tiles[i], + sigma, dct_sz, adaptive_aggregation); + } + } + + // combine tiles and invert color transform + return ColorTransformInverse(MergeTiles(result_tiles, noisy.shape(), r, + dct_sz - r, tiling)); +} + +/*! \brief wrapper for DCTdenoisingBoth: Wiener filtering case (guided) + * + * Returns the denoised image + */ +Image DCTdenoisingGuided(const Image &noisy, const Image &guide, float sigma, + int dct_sz, bool adaptive_aggregation, int nthreads) { + return DCTdenoisingBoth(noisy, guide, sigma, dct_sz, + adaptive_aggregation, true, nthreads); +} + +/*! \brief wrapper for DCTdenoisingBoth: Hard thresholding case + * + * Returns the denoised image + */ +Image DCTdenoising(const Image &noisy, float sigma, + int dct_sz, bool adaptive_aggregation, int nthreads) { + Image dummyguide; + return DCTdenoisingBoth(noisy, dummyguide, sigma, dct_sz, + adaptive_aggregation, false, nthreads); +} + diff --git a/denoise/dctdenoising/DCTdenoising.h b/denoise/dctdenoising/DCTdenoising.h new file mode 100644 index 0000000..3442ad9 --- /dev/null +++ b/denoise/dctdenoising/DCTdenoising.h @@ -0,0 +1,44 @@ +/* + * Code Copyright (c) 2017, Nicola Pierazzo , + * Gabriele Facciolo + * Based on the 2010 article by Guoshen Yu , + * Guillermo Sapiro + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +/*----------------------- Multiscale DCTdenoising -------------------------*/ +// This code implements "Multiscale DCT denoising". +// http://www.ipol.im/pub/art/2017/201 +// Copyright, Nicola Pierazzo, Gabriele Facciolo, 2017. +// Please report bugs and/or send comments to G. Facciolo gfacciol@gmail.com +/*---------------------------------------------------------------------------*/ + +#ifndef DCTDENOISING_DCTDENOISING_HPP +#define DCTDENOISING_DCTDENOISING_HPP + +#include "Image.hpp" + +imgutils::Image DCTdenoising(const imgutils::Image &noisy, float sigma, + int dct_size, bool adaptive_aggregation = true, + int nthreads = 0); +imgutils::Image DCTdenoisingGuided(const imgutils::Image &noisy, + const imgutils::Image &guide, + float sigma, int dct_size, + bool adaptive_aggregation = true, + int nthreads = 0); + +#endif // DCTDENOISING_DCTDENOISING_HPP diff --git a/denoise/dctdenoising/Image.hpp b/denoise/dctdenoising/Image.hpp new file mode 100644 index 0000000..d40273b --- /dev/null +++ b/denoise/dctdenoising/Image.hpp @@ -0,0 +1,99 @@ +/* + * Image.hpp + * + * Created on: 14/gen/2015 + * Author: nicola + */ + +#ifndef IMAGE_HPP_ +#define IMAGE_HPP_ + +#include +#include +#include + +namespace imgutils { + +class Image { + public: + Image() = default; + Image(int rows, int columns, int channels = 1, float val = 0.f); + // construct from C array + Image(const float *data, int rows, int columns, int channels = 1); + + // disable copy constructor + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + // instead of the copy constructor, we want explicit copy + Image copy() const; + + // default move constructor + Image(Image&&) = default; + Image& operator=(Image&&) = default; + + ~Image() = default; + + void Clear(float val = 0.f) { std::fill(data_.begin(), data_.end(), val); } + + const float& val(int col, int row, int chan = 0) const; + float& val(int col, int row, int chan = 0); + const float& val(int pos) const; + float& val(int pos); + + int channels() const { return channels_; } + int columns() const { return columns_; } + int rows() const { return rows_; } + int pixels() const { return columns_ * rows_; } + int samples() const { return channels_ * columns_ * rows_; } + float* data() { return data_.data(); } + const float* data() const { return data_.data(); } + std::pair shape() const { return {rows_, columns_}; } + std::vector::iterator begin() { return data_.begin(); } + std::vector::const_iterator begin() const { return data_.begin(); } + std::vector::iterator end() { return data_.end(); } + std::vector::const_iterator end() const { return data_.end(); } + + protected: + int rows_{0}; + int columns_{0}; + int channels_{0}; + std::vector data_{}; +}; + +inline Image::Image(int rows, int columns, int channels, float val) + : rows_(rows), columns_(columns), channels_(channels), + data_(rows * columns * channels, val) {} + +inline Image::Image(const float *data, int rows, int columns, int channels) + : rows_(rows), columns_(columns), channels_(channels), + data_(data, data + rows * columns * channels) {} + +inline Image Image::copy() const { + return Image(data(), rows(), columns(), channels()); +} + +inline const float& Image::val(int col, int row, int chan) const { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return data_[chan * rows_ * columns_ + row * columns_ + col]; +} + +inline float& Image::val(int col, int row, int chan) { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return data_[chan * rows_ * columns_ + row * columns_ + col]; +} + +inline const float& Image::val(int pos) const { + return data_[pos]; +} + +inline float& Image::val(int pos) { + return data_[pos]; +} + +} /* namespace imgutils */ + +#endif // IMAGE_HPP_ diff --git a/denoise/dctdenoising/LICENSE b/denoise/dctdenoising/LICENSE new file mode 100644 index 0000000..729f5d7 --- /dev/null +++ b/denoise/dctdenoising/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/denoise/dctdenoising/README.md b/denoise/dctdenoising/README.md new file mode 100644 index 0000000..cf5a3d9 --- /dev/null +++ b/denoise/dctdenoising/README.md @@ -0,0 +1,71 @@ +% Multiscale DCT image denoising. + +# ABOUT + +* Author : Nicola Pierazzo +* Author : Gabriele Facciolo +* Copyright : (C) 2017 IPOL Image Processing On Line http://www.ipol.im/ +* Licence : GPL v3+, see GPLv3.txt +* Based on the 2010 implementation of DCT denoising by: + Guoshen Yu and Guillermo Sapiro +* Latest version available at: https://github.com/gfacciol/DCTdenoising + +# OVERVIEW + +This source code provides an implementation of the "Multiscale DCT denoising" +algorithm described in the IPOL article: http://www.ipol.im/pub/art/2017/201 + +# UNIX/LINUX/MAC USER GUIDE + +The code is compilable on Unix/Linux and Mac OS. + +- Compilation. +Automated compilation requires the Cmake and make. + +- Dependencies. +This code requires the libpng, libtiff, libjpeg, and libfftw. + +- Image formats. +Only the PNG, JPEG, and TIFF (float) formats are supported. + +------------------------------------------------------------------------- +Usage: +1. Download the code package and extract it. Go to that directory. + +2. Compile the source code (on Unix/Linux/Mac OS). + + mkdir build; cd build; + cmake ..; make; + +3. Runing DCT image denoising: parameters + + ./dctdenoising sigma [input [output]] # noise std, noisy image, output + [-w patch_size (default 8)] # DCT denoising patch size + [-1 | -2 guide] # -1: only hard thresh., -2: provide guide + [-no_adaptive_aggregation] # disable adaptive aggregation weights + [-n scales(4)] # multiscale: number of scales + [-c factor(.5)] # multiscale: recomposition factor + [-single output_singlescale] # multiscale: save also one-scale result + + +The flag -1 permits to run DCT denoising only with the hard thresholding step, +while -2 allows to specify the guide for the wiener filtering step. +When not set both steps are executed using the output of the first one as guide +for the second one. +Setting no_adaptive_aggregation disables the aggregation weights. + + +Example, run + + ./dctdenoising 15 ../noisy.tiff denoised.png + + +To visualize tiff (float) images use PVFLIP (https://github.com/gfacciol/pvflip) +or ImageJ (https://imagej.nih.gov/ij/index.html) + + +# ABOUT THIS FILE +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without any warranty. diff --git a/denoise/dctdenoising/VERSION b/denoise/dctdenoising/VERSION new file mode 100644 index 0000000..0808d7b --- /dev/null +++ b/denoise/dctdenoising/VERSION @@ -0,0 +1 @@ +0.20180203 diff --git a/denoise/dctdenoising/demoutils.cpp b/denoise/dctdenoising/demoutils.cpp new file mode 100644 index 0000000..2b09553 --- /dev/null +++ b/denoise/dctdenoising/demoutils.cpp @@ -0,0 +1,50 @@ +// +// Created by Gabriele Facciolo on 10/04/17. +// + +#include +#include +#include +#include +#include "demoutils.hpp" + +extern "C" { +#include +} + +using std::string; +using std::free; +using std::strcmp; + +namespace imgutils { + +const char *pick_option(int *c, char **v, const char *o, const char *d) { + int id = d ? 1 : 0; + for (int i = 0; i < *c - id; i++) { + if (v[i][0] == '-' && 0 == strcmp(v[i] + 1, o)) { + char *r = v[i + id] + 1 - id; + for (int j = i; j < *c - id; j++) + v[j] = v[j + id + 1]; + *c -= id + 1; + return r; + } + } + return d; +} + +Image read_image(const string &filename) { + int w, h, c; + float *data = iio_read_image_float_split(filename.c_str(), &w, &h, &c); + Image im(data, h, w, c); + free(data); + return im; +} + +void save_image(const Image &image, const string &filename) { + iio_save_image_float_split(const_cast(filename.c_str()), + const_cast(image.data()), + image.columns(), image.rows(), image.channels()); +} + + +} // namespace imgutils diff --git a/denoise/dctdenoising/demoutils.hpp b/denoise/dctdenoising/demoutils.hpp new file mode 100644 index 0000000..058c98d --- /dev/null +++ b/denoise/dctdenoising/demoutils.hpp @@ -0,0 +1,19 @@ +// +// Created by Nicola Pierazzo on 21/10/15. +// + +#ifndef IMGUTILS_DEMOUTILS_HPP +#define IMGUTILS_DEMOUTILS_HPP + +#include "Image.hpp" +#include + +namespace imgutils { + +const char *pick_option(int *c, char **v, const char *o, const char *d); +Image read_image(const std::string& filename); +void save_image(const Image& image, const std::string& filename); + +} // namespace imgutils + +#endif //IMGUTILS_DEMOUTILS_HPP diff --git a/denoise/dctdenoising/images/cinput.png b/denoise/dctdenoising/images/cinput.png new file mode 100644 index 0000000..7c563ff Binary files /dev/null and b/denoise/dctdenoising/images/cinput.png differ diff --git a/denoise/dctdenoising/images/noisy.tiff b/denoise/dctdenoising/images/noisy.tiff new file mode 100644 index 0000000..c5567ca Binary files /dev/null and b/denoise/dctdenoising/images/noisy.tiff differ diff --git a/denoise/dctdenoising/main.cpp b/denoise/dctdenoising/main.cpp new file mode 100644 index 0000000..3a9e008 --- /dev/null +++ b/denoise/dctdenoising/main.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2016, Gabriele Facciolo + * Nicola Pierazzo + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "DCTdenoising.h" +#include "utils.hpp" +#include "demoutils.hpp" + +using imgutils::pick_option; +using imgutils::read_image; +using imgutils::save_image; +using imgutils::Image; +using std::cerr; +using std::endl; +using std::move; +using std::vector; + +/** + * @file main.cpp + * @brief Main executable file. + * + * @author Gabriele Facciolo + * @author Nicola Pierazzo + */ +int main(int argc, char **argv) { + // read DCT denoising options + const bool usage = static_cast(pick_option(&argc, argv, "h", nullptr)); + const int dct_sz = atoi(pick_option(&argc, argv, "w", "8")); + const char *second_step_guide = pick_option(&argc, argv, "2", ""); + const bool no_second_step = static_cast(pick_option(&argc, argv, "1", NULL)); + const bool no_first_step = second_step_guide[0] != '\0'; + const bool adaptive_aggregation + = ! static_cast(pick_option(&argc, argv, "no_adaptive_aggregation", NULL)); + // read multiscaler options + const char *out_single = pick_option(&argc, argv, "single", ""); + const int scales = atoi(pick_option(&argc, argv, "n", "4")); + const float recompose_factor + = static_cast(atof(pick_option(&argc, argv, "c", ".5"))); + + //! Check if there is the right call for the algorithm + if (usage || argc < 2) { + cerr << "usage: " << argv[0] << " sigma [input [output]] [-1 | -2 guide] " + << "[-w patch_size (default 8)] [-c factor(.5)] [-n scales(4)] " + << "[-single output_singlescale] [-no_adaptive_aggregation]" << endl; + return usage ? EXIT_SUCCESS : EXIT_FAILURE; + } + + if (no_second_step && no_first_step) { + cerr << "You can't use -1 and -2 together." << endl; + return EXIT_FAILURE; + } + +#ifndef _OPENMP + cerr << "Warning: OpenMP not available. The algorithm will run in a single" << + " thread." << endl; +#endif + + // read input + Image noisy = read_image(argc > 2 ? argv[2] : "-"); + const float sigma = static_cast(atof(argv[1])); + // generate the DCT pyramid + vector noisy_p = decompose(noisy, scales); + vector guide_p, denoised_p; + if (no_first_step) { + Image guide = read_image(second_step_guide); + guide_p = decompose(guide, scales); + } + + // apply DCT denoising at each scale of the pyramid + for (int layer = 0; layer < scales; ++layer) { + // noise at the current scale is proportional to the number of pixels + float s = sigma * sqrt(static_cast(noisy_p[layer].pixels()) / noisy.pixels()); + if (!no_first_step) { + Image guide = DCTdenoising(noisy_p[layer], s, dct_sz, adaptive_aggregation); + guide_p.push_back(move(guide)); + } + if (!no_second_step) { + Image result = + DCTdenoisingGuided(noisy_p[layer], guide_p[layer], s, dct_sz, adaptive_aggregation); + denoised_p.push_back(move(result)); + } else { + denoised_p.push_back(move(guide_p[layer])); + } + } + + // recompose pyramid + if (strlen(out_single)) save_image(denoised_p[0], out_single); + Image result = recompose(denoised_p, recompose_factor); + + save_image(result, argc > 3 ? argv[3] : "TIFF:-"); + + return EXIT_SUCCESS; +} diff --git a/denoise/dctdenoising/man/dctdenoising.1 b/denoise/dctdenoising/man/dctdenoising.1 new file mode 100644 index 0000000..bd7c6b6 --- /dev/null +++ b/denoise/dctdenoising/man/dctdenoising.1 @@ -0,0 +1,67 @@ +.TH "IPOL tools User Manual" 1 "03 Feb 2018" "IPOL documentation" + +.SH NAME +dctdenoising + +.SH DESCRIPTION +Multiscale DCT image denoising. + +.SH SYNOPSIS +dctdenoising sigma [input [output]] [-1 | -2 guide] [-w patch_size (default 8)] [-c factor(.5)] [-n scales(4)] [-single output_singlescale] [-no_adaptive_aggregation] + +.SH OPTIONS +.TP +sigma +noise std (int, mandatory, recomended = 16) +.TP +input +name input graphics file (jpeg | png | tiff) +.TP +output +name output graphics file (jpeg | png | tiff) +.TP +-1|2 +guide (-1: only hard thresh., -2: provide guide) +.TP +-w +DCT denoising patch size (int, optional, default = 8) +.TP +-c +factor, multiscale: recomposition factor (double, optional, default = 0.5) +.TP +-n +scales, multiscale: number of scales (int, optional, default = 4) +.TP +-single +output singlescale, multiscale: save also one-scale result +.TP +-no_adaptive_aggregation +disable adaptive aggregation weights (bool, optional, default = false) + +.SH EXAMPLE +dctdenoising 16 noisy.tiff denoised.png + +.SH COPYRIGHT +Copyright : (C) 2017 IPOL Image Processing On Line http://www.ipol.im/ + Licence : GPL v3+, see GPLv3.txt + +.SH SEE ALSO + cjpeg (1), + djpeg (1) + +.SH CONTACTS +.TP +Author: +Nicola Pierazzo +.TP +Author: +Gabriele Facciolo +.TP +Based on the 2010 implementation of DCT denoising by: +Guoshen Yu +.TP +and +Guillermo Sapiro +.TP +Latest version available at: +https://github.com/gfacciol/DCTdenoising diff --git a/denoise/dctdenoising/utils.cpp b/denoise/dctdenoising/utils.cpp new file mode 100644 index 0000000..3ec115d --- /dev/null +++ b/denoise/dctdenoising/utils.cpp @@ -0,0 +1,294 @@ +// +// Created by Nicola Pierazzo on 21/10/15. +// + +#include +#include +#include +#include +#include +#include "utils.hpp" + +using std::pair; +using std::sqrt; +using std::vector; +using std::move; +using std::max; +using std::min; + +namespace imgutils { + +// use isometric DCT for multiscale decompose/recompose +#define ISOMETRIC_DCT + +inline int SymmetricCoordinate(int pos, int size) { + if (pos < 0) pos = -pos - 1; + if (pos >= 2 * size) pos %= 2 * size; + if (pos >= size) pos = 2 * size - 1 - pos; + return pos; +} + +/*! \brief Compute best tiling of image in at most ntiles + * + * Returns the pair: rows, columns + */ +pair ComputeTiling(int rows, int columns, int ntiles) { + // The objective is extract ntiles square-ish tiles + // For a square image the optimal number of rows is sqrt(ntiles) + // The ratio rows/columns permits to handle rectangular images + float best_r = sqrt(static_cast(ntiles * rows) / columns); + int r_low = static_cast(best_r); + int r_up = r_low + 1; + if (r_low < 1) return {1, ntiles}; // single row + if (r_up > ntiles) return {ntiles, 1}; // single column + // look for the nearest integer divisors of ntiles + while (ntiles % r_low != 0) --r_low; + while (ntiles % r_up != 0) ++r_up; + // At this point there are two possible tilings: + // {r_low, ntiles / r_low} and {r_up, ntiles / r_up}. + // We need to select the best. + // To do that, we consider the shape of the tiles. + // In the first case, the tiles are roughly + // {rows / r_low, columns * r_low / ntiles} pixels. + // In the second case, the tiles are + // {rows / r_up, columns * r_up / ntiles} pixels. + // Since r_low <= best_r <= r_up the first tile will have i + // more rows than columns and vice-versa. + // + // To select the best case we consider the ratio between the + // lengths of the longer and the shorter edge of a tile. + // The closer this ratio is to 1, the "squarer" the tile will be. + // In other words, we select the first tiling if + // (rows / r_low) / (columns * r_low / ntiles) < + // (columns * r_up / ntiles) / (rows / r_up) + // That is equivalent to (all values are > 0): + // rows * ntiles < r_up * r_low * columns + if (r_up * r_low * columns > ntiles * rows) { + return {r_low, ntiles / r_low}; + } else { + return {r_up, ntiles / r_up}; + } +} + +/*! \brief Split image in tiles + * + * Returns a vector containing tiling.first x tiling.sencond images + * each padded by pad_* pixels. Tiles are stored in lexicographic order. + * Padding outside the image is done by symmetrization + */ +vector SplitTiles(const Image &src, int pad_before, int pad_after, + pair tiling) { + vector result; + for (int tr = 0; tr < tiling.first; ++tr) { + int rstart = src.rows() * tr / tiling.first - pad_before; + int rend = src.rows() * (tr + 1) / tiling.first + pad_after; + for (int tc = 0; tc < tiling.second; ++tc) { + int cstart = src.columns() * tc / tiling.second - pad_before; + int cend = src.columns() * (tc + 1) / tiling.second + pad_after; + // copy image to tile using the above computed limits + Image tile(rend - rstart, cend - cstart, src.channels()); + for (int ch = 0; ch < src.channels(); ++ch) { + for (int row = rstart; row < rend; ++row) { + for (int col = cstart; col < cend; ++col) { + tile.val(col - cstart, row - rstart, ch) = src.val( + SymmetricCoordinate(col, src.columns()), + SymmetricCoordinate(row, src.rows()), + ch); + } + } + } + result.push_back(move(tile)); + } + } + return result; +} + +/*! \brief Recompose tiles produced by SplitTiles + * + * Returns an image resulting of recomposing the tiling + * padded margins are averaged in the result + */ +Image MergeTiles(const vector> &src, pair shape, + int pad_before, int pad_after, pair tiling) { + int channels = src[0].first.channels(); + Image result(shape.first, shape.second, channels); + Image weights(shape.first, shape.second); + auto tile = src.begin(); + for (int tr = 0; tr < tiling.first; ++tr) { + int rstart = shape.first * tr / tiling.first - pad_before; + int rend = shape.first * (tr + 1) / tiling.first + pad_after; + for (int tc = 0; tc < tiling.second; ++tc) { + int cstart = shape.second * tc / tiling.second - pad_before; + int cend = shape.second * (tc + 1) / tiling.second + pad_after; + // copy tile to image using the above computed limits + for (int ch = 0; ch < channels; ++ch) { + for (int row = max(0, rstart); row < min(shape.first, rend); ++row) { + for (int col = max(0, cstart); col < min(shape.second, cend); ++col) { + result.val(col, row, ch) += + tile->first.val(col - cstart, row - rstart, ch); + } + } + } + // image weight due to the padding + for (int row = max(0, rstart); row < min(shape.first, rend); ++row) { + for (int col = max(0, cstart); col < min(shape.second, cend); ++col) { + weights.val(col, row) += tile->second.val(col - cstart, row - rstart); + } + } + ++tile; + } + } + // normalize by the weight + for (int ch = 0; ch < channels; ++ch) { + for (int row = 0; row < shape.first; ++row) { + for (int col = 0; col < shape.second; ++col) { + result.val(col, row, ch) /= weights.val(col, row); + } + } + } + return result; +} + +/*! \brief 2D DCT transform of image (channel-wise) + * + * Operates over the img in-place + * Computes the normalized but not orthogonal 2D DCT + */ +void dct_inplace(Image &img) { + int n[] = {img.rows(), img.columns()}; + fftwf_r2r_kind dct2[] = {FFTW_REDFT10, FFTW_REDFT10}; + fftwf_plan plan = fftwf_plan_many_r2r(2, n, img.channels(), img.data(), NULL, + 1, img.pixels(), img.data(), NULL, 1, + img.pixels(), dct2, FFTW_ESTIMATE); + fftwf_execute(plan); + fftwf_destroy_plan(plan); + +#ifdef ISOMETRIC_DCT + ////> isometric normalization + // this normalization (and scaling) affects several other functions: + // idct_inplace, decompose, recompose + // but they can all be removed only by applying the Normalization below + double norm_factor = sqrt(.25f / (img.rows() * img.columns())); + for (int ch = 0; ch < img.channels(); ++ch) { + for (int row = 0; row < img.rows(); ++row) { + img.val(0, row, ch) /= sqrt(2.f); + for (int col = 0; col < img.columns(); ++col) { + img.val(col, row, ch) *= norm_factor; + } + } + for (int col = 0; col < img.columns(); ++col) { + img.val(col, 0, ch) /= sqrt(2.f); + } + } +#else + ////> Normalization + for (int i = 0; i < img.samples(); ++i) { + img.val(i) /= 4 * img.pixels(); + } +#endif +} + +/*! \brief 2D inverse DCT transform of image (channel-wise) + * + * Operates over the img in-place + */ +void idct_inplace(Image &img) { +#ifdef ISOMETRIC_DCT + ////> isometric normalization + long double norm_factor = sqrt(.25f / (img.rows() * img.columns())); + for (int ch = 0; ch < img.channels(); ++ch) { + for (int row = 0; row < img.rows(); ++row) { + img.val(0, row, ch) *= sqrt(2.f); + for (int col = 0; col < img.columns(); ++col) { + img.val(col, row, ch) *= norm_factor; + } + } + for (int col = 0; col < img.columns(); ++col) { + img.val(col, 0, ch) *= sqrt(2.f); + } + } +#endif + + int n[] = {img.rows(), img.columns()}; + fftwf_r2r_kind idct2[] = {FFTW_REDFT01, FFTW_REDFT01}; + fftwf_plan plan = fftwf_plan_many_r2r(2, n, img.channels(), img.data(), NULL, + 1, img.pixels(), img.data(), NULL, 1, + img.pixels(), idct2, FFTW_ESTIMATE); + fftwf_execute(plan); + fftwf_destroy_plan(plan); +} + +/*! \brief Dyadic DCT pyramid decomposition. + * + * Returns a vector containing #levels images + */ +vector decompose(const Image &img, int levels) { + vector pyramid; + Image freq = img.copy(); + dct_inplace(freq); + int h{freq.rows()}, w{freq.columns()}; + for (int i = 0; i < levels; ++i) { + // Copy data + Image layer(h, w, freq.channels()); +#ifdef ISOMETRIC_DCT + ////> isometric normalization scaling + const double scaling = std::sqrt((double)(w*h)/((double)(img.rows()*img.columns()))); +#else + const double scaling = 1.0; +#endif + for (int ch = 0; ch < freq.channels(); ++ch) { + for (int r = 0; r < h; ++r) { + for (int c = 0; c < w; ++c) { + layer.val(c, r, ch) = freq.val(c, r, ch) * scaling; + } + } + } + // Inverse DCT + idct_inplace(layer); + w /= 2; + h /= 2; + pyramid.push_back(move(layer)); + } + return pyramid; +} + +/*! \brief Dyadic DCT pyramid conservative recomposition. + * + * Takes a vector containing the layers of the pyramid. Computes + * the transform of each layer and recomposes the frequencies in a + * fine-to-coarse fashion, copying into the final result only the + * lowest recompose_factor frequencies of each coarse layer. + * After inverting the DCT, returns the resulting image. + */ +Image recompose(const vector &pyramid, float recompose_factor) { + // Use the bigger image to determine width, height and number of channels + Image output = pyramid[0].copy(); + // Perform the DCT + dct_inplace(output); + + for (int i = 1; i < static_cast(pyramid.size()); ++i) { + // Read level i of the pyramid + Image layer = pyramid[i].copy(); + // Perform the DCT + dct_inplace(layer); +#ifdef ISOMETRIC_DCT + ////> isometric normalization scaling + const double scaling = std::sqrt((double)(output.rows()*output.columns())/((double)(layer.rows()*layer.columns()))); +#else + const double scaling = 1.0; +#endif + // Copy data (selected by recompose_factor) + for (int ch = 0; ch < layer.channels(); ++ch) { + for (int r = 0; r < layer.rows() * recompose_factor; ++r) { + for (int c = 0; c < layer.columns() * recompose_factor; ++c) { + output.val(c, r, ch) = layer.val(c, r, ch) * scaling; + } + } + } + } + // IDCT of the output image + idct_inplace(output); + return output; +} + +} // namespace imgutils diff --git a/denoise/dctdenoising/utils.hpp b/denoise/dctdenoising/utils.hpp new file mode 100644 index 0000000..7585c09 --- /dev/null +++ b/denoise/dctdenoising/utils.hpp @@ -0,0 +1,27 @@ +// +// Created by Nicola Pierazzo on 21/10/15. +// + +#ifndef IMGUTILS_UTILS_HPP +#define IMGUTILS_UTILS_HPP + +#include "Image.hpp" +#include +#include + +namespace imgutils { + +std::pair ComputeTiling(int rows, int columns, int tiles); +std::vector SplitTiles(const Image &src, int pad_before, int pad_after, + std::pair tiling); +Image MergeTiles(const std::vector> &src, + std::pair shape, int pad_before, int pad_after, + std::pair tiling); +void dct_inplace(Image &img); +void idct_inplace(Image &img); +std::vector decompose(const Image &img, int levels); +Image recompose(const std::vector &pyramid, float recompose_factor); + +} // namespace imgutils + +#endif //IMGUTILS_UTILS_HPP diff --git a/denoise/ksvd/CMakeLists.txt b/denoise/ksvd/CMakeLists.txt new file mode 100644 index 0000000..9409ac2 --- /dev/null +++ b/denoise/ksvd/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 2.8) +project(ksvd) + +# The build type "Release" adds some optimizations +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE "Release") +endif () + +# Are we using gcc? +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC on MacOs needs this option to use the clang assembler + if (APPLE) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wa,-q") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-q") + endif () + # Optimize to the current CPU and enable warnings + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -Wall -Wextra") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra") +endif () + +# Enable C99 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") +else () + set (CMAKE_C_STANDARD 99) +endif () + +# Enable OpenMP +find_package (OpenMP REQUIRED) +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + +find_path (IIO_INCLUDE_DIR iio.h) +find_library (IIO_LIBRARIES NAMES iio) +include_directories (SYSTEM ${iio_INCLUDE_DIR}) +link_libraries (${IIO_LIBRARIES}) +if (NOT IIO_INCLUDE_DIR) + message (FATAL_ERROR "IIO not found.") +endif () + +set(SOURCE_FILES + ksvd.cpp + ksvd.h + lib_ormp.cpp + lib_ormp.h + lib_svd.cpp + lib_svd.h + main.cpp + utilities.cpp + utilities.h) + +add_executable(ksvd ${SOURCE_FILES}) + diff --git a/denoise/ksvd/README.txt b/denoise/ksvd/README.txt new file mode 100644 index 0000000..de18ff4 --- /dev/null +++ b/denoise/ksvd/README.txt @@ -0,0 +1,61 @@ +% K-SVD image denoising. + +# ABOUT + +* Author : Marc Lebrun +* Copyright : (C) 2011 IPOL Image Processing On Line http://www.ipol.im/ +* Licence : GPL v3+, see GPLv3.txt + +# OVERVIEW + +This source code provides an implementation of the K-SVD image denoising. + +# UNIX/LINUX/MAC USER GUIDE + +The code is compilable on Unix/Linux and Mac OS. + +- Compilation. +Automated compilation requires the make program. + +- Library. +This code requires the libpng library. + +- Image format. +Only the PNG format is supported. + +------------------------------------------------------------------------- +Usage: +1. Download the code package and extract it. Go to that directory. + +2. Compile the source code (on Unix/Linux/Mac OS). +There are two ways to compile the code. +(1) RECOMMENDED, with Open Multi-Processing multithread parallelization +(http://openmp.org/). Roughly speaking, it accelerates the program using the +multiple processors in the computer. Run +make OMP=1 + +OR +(2) If the complier does not support OpenMp, run +make + +3. Run K-SVD image denoising. +./ksvd +(1) you can moreover want to compute the bias (algorithm applied to the original +image). To do this, use doBias = true. +There are multiple ways to run the code: +(2) if you want to use the speed-up trick, use doSpeedUp = true (recommanded) +(3) if you want to obtain the best PSNR result, use doBestPSNR = true + +Example, run +./ksvd cinput.png 10 ImNoisy.png ImDenoised.png ImDiff.png ImBias.png ImDiffBias.png 0 1 0 + + + +# ABOUT THIS FILE + +Copyright 2011 IPOL Image Processing On Line http://www.ipol.im/ + +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without any warranty. diff --git a/denoise/ksvd/VERSION b/denoise/ksvd/VERSION new file mode 100644 index 0000000..0808d7b --- /dev/null +++ b/denoise/ksvd/VERSION @@ -0,0 +1 @@ +0.20180203 diff --git a/denoise/ksvd/ksvd.cpp b/denoise/ksvd/ksvd.cpp new file mode 100644 index 0000000..78d2e30 --- /dev/null +++ b/denoise/ksvd/ksvd.cpp @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2011, Marc Lebrun + * All rights reserved. + * + * This program is free software: you can use, modify and/or + * redistribute it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later + * version. You should have received a copy of this license along + * this program. If not, see . + */ + + +/** + * @file ksvd.cpp + * @brief K-SVD denoising functions + * + * @author Marc Lebrun + **/ + +#include +#include +#include + +#include "ksvd.h" +#include "lib_ormp.h" +#include "lib_svd.h" + +#ifdef _OPENMP +#endif + +using namespace std; + +/** + * @brief Run K-SVD + * + * @param sigma: noise value; + * @param img_noisy : pointer to an allocated array of pixel, + * containing the noisy image; + * @param img_denoised : pointer to an allocated array which + * will contain the final denoised image; + * @param width : width of the image; + * @param height : height of the image; + * @param chnls : number of channels of the image + * @param useT : if true, use the trick acceleration in order to + * speed up the algorithm by learning the dictionary + * on a sub sample of the full patches. + * + * @return none. + **/ +void ksvd_ipol(const double sigma, + double *img_noisy, + float *img_denoised, + const unsigned width, + const unsigned height, + const unsigned chnls, + const bool useT) { + //! Initializations + const unsigned N1 = (sigma * 255.0l <= 20 ? 5 : (sigma * 255.0l <= 60 ? 7 + : 9)); //! Size of patches + const unsigned N2 = 256; //! size of the dictionary + const unsigned N1_2 = N1 * N1; + const unsigned N_iter = 15; //! Number of iterations + const unsigned + T = (sigma * 255.0l > 40.0l ? 8 : (sigma * 255.0l > 10.0l ? 16 : 32)); + const double gamma = 5.25l; + const unsigned num_patches = (width - N1 + 1) * (height - N1 + 1); + //! C = sqrt(1/(chnls * N1 * N1)*chi2inv(0.93,chnls * N1 * N1)); + const double C = (chnls == 1 ? (N1 == 5 ? 1.2017729876383829 : (N1 == 7 + ? 1.1456550151825420 + : 1.1139195378939404)) + : (N1 == 5 ? 1.1182997771678573 : (N1 == 7 + ? 1.0849724948297015 + : 1.0662877194412401))); + + //! Assuming that img_noisy \isin [0, 255] + for (unsigned k = 0; k < width * height * chnls; k++) + img_noisy[k] /= 255.0l; + + //! Declarations + matD_t patches(num_patches, vecD_t(N1_2 * chnls)); + matD_t dictionary(N2, vecD_t(N1_2 * chnls)); + + //! Decompose the image into patches + im2patches(img_noisy, patches, width, height, chnls, N1); + + if (useT) cerr << "Acceleration trick used" << endl; + //! Keep (1 / T) patches to learn the dictionary + if (useT || floor(num_patches / T) > N2) { + //! Obtain less patches (divided by T) + const unsigned num_sub_patches = (unsigned) floor(num_patches / T); + matD_t sub_patches(num_sub_patches, vecD_t(N1_2 * chnls)); + for (unsigned k = 0; k < num_sub_patches; k++) + sub_patches[k] = patches[k * T]; + + //! Obtain the initial dictionary + obtain_dict(dictionary, sub_patches); + + //! Learn dictionary + ksvd_process(img_noisy, img_denoised, sub_patches, dictionary, \ + sigma, N1, N2, N_iter, gamma, C, width, \ + height, chnls, false); + + //! Denoising + ksvd_process(img_noisy, img_denoised, patches, dictionary, \ + sigma, N1, N2, 1, gamma, C, width, \ + height, chnls, true); + } else { + //! Obtain the initial dictionary + obtain_dict(dictionary, patches); + + //! Denoising + ksvd_process(img_noisy, img_denoised, patches, dictionary, sigma, \ + N1, N2, N_iter, gamma, C, width, height, chnls, true); + } + + //! Back to the [0, 255] for the image value + for (unsigned k = 0; k < width * height * chnls; k++) + img_denoised[k] *= 255.0f; +} + +/** + * @brief Decompose the image into patches + * + * @param img : pointer to an allocated array containing + * the image to decompose; + * @param patches : will contain all patches (whose size + * is N x N) of img; + * @param width : width of img; + * @param height : height of img; + * @param chnls : number of channels of img. + * + * @return none. + **/ +void im2patches(const double *img, + matD_t &patches, + const unsigned width, + const unsigned height, + const unsigned chnls, + const unsigned N) { + //! Declarations + const unsigned h_p = height - N + 1; + const unsigned wh_p = (width - N + 1) * h_p; + + matD_t::iterator it_p = patches.begin(); + for (unsigned k = 0; k < wh_p; k++, it_p++) { + unsigned dk = k / h_p + (k % h_p) * width; + for (unsigned c = 0; c < chnls; c++) { + unsigned dc = c * width * height + dk; + iterD_t it = (*it_p).begin() + c * N * N; + for (unsigned p = 0; p < N; p++, dc++) + for (unsigned q = 0; q < N; q++, it++) + (*it) = (double) img[dc + q * width]; + } + } +} + +/** + * @brief Reconstruct images by aggregating patches of size N x N + * + * @param patches : matrix containing patches; + * @param img : pointer to an allocated array + * which will contain the denoised image; + * @param img_ref : pointer to an allocated array which + * contains the noisy image; + * @param with : width of both images; + * @param height : height of both images; + * @param chnls : number of channels of both images; + * @param lambda : weighting coefficient used for the addition + * of img_ref to img; + * @param N : size of patches (N x N). + * + * @return none. + **/ +void patches2im(matD_t &patches, + float *img, + const double *img_ref, + const unsigned width, + const unsigned height, + const unsigned chnls, + const double lambda, + const unsigned N) { + //! Declarations + const unsigned h_p = height - N + 1; + const unsigned wh_p = (width - N + 1) * h_p; + + for (unsigned c = 0; c < chnls; c++) { + vecD_t denominator(height * width, 0.0f); + vecD_t numerator(height * width, 0.0f); + matD_t::iterator it_p = patches.begin(); + + //! Aggregation + for (unsigned k = 0; k < wh_p; k++, it_p++) { + unsigned ind = (k % h_p) * width + k / h_p; + iterD_t it = (*it_p).begin() + c * N * N; + for (unsigned q = 0; q < N; q++, ind++) + for (unsigned p = 0; p < N; p++, it++) { + numerator[p * width + ind] += (*it); + denominator[p * width + ind]++; + } + } + + //! Weighting + unsigned dc_i = c * width * height; + iterD_t it_d = denominator.begin(); + iterD_t it_n = numerator.begin(); + for (unsigned k = 0; k < height * width; k++, dc_i++, it_d++, it_n++) + img[dc_i] = ((*it_n) + lambda * img_ref[dc_i]) / ((*it_d) + lambda); + } +} + +/** + * @brief Obtain random permutation for a tabular + * + * @param perm : will contain a random sequence of [1, ..., N] + * where N is the size of perm. + * + * @return none. + **/ +void randperm(vecU_t &perm) { + //! Initializations + const unsigned N = perm.size(); + vecU_t tmp(N + 1, 0); + tmp[1] = 1; + srand(unsigned(time(NULL))); + + for (unsigned i = 2; i < N + 1; i++) { + unsigned j = rand() % i + 1; + tmp[i] = tmp[j]; + tmp[j] = i; + } + + iterU_t it_t = tmp.begin() + 1; + for (iterU_t it_p = perm.begin(); it_p < perm.end(); it_p++, it_t++) + (*it_p) = (*it_t) - 1; +} + +/** + * @brief Obtain the initial dictionary, which + * its columns are normalized + * + * @param dictionary : will contain random patches from patches, + * with its columns normalized; + * @param patches : contains all patches in the noisy image. + * + * @return none. + **/ +void obtain_dict(matD_t &dictionary, matD_t const &patches) { + //! Declarations + vecU_t perm(patches.size()); + + //! Obtain random indices + randperm(perm); + + //! Getting the initial random dictionary from patches + iterU_t it_p = perm.begin(); + for (matD_t::iterator it_d = dictionary.begin(); it_d < dictionary.end(); + it_d++, it_p++) + (*it_d) = patches[*it_p]; + + //! Normalize column + double norm; + for (matD_t::iterator it = dictionary.begin(); it < dictionary.end(); it++) { + norm = 0.0l; + for (iterD_t it_d = (*it).begin(); it_d < (*it).end(); it_d++) + norm += (*it_d) * (*it_d); + + norm = 1 / sqrtl(norm); + for (iterD_t it_d = (*it).begin(); it_d < (*it).end(); it_d++) + (*it_d) *= norm; + } +} + +/** + * @brief Apply the whole algorithm of K-SVD + * + * @param img_noisy : pointer to an allocated array containing + * the original noisy image; + * @param img_denoised : pointer to an allocated array which + * will contain the final denoised image; + * @param patches : matrix containing all patches including in + * img_noisy; + * @param dictionary : initial random dictionary, which will be + * updated in each iteration of the algo; + * @param sigma : noise value; + * @param N1 : size of patches (N1 x N1); + * @param N2 : number of atoms in the dictionary; + * @param N_iter : number of iteration; + * @param gamma : value used in the correction matrix in the + * case of color image; + * @param C : coefficient used for the stopping criteria of + * the ORMP; + * @param width : width of both images; + * @param height : height of both images; + * @param chnls : number of channels of both images; + * @param doReconstruction : if true, do the reconstruction of + * the final denoised image from patches + * (only in the case of the acceleration + * trick). + * + * @return none. + **/ +void ksvd_process(const double *img_noisy, + float *img_denoised, + matD_t &patches, + matD_t &dictionary, + const double sigma, + const unsigned N1, + const unsigned N2, + const unsigned N_iter, + const double gamma, + const double C, + const unsigned width, + const unsigned height, + const unsigned chnls, + const bool doReconstruction) { + //! Declarations + const unsigned N1_2 = N1 * N1; + const double corr = (sqrtl(1.0l + gamma) - 1.0l) / ((double) N1_2); + const double eps = ((double) (chnls * N1_2)) * C * C * sigma * sigma; + const unsigned h_p = patches[0].size(); + const unsigned w_p = patches.size(); + + //! Mat & Vec initializations + matD_t dict_ormp(N2, vecD_t(h_p, 0.0l)); + matD_t patches_ormp(w_p, vecD_t(h_p, 0.0l)); + matD_t tmp(h_p, vecD_t(N2, 0.0l)); + vecD_t normCol(N2); + matD_t Corr(h_p, vecD_t(h_p, 0.0l)); + vecD_t U(h_p); + vecD_t V; + matD_t E(w_p, vecD_t(h_p)); + + //! Vector for ORMP + matD_t ormp_val(w_p, vecD_t()); + matU_t ormp_ind(w_p, vecU_t()); + matD_t res_ormp(N2, vecD_t(w_p)); + matU_t omega_table(N2, vecU_t()); + vecU_t omega_size_table(N2, 0); + matD_t alpha(N2, vecD_t()); + + //! To avoid reallocation of memory + for (unsigned k = 0; k < w_p; k++) { + ormp_val[k].reserve(N2); + ormp_ind[k].reserve(N2); + } + + for (matU_t::iterator it = omega_table.begin(); it < omega_table.end(); it++) + it->reserve(w_p); + + V.reserve(w_p); + + //! Correcting matrix + for (unsigned i = 0; i < h_p; i++) + Corr[i][i] = 1.0l; + + for (unsigned c = 0; c < chnls; c++) { + matD_t::iterator it_Corr = Corr.begin() + N1_2 * c; + for (unsigned i = 0; i < N1_2; i++, it_Corr++) { + iterD_t it = it_Corr->begin() + N1_2 * c; + for (unsigned j = 0; j < N1_2; j++, it++) + (*it) += corr; + } + } + +#pragma omp parallel for + for (unsigned j = 0; j < w_p; j++) { + for (unsigned c = 0; c < chnls; c++) { + iterD_t it_ormp = patches_ormp[j].begin() + c * N1_2; + iterD_t it = patches[j].begin() + c * N1_2; + for (unsigned i = 0; i < N1_2; i++, it++, it_ormp++) { + double val = 0.0l; + iterD_t it_tmp = patches[j].begin() + c * N1_2; + for (unsigned k = 0; k < N1_2; k++, it_tmp++) + val += corr * (*it_tmp); + (*it_ormp) = val + (*it); + } + } + } + + //! Big loop + for (unsigned iter = 0; iter < N_iter; iter++) { + //! Sparse coding + if (doReconstruction) + cerr << "Final Step :" << endl; + else + cerr << "Step " << iter + 1 << ":" << endl; + cerr << " - Sparse coding" << endl; + + for (unsigned i = 0; i < h_p; i++) { + iterD_t it_tmp = tmp[i].begin(); + for (unsigned j = 0; j < N2; j++, it_tmp++) { + double val = 0.0l; + iterD_t it_corr_i = Corr[i].begin(); + iterD_t it_dict_j = dictionary[j].begin(); + for (unsigned k = 0; k < h_p; k++, it_corr_i++, it_dict_j++) + val += (*it_corr_i) * (*it_dict_j); + (*it_tmp) = val * val; + } + } + + iterD_t it_normCol = normCol.begin(); + for (unsigned j = 0; j < N2; j++, it_normCol++) { + double val = 0.0l; + for (unsigned i = 0; i < h_p; i++) + val += tmp[i][j]; + (*it_normCol) = 1.0l / sqrtl(val); + } + + for (unsigned i = 0; i < h_p; i++) { + iterD_t it_normCol_j = normCol.begin(); + for (unsigned j = 0; j < N2; j++, it_normCol_j++) { + double val = 0.0l; + iterD_t it_corr_i = Corr[i].begin(); + iterD_t it_dict_j = dictionary[j].begin(); + for (unsigned k = 0; k < h_p; k++, it_corr_i++, it_dict_j++) + val += (*it_corr_i) * (*it_dict_j); + dict_ormp[j][i] = val * (*it_normCol_j); + } + } + + //! ORMP process + cerr << " - ORMP process" << endl; + ormp_process(patches_ormp, dict_ormp, ormp_ind, ormp_val, N2, eps); + + for (unsigned i = 0; i < w_p; i++) { + iterU_t it_ind = ormp_ind[i].begin(); + iterD_t it_val = ormp_val[i].begin(); + const unsigned size = ormp_val[i].size(); + for (unsigned j = 0; j < size; j++, it_ind++, it_val++) + (*it_val) *= normCol[*it_ind]; + } + + //! Residus + for (unsigned i = 0; i < N2; i++) { + omega_size_table[i] = 0; + omega_table[i].clear(); + alpha[i].clear(); + for (iterD_t it = res_ormp[i].begin(); it < res_ormp[i].end(); it++) + *it = 0.0l; + } + + for (unsigned i = 0; i < w_p; i++) { + iterU_t it_ind = ormp_ind[i].begin(); + iterD_t it_val = ormp_val[i].begin(); + for (unsigned j = 0; j < ormp_val[i].size(); j++, it_ind++, it_val++) { + omega_table[*it_ind].push_back(i); + omega_size_table[*it_ind]++; + alpha[*it_ind].push_back(*it_val); + res_ormp[*it_ind][i] = *it_val; + } + } + + //! Dictionary update + cerr << " - Dictionary update" << endl; + for (unsigned l = 0; l < N2; l++) { + //! Initializations + const unsigned omega_size = omega_size_table[l]; + iterD_t it_dict_l = dictionary[l].begin(); + iterD_t it_alpha_l = alpha[l].begin(); + iterU_t it_omega_l = omega_table[l].begin(); + U.assign(U.size(), 0.0l); + + if (omega_size > 0) { + iterD_t it_a = it_alpha_l; + iterU_t it_o = it_omega_l; + for (unsigned j = 0; j < omega_size; j++, it_a++, it_o++) { + iterD_t it_d = it_dict_l; + iterD_t it_e = E[j].begin(); + iterD_t it_p = patches[*it_o].begin(); + for (unsigned i = 0; i < h_p; i++, it_d++, it_e++, it_p++) + (*it_e) = (*it_p) + (*it_d) * (*it_a); + } + + matD_t::iterator it_res = res_ormp.begin(); + for (unsigned k = 0; k < N2; k++, it_res++) { + iterU_t it_o = it_omega_l; + iterD_t it_dict_k = dictionary[k].begin(); + for (unsigned j = 0; j < omega_size; j++, it_o++) { + const double val = (*it_res)[*it_o]; + if (fabs(val) > 0.0l) { + iterD_t it_d = it_dict_k; + iterD_t it_e = E[j].begin(); + for (unsigned i = 0; i < h_p; i++, it_d++, it_e++) + (*it_e) -= (*it_d) * val; + } + } + } + + //! SVD truncated + V.resize(omega_size); + double S = svd_trunc(E, U, V); + + dictionary[l] = U; + + it_a = it_alpha_l; + iterD_t it_v = V.begin(); + it_o = it_omega_l; + for (unsigned j = 0; j < omega_size; j++, it_a++, it_v++, it_o++) + res_ormp[l][*it_o] = (*it_a) = (*it_v) * S; + } + } + cerr << " - done." << endl; + } + + if (doReconstruction) { + //! Final patches estimations + cerr << " - Aggregation" << endl; + for (matD_t::iterator it = patches.begin(); it < patches.end(); it++) + for (iterD_t it_p = it->begin(); it_p < it->end(); it_p++) + *it_p = 0.0; + +#pragma omp parallel for + for (unsigned l = 0; l < N2; l++) { + iterD_t it_a = alpha[l].begin(); + iterU_t it_o = omega_table[l].begin(); + const unsigned omega_size = omega_size_table[l]; + for (unsigned j = 0; j < omega_size; j++, it_a++, it_o++) { + const double val = (*it_a); + iterD_t it_d = dictionary[l].begin(); + iterD_t it_p = patches[*it_o].begin(); + for (unsigned k = 0; k < h_p; k++, it_d++, it_p++) + (*it_p) += (*it_d) * val; + } + } + + //! First, obtention of the denoised image without weighting with lambda + patches2im(patches, + img_denoised, + img_noisy, + width, + height, + chnls, + 0.0, + N1); + + //! Second, obtention of lambda from the norm between img_denoised and img_noisy + double d = 0.0; + for (unsigned k = 0; k < width * height * chnls; k++) + d += (img_denoised[k] - img_noisy[k]) * (img_denoised[k] - img_noisy[k]); + d /= (height * width * chnls * sigma * sigma); + const double lambda = abs(sqrtl(d) - 1.0); + + //! Finally, obtention of the denoised image with lambda + patches2im(patches, + img_denoised, + img_noisy, + width, + height, + chnls, + lambda, + N1); + } +} + + diff --git a/denoise/ksvd/ksvd.h b/denoise/ksvd/ksvd.h new file mode 100644 index 0000000..4e3091a --- /dev/null +++ b/denoise/ksvd/ksvd.h @@ -0,0 +1,68 @@ +/** + ksvd.h +**/ + +#ifndef KSVD_H_INCLUDED +#define KSVD_H_INCLUDED + +#include + +typedef std::vector > matD_t; +typedef std::vector > matU_t; +typedef std::vector vecD_t; +typedef std::vector vecU_t; +typedef std::vector::iterator iterD_t; +typedef std::vector::iterator iterU_t; + +//! Run K-SVD +void ksvd_ipol(const double sigma , + double * img_noisy , + float * img_denoised, + const unsigned width , + const unsigned height , + const unsigned chnls , + const bool useT ); + +//! Decompose the image into patches of size NxN +void im2patches(const double * img, + matD_t &patches, + const unsigned width, + const unsigned height, + const unsigned chnls, + const unsigned N); + +//! Reconstruct images by aggregating patches of size N x N +void patches2im(matD_t &patches, + float * img, + const double * img_ref, + const unsigned width, + const unsigned height, + const unsigned chnls, + const double lambda, + const unsigned N); + +//! Obtain random permutation for a tabular of size N +void randperm(vecU_t &perm); + + //! Obtain the initial dictionary, which + //! columns are normalized +void obtain_dict(matD_t &dictionary, + matD_t const& patches); + +//! Process K-SVD denoising +void ksvd_process(const double *img_noisy, + float * img_denoised, + matD_t &patches, + matD_t &dictionary, + const double sigma, + const unsigned N1, + const unsigned N2, + const unsigned N_iter, + const double gamma, + const double C, + const unsigned width, + const unsigned height, + const unsigned chnls, + const bool doReconstruction); + +#endif // KSVD_H_INCLUDED diff --git a/denoise/ksvd/lib_ormp.cpp b/denoise/ksvd/lib_ormp.cpp new file mode 100644 index 0000000..cac1f6e --- /dev/null +++ b/denoise/ksvd/lib_ormp.cpp @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2011, Marc Lebrun + * All rights reserved. + * + * This program is free software: you can use, modify and/or + * redistribute it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later + * version. You should have received a copy of this license along + * this program. If not, see . + */ + + /** + * @file lib_ormp.cpp + * @brief ORMP process, based on the SPAMS toolbox by Julien Mairal + * + * @author Marc Lebrun + **/ + +#include +#include + +#ifdef _OPENMP +#include +#endif + +#include "lib_ormp.h" + +using namespace std; + +#define MIN(a,b) (((a) > (b)) ? (b) : (a)) + +/** + * @brief compute an Orthogonal Recursive Matching Pursuit + * + * @param X : (Np x n) X[i] is the vector to be represented in D + * @param D : (k x n) dictionary (D[j] is the j-th atom) + * @param A : (k x Np) sparse coordinates : A[j][i] is the + * coordinate of X[i] on D[j] + **/ + +//! The matrix are accessed in column order i.e. X[i] is the i-th column of X +//! CONVENTION : we will denote by A_B the variable containing the +//! matrix transpose(A)*B (which contains the scalar products between the columns of A +//! and the columns of B. +void ormp_process(matD_t &X, + matD_t &D, + matU_t &ind_v, + matD_t &val_v, + unsigned L, + const double eps) +{ + //! Declarations + const unsigned n = X[0].size(); + const unsigned Np = X.size(); + const unsigned k = D.size(); + + //! Initializations + if (L <= 0) + return; + + L = MIN(n, MIN(L,k)); + + #pragma omp parallel shared(ind_v, val_v, X, D) + { + //! Declarations + vecD_t norm(k), scores(k), x_T(k); + matD_t A(L, vecD_t(L)), D_ELj(k, vecD_t(L)), D_D(k, vecD_t(k)), D_DLj(k, vecD_t(L)); + + //! Compute the scalar products between the atoms of the dictionary + for (unsigned j = 0; j < k; j++) + { + iterD_t it_D_D = D_D[j].begin(); + for (unsigned i = 0; i < k; i++, it_D_D++) + { + iterD_t D_i = D[i].begin(); + iterD_t D_j = D[j].begin(); + long double val = 0; + for (unsigned s = 0; s < n; s++, D_i++, D_j++) + val += (long double) (*D_i) * (long double) (*D_j); + (*it_D_D) = (double) val; + } + } + + #pragma omp for schedule(dynamic) nowait + for (unsigned i = 0; i < Np; ++i) + { + //! Initialization + ind_v[i].clear(); + val_v[i].clear(); + iterD_t X_i = X[i].begin(); + + //! Compute the norm of X[i] + long double normX = 0.0l; + for (iterD_t it_x = X_i; it_x < X[i].end(); it_x++) + normX += (long double) (*it_x) * (long double) (*it_x); + + //! Compute the scalar products between X[i] and the elements + //! of the dictionary + for (unsigned j = 0; j < k; j++) + { + iterD_t it_d = D[j].begin(); + iterD_t it_x = X_i; + long double val = 0.0l; + for (unsigned s = 0; s < n; s++, it_d++, it_x++) + val += (long double) (*it_d) * (long double) (*it_x); + x_T[j] = (double)val; + } + + coreORMP(D, D_D, scores, norm, A, D_ELj, D_DLj, x_T, + ind_v[i], val_v[i], eps, (double) normX); + + } //! End of I-lop + + } //! End of parallel section +} + +/** + * @brief Sub function of ormp_process + * + * @param D : dictionary; + * @param D_D : precomputed matrix D * D'; + * @param scores[i] = ^2 + * @param norm[i] = ||t_i^j||^2 = 1 - \sum_{p=0}^{j_1} ^2 + * @param A : sparse coordinates + * @param D_ELj[i][j] : equals at the end of the j-loop + * @param D_DLj[i][s] = + * @param x_T[i] = = - \sum_{p=0}^{j-1} + * @param ind : will contain index of atoms of D at the end of the j-loop + * @param coord[q] = \alpha_l_q = \sum_{p=q}^j a_{pq} : coordinate + * of x on d_l_q at the end of the j-loop + * @param eps : break condition + * @param normr = ||x||^2 - \sum_{p=0}^j ^2 + * + * @return none. + **/ +void coreORMP(matD_t &D, + matD_t &D_D, + vecD_t &scores, + vecD_t &norm, + matD_t &A, + matD_t &D_ELj, + matD_t &D_DLj, + vecD_t &x_T, + vecU_t &ind, + vecD_t &coord, + const double eps, + double normr) +{ + //! Declarations + const unsigned L = A.size(); + const unsigned p = D.size(); + + if (normr <= eps || L == 0) + return; + + //! Initializations + scores = x_T; + for (iterD_t it = norm.begin(); it < norm.end(); it++) + *it = 1.0l; + + for (matD_t::iterator it_A = A.begin(); it_A < A.end(); it_A++) + for (iterD_t it = it_A->begin(); it < it_A->end(); it++) + *it = 0.0l; + + //! Declarations + iterD_t A_j, it_A_j, it_D_ELj_lj, it_gs, x_T_i, norm_i, scores_i, it_A_j_tmp; + iterD_t D_lj, it_D_DLj; + matD_t::iterator it_D_ELj, it_A; + vecD_t x_el; + + //! Loop over j + unsigned j; + for (j = 0; j < L; j++) + { + //! Initialization + const unsigned lj = ind_fmax(scores); + A_j = A[j].begin(); + + //! Stop if we cannot inverse norm[lj] + if (norm[lj] < 1e-6) + break; + + const long double invNorm = 1.0l / (long double) sqrtl(norm[lj]); + const long double x_elj = (long double) x_T[lj] * invNorm; + const long double delta = x_elj * x_elj; + + x_el.push_back(x_elj); + coord.push_back(x_elj); //! The coordinate of x on the last chosen vector + normr = (double) ((long double) normr - delta);//! Update of the residual + ind.push_back(lj); //! Memorize the chosen index + + //! Gram-Schmidt Algorithm, Update of A + it_A_j = A_j; + it_D_ELj_lj = D_ELj[lj].begin(); + for (unsigned i = 0; i < j; i++, it_A_j++, it_D_ELj_lj++) + (*it_A_j) = (*it_D_ELj_lj); + + it_A_j = A_j; + for (unsigned i = 0; i < j; i++, it_A_j++) + { + long double sum = 0.0l; + it_A = A.begin() + i; + it_A_j_tmp = it_A_j; + + for (unsigned s = 0; s < j - i; s++, it_A++, it_A_j_tmp++) + sum -= (long double) (*it_A)[i] * (long double) (*it_A_j_tmp); + + (*it_A_j) = (double) (sum * invNorm); + } + (*it_A_j) = (double) invNorm; + + if (j == L-1 || (normr <= eps)) + { + j++; + break; + } + + //! Update of D_DLj + it_D_DLj = D_D[lj].begin(); + for (unsigned i = 0; i < p; i++, it_D_DLj++) + D_DLj[i][j] = (*it_D_DLj); + + //! Compute the scalar product D[j]_D[lj] and memorize it now + long double val = 0.0l; + D_lj = D[lj].begin(); + for (iterD_t D_j = D[j].begin(); D_j < D[j].end(); D_j++, D_lj++) + val += (long double) (*D_j) * (long double) (*D_lj); + D_DLj[j][j] = (double) val; + + //! Update of D_ELj, x_T, norm, and scores. + x_T_i = x_T.begin(); + norm_i = norm.begin(); + scores_i = scores.begin(); + it_D_ELj = D_ELj.begin(); + for (unsigned i = 0; i < p; i++, x_T_i++, norm_i++, scores_i++, it_D_ELj++) + { + long double val = 0; + it_A_j = A_j; + it_gs = D_DLj[i].begin(); + for (unsigned s = 0; s < j + 1; s++, it_A_j++, it_gs++) + val += (long double) (*it_A_j) * (long double) (*it_gs); + + (*it_D_ELj)[j] = (double) val; + (*x_T_i) = (double) ((long double) (*x_T_i) - x_elj * val); + (*norm_i) = (double) ((long double) (*norm_i) - val * val); + (*scores_i) = (double) ((long double) (*x_T_i) * (long double) (*x_T_i) \ + / (long double) (*norm_i)); + } + + for (iterU_t ind_s = ind.begin(); ind_s <= ind.begin() + j; ind_s++) + scores[*ind_s] = double(); + } + + //! Compute the final coordinates of x on the chosen atoms of the dictionary + iterD_t it_coord = coord.begin(); + for (unsigned i = 0; i < j; i++, it_coord++) + { + long double sum = 0; + matD_t::iterator it_a = A.begin() + i; + iterD_t it_x = x_el.begin() + i; + for (unsigned s = 0; s < j - i; s++, it_a++, it_x++) + sum += (long double) (*it_a)[i] * (long double) (*it_x); + (*it_coord) = (double) sum; + } +} + +/** + * @brief Find the index of the maximum absolute value + * of a table + * + * @param V : table of values. + * + * @return : return the index of the maximum absolute + * value of V. + **/ +unsigned ind_fmax(vecD_t &V) +{ + double val = 0.0f; + unsigned ind = 0; + iterD_t it_v; + unsigned j = 0; + + //! Find the index of the maximum of V + for (it_v = V.begin(); it_v < V.end(); it_v++, j++) + if (val < fabs(*it_v)) + { + val = fabs(*it_v); + ind = j; + } + + return ind; +} + + + diff --git a/denoise/ksvd/lib_ormp.h b/denoise/ksvd/lib_ormp.h new file mode 100644 index 0000000..77667f9 --- /dev/null +++ b/denoise/ksvd/lib_ormp.h @@ -0,0 +1,39 @@ +#ifndef LIBORMP_H_INCLUDED +#define LIBORMP_H_INCLUDED + +#include + +typedef std::vector > matD_t; +typedef std::vector > matU_t; +typedef std::vector vecD_t; +typedef std::vector vecU_t; +typedef std::vector::iterator iterD_t; +typedef std::vector::iterator iterU_t; + +//! ORMP process +void ormp_process(matD_t &X, + matD_t &D, + matU_t &ind_v, + matD_t &val_v, + unsigned L, + const double eps); + +//! sub function for ORMP process +void coreORMP(matD_t &D, + matD_t &D_D, + vecD_t &scores, + vecD_t &norm, + matD_t &A, + matD_t &D_ELj, + matD_t &D_DLj, + vecD_t &x_T, + vecU_t &ind, + vecD_t &coord, + const double eps2, + double normr); + +//! Find best ind +unsigned ind_fmax(vecD_t &V); + + +#endif // LIBORMP_H_INCLUDED diff --git a/denoise/ksvd/lib_svd.cpp b/denoise/ksvd/lib_svd.cpp new file mode 100644 index 0000000..0036084 --- /dev/null +++ b/denoise/ksvd/lib_svd.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2011, Marc Lebrun + * All rights reserved. + * + * This program is free software: you can use, modify and/or + * redistribute it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later + * version. You should have received a copy of this license along + * this program. If not, see . + */ + + +/** + * @file lib_svd.cpp + * @brief Process a truncated SVD by the power method + * + * @author Marc Lebrun + **/ + +#include "lib_svd.h" + +#include +#include + +/** + * @brief Process a truncated svd (only the largest singular + * value is processed). + * /!\ Warning: tX is transposed for convenience + * + * @param tX : (n, m) (so X is a m x n matrix); + * @param U (m) : will contain the new coefficients; + * @param V (n) : will contain the largest principal vectors. + * + * @return S, the largest singular value + **/ +double svd_trunc(mat_t &tX, + vec_t &U, + vec_t &V) +{ + //! Declarations + const double epsilon = 10e-6; + const unsigned max_iter = 100; + unsigned iter = 0; + bool go_on = true; + double S_old = 0; + double S = 0; + const unsigned m = U.size(); + const unsigned n = V.size(); + iter_t it_v, it_u, it_x; + + long double norm = 0.0l; + it_v = V.begin(); + for (unsigned j = 0; j < n; j++, it_v++) + { + long double val = 0.0l; + it_x = tX[j].begin(); + for (unsigned i = 0; i < m; i++, it_x++) + val += (long double) fabsl(*it_x); + (*it_v) = (double) val; + norm += val * val; + } + + long double s_inv = -1.0l / sqrt(norm); + + for (it_v = V.begin(); it_v < V.end(); it_v++) + (*it_v) *= (double) s_inv; + + while(iter < max_iter && go_on) + { + S_old = S; + it_u = U.begin(); + norm = 0.0l; + for (unsigned i = 0; i < m; i++, it_u++) + { + long double value = 0.0l; + it_v = V.begin(); + for (unsigned j = 0; j < n; j++, it_v++) + value += (long double) tX[j][i] * (long double) (*it_v); + (*it_u) = (double) value; + norm += value * value; + } + s_inv = 1.0l / sqrt(norm); + + for (it_u = U.begin(); it_u < U.end(); it_u++) + (*it_u) *= s_inv; + + for (it_v = V.begin(); it_v < V.end(); it_v++) + (*it_v) = 0.0l; + + it_v = V.begin(); + for (unsigned j = 0; j < n; j++, it_v++) + { + it_u = U.begin(); + it_x = tX[j].begin(); + for (unsigned i = 0; i < m; i++, it_u++, it_x++) + (*it_v) += (*it_x) * (*it_u); + } + + + norm = 0.0l; + for (it_v = V.begin(); it_v < V.end(); it_v++) + norm += (*it_v) * (*it_v); + + S = sqrt(norm); + s_inv = 1.0l / (long double) S; + + for (it_v = V.begin(); it_v < V.end(); it_v++) + (*it_v) *= s_inv; + + iter++; + go_on = fabsl(S - S_old) > epsilon * S; + } + + return S; +} diff --git a/denoise/ksvd/lib_svd.h b/denoise/ksvd/lib_svd.h new file mode 100644 index 0000000..cec858b --- /dev/null +++ b/denoise/ksvd/lib_svd.h @@ -0,0 +1,16 @@ +#ifndef LIB_SVD_H_INCLUDED +#define LIB_SVD_H_INCLUDED + +#include + +typedef std::vector > mat_t; +typedef std::vector vec_t; +typedef std::vector::iterator iter_t; + +//! Compute the largest singular value by the power method +//! WARNING : tX is transposed!!! +double svd_trunc(mat_t &tX, + vec_t &U, + vec_t &V); + +#endif // LIB_SVD_H_INCLUDED diff --git a/denoise/ksvd/main.cpp b/denoise/ksvd/main.cpp new file mode 100644 index 0000000..35a1ed6 --- /dev/null +++ b/denoise/ksvd/main.cpp @@ -0,0 +1,76 @@ +/** + * @file main.cpp + * @brief Main executable file + * + * + * + * @author MARC LEBRUN + */ + +#include +#include +#include + +#include "utilities.h" +#include "ksvd.h" + +extern "C" { +#include "iio.h" +} + +using std::cerr; +using std::endl; + +const char *pick_option(int *c, char **v, const char *o, const char *d) { + int id = d ? 1 : 0; + for (int i = 0; i < *c - id; i++) { + if (v[i][0] == '-' && 0 == strcmp(v[i] + 1, o)) { + char *r = v[i + id] + 1 - id; + for (int j = i; j < *c - id; j++) + v[j] = v[j + id + 1]; + *c -= id + 1; + return r; + } + } + return d; +} + +int main(int argc, char **argv) { + bool useAcceleration = pick_option(&argc, argv, "a", NULL); + if (argc != 4) { + cerr << "usage: " << argv[0] << " input sigma output [-a]" << endl; + cerr << "\t-a use acceleration" << endl; + return EXIT_FAILURE; + } + + //! read input image + int height, width, chnls; + float *img = iio_read_image_float_split(argv[1], &width, &height, &chnls); + double fSigma = atof(argv[2]); + + int whc = width * height * chnls; + float *img_denoised = new float[whc]; + double *img_noisy = new double[whc]; + + for (int i = 0; i < whc; ++i) img_noisy[i] = img[i]; + + //! Denoising + ksvd_ipol((double) fSigma / 255.0l, + img_noisy, + img_denoised, + width, + height, + chnls, + useAcceleration); + + iio_save_image_float_split(argv[3], img_denoised, width, height, chnls); + + //! Free Memory + delete[] img_denoised; + free(img); + + return EXIT_SUCCESS; +} + + + diff --git a/denoise/ksvd/man/ksvd.1 b/denoise/ksvd/man/ksvd.1 new file mode 100644 index 0000000..8d0d2d0 --- /dev/null +++ b/denoise/ksvd/man/ksvd.1 @@ -0,0 +1,46 @@ +.TH "IPOL tools User Manual" 1 "03 Feb 2018" "IPOL documentation" + +.SH NAME +ksvd + +.SH DESCRIPTION +K-SVD image denoising. + +.SH SYNOPSIS +ksvd input sigma output [-a] + +.SH OPTIONS +.TP +input +name input graphics file (jpeg | png | tiff) +.TP +sigma +noise std (int, mandatory, recomended = 16) +.TP +output +name output graphics file (jpeg | png | tiff) +.TP +-a +use acceleration + +.SH EXAMPLE +ksvd cinput.png 10 ImNoisy.png ImDenoised.png ImDiff.png ImBias.png ImDiffBias.png 0 1 0 + +.SH COPYRIGHT +Copyright : (C) 2017 IPOL Image Processing On Line http://www.ipol.im/ + Licence : GPL v3+, see GPLv3.txt + +.SH SEE ALSO + cjpeg (1), + djpeg (1) + +.SH CONTACTS +.TP +Author: +Nicola Pierazzo +.TP +Author: +Gabriele Facciolo +.TP +Latest version available at: +https://github.com/npd/ksvd diff --git a/denoise/ksvd/utilities.cpp b/denoise/ksvd/utilities.cpp new file mode 100644 index 0000000..e994de9 --- /dev/null +++ b/denoise/ksvd/utilities.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2011, Marc Lebrun + * All rights reserved. + * + * This program is free software: you can use, modify and/or + * redistribute it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later + * version. You should have received a copy of this license along + * this program. If not, see . + */ + + +/** + * @file utilities.cpp + * @brief Utilities functions + * + * @author Marc Lebrun + **/ + +#include "utilities.h" +#include + +using namespace std; +/** + * @brief Compute PSNR and RMSE between img_1 and img_2 + * + * @param img_1 : pointer to an allocated array of pixels. + * @param img_2 : pointer to an allocated array of pixels. + * @param psnr : will contain the PSNR + * @param rmse : will contain the RMSE + * @param size : size of both images + * + * @return none. + **/ +void psnr_rmse(const float * img_1, + const float * img_2, + float * psnr, + float * rmse, + const unsigned size) +{ + float tmp = 0.0f; + for (unsigned k = 0; k < size; k++) + tmp += (img_1[k] - img_2[k]) * (img_1[k] - img_2[k]); + + (*rmse) = sqrtf(tmp / (float) size); + (*psnr) = 20.0f * log10f(255.0f / (*rmse)); +} diff --git a/denoise/ksvd/utilities.h b/denoise/ksvd/utilities.h new file mode 100644 index 0000000..019b992 --- /dev/null +++ b/denoise/ksvd/utilities.h @@ -0,0 +1,13 @@ +#ifndef UTILITIES_H_INCLUDED +#define UTILITIES_H_INCLUDED + +#include + +//! Compute PSNR and RMSE +void psnr_rmse(const float * img_1, + const float * img_2, + float * psnr, + float * rmse, + const unsigned size); + +#endif // UTILITIES_H_INCLUDED diff --git a/denoise/nl-bayes/CMakeLists.txt b/denoise/nl-bayes/CMakeLists.txt new file mode 100644 index 0000000..55ba078 --- /dev/null +++ b/denoise/nl-bayes/CMakeLists.txt @@ -0,0 +1,69 @@ +cmake_minimum_required(VERSION 2.8) +project(nl-bayes) + +# The build type "Release" adds some optimizations +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE "Release") +endif () + +# GCC on MacOs needs this option to use the clang assembler +if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (APPLE)) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wa,-q") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-q") + set (CMAKE_INCLUDE_SYSTEM_FLAG_C "-isystem") + set (CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-isystem") +endif () + +# Optimize to the current CPU and enable warnings +if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR +(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR +(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -Wall -Wextra") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra") +endif () + +# Enable C99 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") +else () + set (CMAKE_C_STANDARD 99) +endif () + +# Enable C++11 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") +else () + set (CMAKE_CXX_STANDARD 11) +endif () + +# Enable OpenMP +find_package (OpenMP) +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + +# Link LibTIFF, LibJPEG, LibPNG +find_path (IIO_INCLUDE_DIR iio.h) +find_library (IIO_LIBRARIES NAMES iio) +include_directories (SYSTEM ${iio_INCLUDE_DIR}) +link_libraries (${IIO_LIBRARIES}) +if (NOT IIO_INCLUDE_DIR) + message (FATAL_ERROR "IIO not found.") +endif () + +# Enable Eigen +find_path (EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library PATH_SUFFIXES eigen3 eigen) +include_directories (SYSTEM ${EIGEN3_INCLUDE_DIR}) +if (NOT EIGEN3_INCLUDE_DIR) + message (FATAL_ERROR "Eigen3 not found.") +endif () + +set(SOURCE_FILES + main.cpp + NLBayes.cpp + NLBayes.h + Image.hpp + utils.cpp + utils.hpp +) + +add_executable(nl-bayes ${SOURCE_FILES}) diff --git a/denoise/nl-bayes/Image.hpp b/denoise/nl-bayes/Image.hpp new file mode 100644 index 0000000..a39516f --- /dev/null +++ b/denoise/nl-bayes/Image.hpp @@ -0,0 +1,110 @@ +/* + * BasicImage.hpp + * + * Created on: 14/gen/2015 + * Author: nicola + */ + +#ifndef IMAGE_HPP_ +#define IMAGE_HPP_ + +#include +#include +#include + +namespace imgutils { + +template +class BasicImage { + public: + BasicImage() = default; + BasicImage(int rows, int columns, int channels = 1, T val = 0); + // construct from C array + BasicImage(const T *data, int rows, int columns, int channels = 1); + + // disable copy constructor + BasicImage(const BasicImage&) = delete; + BasicImage& operator=(const BasicImage&) = delete; + // instead of the copy constructor, we want explicit copy + BasicImage copy() const; + + // default move constructor + BasicImage(BasicImage&&) = default; + BasicImage& operator=(BasicImage&&) = default; + + ~BasicImage() = default; + + void Clear(T val = 0.f) { std::fill(data_.begin(), data_.end(), val); } + + const T& val(int col, int row, int chan = 0) const; + T& val(int col, int row, int chan = 0); + const T& val(int pos) const; + T& val(int pos); + + int channels() const { return channels_; } + int columns() const { return columns_; } + int rows() const { return rows_; } + int pixels() const { return columns_ * rows_; } + int samples() const { return channels_ * columns_ * rows_; } + T* data() { return data_.data(); } + const T* data() const { return data_.data(); } + std::pair shape() const { return {rows_, columns_}; } + typename std::vector::iterator begin() { return data_.begin(); } + typename std::vector::const_iterator begin() const { return data_.begin(); } + typename std::vector::iterator end() { return data_.end(); } + typename std::vector::const_iterator end() const { return data_.end(); } + + protected: + int rows_{0}; + int columns_{0}; + int channels_{0}; + std::vector data_{}; +}; + +typedef BasicImage Image; +typedef BasicImage BoolMask; + +template +inline BasicImage::BasicImage(int rows, int columns, int channels, T val) + : rows_(rows), columns_(columns), channels_(channels), + data_(rows * columns * channels, val) {} + +template +inline BasicImage::BasicImage(const T *data, int rows, int columns, int channels) + : rows_(rows), columns_(columns), channels_(channels), + data_(data, data + rows * columns * channels) {} + +template +inline BasicImage BasicImage::copy() const { + return BasicImage(data(), rows(), columns(), channels()); +} + +template +inline const T& BasicImage::val(int col, int row, int chan) const { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return data_[chan * rows_ * columns_ + row * columns_ + col]; +} + +template +inline T& BasicImage::val(int col, int row, int chan) { + assert(0 <= col && col < columns_); + assert(0 <= row && row < rows_); + assert(0 <= chan && chan < channels_); + return data_[chan * rows_ * columns_ + row * columns_ + col]; +} + +template +inline const T& BasicImage::val(int pos) const { + return data_[pos]; +} + +template +inline T& BasicImage::val(int pos) { + return data_[pos]; +} + +} /* namespace imgutils */ + +#endif // IMAGE_HPP_ diff --git a/denoise/nl-bayes/NLBayes.cpp b/denoise/nl-bayes/NLBayes.cpp new file mode 100644 index 0000000..b0a1c65 --- /dev/null +++ b/denoise/nl-bayes/NLBayes.cpp @@ -0,0 +1,377 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "Image.hpp" +#include "utils.hpp" +#include "NLBayes.h" + +#ifdef _OPENMP +#include +#endif + +using imgutils::Image; +using imgutils::BoolMask; +using imgutils::ComputeTiling; +using imgutils::SplitTiles; +using imgutils::MergeTiles; +using std::move; +using std::sqrt; +using std::pair; +using std::abs; +using std::vector; +using std::copy; +using std::nth_element; +using std::max; +using std::swap; +using Eigen::Matrix; +using Eigen::MatrixXf; +using Eigen::VectorXf; +using Eigen::LDLT; +using Eigen::Success; + +namespace step1 { +constexpr int patch_size = 3; +constexpr int ps2 = patch_size * patch_size; +constexpr int search_window = 8 * patch_size - 1; +constexpr int border = (search_window - patch_size) / 2; +constexpr int offset = search_window / 2; +constexpr int nsim = 50; +constexpr float flat_threshold = 1.f; +} + +namespace step2 { +constexpr int patch_size = 5; +constexpr int search_window = 8 * patch_size - 1; +constexpr int border = (search_window - patch_size) / 2; +constexpr int offset = search_window / 2; +constexpr int nsim_min = 40; +constexpr float tau0 = 16.f; +} + +class PatchDist { + public: + float dist; + int row; + int col; + friend bool operator<(const PatchDist& l, const PatchDist& r) { + return l.dist < r.dist; + } +}; + +Image ColorTransform(Image &&src) { + Image img = move(src); + if (img.channels() == 3) { + for (int row = 0; row < img.rows(); ++row) { + for (int col = 0; col < img.columns(); ++col) { + float r, g, b; + r = img.val(col, row, 0); + g = img.val(col, row, 1); + b = img.val(col, row, 2); + img.val(col, row, 0) = (r + g + b) / sqrt(3.f); + img.val(col, row, 1) = (r - b) / sqrt(2.f); + img.val(col, row, 2) = (r - 2 * g + b) / sqrt(6.f); + } + } + } + return img; +} + +Image ColorTransformInverse(Image &&src) { + Image img = move(src); + if (img.channels() == 3) { + for (int row = 0; row < img.rows(); ++row) { + for (int col = 0; col < img.columns(); ++col) { + float y, u, v; + y = img.val(col, row, 0); + u = img.val(col, row, 1); + v = img.val(col, row, 2); + img.val(col, row, 0) = (sqrt(2.f) * y + sqrt(3.f) * u + v) / sqrt(6.f); + img.val(col, row, 1) = (y - sqrt(2.f) * v) / sqrt(3.f); + img.val(col, row, 2) = (sqrt(2.f) * y - sqrt(3.f) * u + v) / sqrt(6.f); + } + } + } + return img; +} + +namespace step1 { + +float ComputeDistance(const Image &src, int r1, int c1, int r2, int c2) { + float ans = 0.f; + for (int row = 0; row < patch_size; ++row) { + for (int col = 0; col < patch_size; ++col) { + float d = src.val(col + c1, row + r1, 0) - src.val(col + c2, row + r2, 0); + ans += d * d; + } + } + return ans; +} + +pair compute(const Image &noisy, float sigma) { + float sigma2 = sigma * sigma; + Image result(noisy.rows(), noisy.columns(), noisy.channels()); + Image weights(noisy.rows(), noisy.columns()); + BoolMask processed(noisy.rows(), noisy.columns()); + + // (row, col) are the coordinates of the top left pixel of the search window. + // the central patch starts at (row + border, col + border) + for (int row = 0; row <= noisy.rows() - search_window; ++row) { + for (int col = 0; col <= noisy.columns() - search_window; ++col) { + if (!processed.val(col + border, row + border)) { + vector distances; + + // get the group of similar patches + // patches are indexed via their top left coordinate + // the central patch is on (row + border, col + border) + for (int r = row; r <= row + search_window - patch_size; ++r) { + for (int c = col; c <= col + search_window - patch_size; ++c) { + float d = ComputeDistance(noisy, r, c, row + border, col + border); + distances.push_back({d, r, c}); + } + } + assert(distances.size() == (2 * border + 1) * (2 * border + 1)); + // we want to be sure to denoise the central patch + swap(distances[0], distances[border * (2 * border + 2)]); + // the first nsim element are the smallest ones + nth_element(distances.begin() + 1, + distances.begin() + nsim - 1, + distances.end()); + + // compute the variance of the block + float variance = 0.f; + for (int chan = 0; chan < noisy.channels(); ++chan) { + float sum = 0.f, sum2 = 0.f; + for (int i = 0; i < nsim; ++i) { + for (int r = 0; r < patch_size; ++r) { + for (int c = 0; c < patch_size; ++c) { + float val = noisy.val(distances[i].col + c, + distances[i].row + r, + chan); + sum += val; + sum2 += val * val; + } + } + } + variance += (sum2 - (sum * sum / (ps2 * nsim))) / + ((ps2 * nsim - 1) * noisy.channels()); + } + + for (int chan = 0; chan < noisy.channels(); ++chan) { + // put all the patches of the group as rows of a matrix + Matrix block; + for (int i = 0; i < nsim; ++i) { + int pos = 0; + for (int r = 0; r < patch_size; ++r) { + for (int c = 0; c < patch_size; ++c) { + block(pos++, i) = noisy.val(distances[i].col + c, + distances[i].row + r, + chan); + } + } + } + + if (variance < flat_threshold * sigma2) { + block.setConstant(block.array().mean()); + } else { + // Compute the centered block + Matrix + cblock = block.colwise() - block.rowwise().mean(); + // Compute the covariance matrix of the block of similar patches + Matrix + covariance = cblock * cblock.transpose() / (nsim - 1); + // Bayes' Filtering -> block -= sigma2 C^-1 (block - avg) + LDLT> solver(covariance); + if (solver.info() == Success) + block -= sigma2 * solver.solve(cblock); + } + + // Aggregate + for (int i = 0; i < nsim; ++i) { + int pos = 0; + for (int r = 0; r < patch_size; ++r) { + for (int c = 0; c < patch_size; ++c) { + result.val(distances[i].col + c, distances[i].row + r, chan) += + block(pos++, i); + } + } + } + } + + // Mark as done and increment distances + for (int i = 0; i < nsim; ++i) { + processed.val(distances[i].col, distances[i].row) = true; + for (int r = 0; r < patch_size; ++r) { + for (int c = 0; c < patch_size; ++c) { + ++weights.val(distances[i].col + c, distances[i].row + r); + } + } + } + } + } + } + + return {move(result), move(weights)}; +} +} + +namespace step2 { + +float ComputeDistance(const Image &src, int r1, int c1, int r2, int c2) { + float ans = 0.f; + for (int chan = 0; chan < src.channels(); ++chan) { + for (int row = 0; row < patch_size; ++row) { + for (int col = 0; col < patch_size; ++col) { + float d = src.val(col + c1, row + r1, chan) - + src.val(col + c2, row + r2, chan); + ans += d * d; + } + } + } + return ans; +} + +pair compute(const Image &noisy, const Image &guide, float sigma) { + float sigma2 = sigma * sigma; + int ps2 = patch_size * patch_size * noisy.channels(); + Image result(noisy.rows(), noisy.columns(), noisy.channels()); + Image weights(noisy.rows(), noisy.columns()); + BoolMask processed(noisy.rows(), noisy.columns()); + + // (row, col) are the coordinates of the top left pixel of the search window. + // the central patch starts at (row + border, col + border) + for (int row = 0; row <= noisy.rows() - search_window; ++row) { + for (int col = 0; col <= noisy.columns() - search_window; ++col) { + if (!processed.val(col + border, row + border)) { + vector distances; + + // get the group of similar patches + // patches are indexed via their top left coordinate + // the central patch is on (row + border, col + border) + int nsim = 0; + for (int r = row; r <= row + search_window - patch_size; ++r) { + for (int c = col; c <= col + search_window - patch_size; ++c) { + float d = ComputeDistance(guide, r, c, row + border, col + border); + distances.push_back({d, r, c}); + if (d < tau0 * ps2) + ++nsim; + } + } + nsim = max(nsim, nsim_min); + assert(distances.size() == (2 * border + 1) * (2 * border + 1)); + // we want to be sure to denoise the central patch + swap(distances[0], distances[border * (2 * border + 2)]); + // the first nsim element are the smallest ones + nth_element(distances.begin() + 1, distances.begin() + nsim - 1, + distances.end()); + + // put all the patches of the group as rows of a matrix + MatrixXf block(ps2, nsim), gblock(ps2, nsim); + for (int i = 0; i < nsim; ++i) { + int pos = 0; + for (int chan = 0; chan < noisy.channels(); ++chan) { + for (int r = 0; r < patch_size; ++r) { + for (int c = 0; c < patch_size; ++c) { + block(pos, i) = noisy.val(distances[i].col + c, + distances[i].row + r, + chan); + gblock(pos++, i) = guide.val(distances[i].col + c, + distances[i].row + r, + chan); + } + } + } + } + + // Compute the mean of the block of similar patches in the guide + VectorXf mean = gblock.rowwise().mean(); + // Compute the covariance matrix of the block of similar patches in the guide + MatrixXf covariance = (gblock.colwise() - mean) * + (gblock.colwise() - mean).transpose() / (nsim - 1); + // Bayes' Filtering -> block -= sigma2 C^-1 (block - avg) + LDLT + solver(covariance + sigma2 * MatrixXf::Identity(ps2, ps2)); + if (solver.info() == Success) + block -= sigma2 * solver.solve(block.colwise() - mean); + + // Aggregate + for (int i = 0; i < nsim; ++i) { + int pos = 0; + for (int chan = 0; chan < noisy.channels(); ++chan) { + for (int r = 0; r < patch_size; ++r) { + for (int c = 0; c < patch_size; ++c) { + result.val(distances[i].col + c, distances[i].row + r, chan) += + block(pos++, i); + } + } + } + } + + // Mark as done and increment distances + for (int i = 0; i < nsim; ++i) { + processed.val(distances[i].col, distances[i].row) = true; + for (int r = 0; r < patch_size; ++r) { + for (int c = 0; c < patch_size; ++c) { + ++weights.val(distances[i].col + c, distances[i].row + r); + } + } + } + } + } + } + + return {move(result), move(weights)}; +} +} + +Image NLBstep1(const imgutils::Image &noisy, float sigma, int nthreads) { +#ifdef _OPENMP + if (!nthreads) nthreads = omp_get_max_threads(); // number of threads +#else + nthreads = 1; +#endif // _OPENMP + + pair tiling = ComputeTiling(noisy.rows(), noisy.columns(), + nthreads); + vector noisy_tiles = SplitTiles(ColorTransform(noisy.copy()), + step1::offset, step1::offset, tiling); + vector> result_tiles(nthreads); + +#pragma omp parallel for num_threads(nthreads) + for (int i = 0; i < nthreads; ++i) { + result_tiles[i] = step1::compute(noisy_tiles[i], sigma); + } + + return ColorTransformInverse(MergeTiles(result_tiles, noisy.shape(), + step1::offset, step1::offset, + tiling)); +} + +Image NLBstep2(const imgutils::Image &noisy, const imgutils::Image &guide, + float sigma, int nthreads) { +#ifdef _OPENMP + if (!nthreads) nthreads = omp_get_max_threads(); // number of threads +#else + nthreads = 1; +#endif // _OPENMP + + pair tiling = ComputeTiling(noisy.rows(), noisy.columns(), + nthreads); + vector noisy_tiles = SplitTiles(noisy, step2::offset, step2::offset, + tiling); + vector guide_tiles = SplitTiles(guide, step2::offset, step2::offset, + tiling); + vector> result_tiles(nthreads); + +#pragma omp parallel for num_threads(nthreads) + for (int i = 0; i < nthreads; ++i) { + result_tiles[i] = step2::compute(noisy_tiles[i], guide_tiles[i], sigma); + } + + return MergeTiles(result_tiles, noisy.shape(), step2::offset, step2::offset, + tiling); +} diff --git a/denoise/nl-bayes/NLBayes.h b/denoise/nl-bayes/NLBayes.h new file mode 100644 index 0000000..09a11a4 --- /dev/null +++ b/denoise/nl-bayes/NLBayes.h @@ -0,0 +1,7 @@ +#include "Image.hpp" + +imgutils::Image NLBstep1(const imgutils::Image &noisy, float sigma, + int nthreads = 0); +imgutils::Image NLBstep2(const imgutils::Image &noisy, + const imgutils::Image &guide, + float sigma, int nthreads = 0); diff --git a/denoise/nl-bayes/README.txt b/denoise/nl-bayes/README.txt new file mode 100644 index 0000000..6269458 --- /dev/null +++ b/denoise/nl-bayes/README.txt @@ -0,0 +1,8 @@ +% Trimmed-down version of the Non-Local Bayes denoising algorithm + + +# ABOUT + +* Copyright : (C) 2009, 2010 IPOL Image Processing On Line http://www.ipol.im/ +* Licence : GPL v3.0 + diff --git a/denoise/nl-bayes/VERSION b/denoise/nl-bayes/VERSION new file mode 100644 index 0000000..0808d7b --- /dev/null +++ b/denoise/nl-bayes/VERSION @@ -0,0 +1 @@ +0.20180203 diff --git a/denoise/nl-bayes/main.cpp b/denoise/nl-bayes/main.cpp new file mode 100644 index 0000000..b22e58d --- /dev/null +++ b/denoise/nl-bayes/main.cpp @@ -0,0 +1,66 @@ +// +// Created by Nicola Pierazzo on 08/07/16. +// + +#include +#include +#include +#include +#include +#include + +#include "NLBayes.h" +#include "utils.hpp" + +using imgutils::pick_option; +using imgutils::read_image; +using imgutils::save_image; +using imgutils::Image; +using std::cerr; +using std::endl; +using std::move; + +int main(int argc, char **argv) { + const bool usage = static_cast(pick_option(&argc, argv, "h", nullptr)); + const bool + no_second_step = static_cast(pick_option(&argc, argv, "1", NULL)); + const char *second_step_guide = pick_option(&argc, argv, "2", ""); + const bool no_first_step = second_step_guide[0] != '\0'; + + //! Check if there is the right call for the algorithm + if (usage || argc < 2) { + cerr << "usage: " << argv[0] << " sigma [input [output]] " + << "[-1 | -2 guide] " << endl; + return usage ? EXIT_SUCCESS : EXIT_FAILURE; + } + + if (no_second_step && no_first_step) { + cerr << "You can't use -1 and -2 together." << endl; + return EXIT_FAILURE; + } + +#ifndef _OPENMP + cerr << "Warning: OpenMP not available. The algorithm will run in a single" << + " thread." << endl; +#endif + + Image noisy = read_image(argc > 2 ? argv[2] : "-"); + Image guide, result; + const float sigma = static_cast(atof(argv[1])); + + if (!no_first_step) { + guide = NLBstep1(noisy, sigma); + } else { + guide = read_image(second_step_guide); + } + + if (!no_second_step) { + result = NLBstep2(noisy, guide, sigma); + } else { + result = move(guide); + } + + save_image(result, argc > 3 ? argv[3] : "TIFF:-"); + + return EXIT_SUCCESS; +} diff --git a/denoise/nl-bayes/man/nl-bayes.1 b/denoise/nl-bayes/man/nl-bayes.1 new file mode 100644 index 0000000..bad0aa3 --- /dev/null +++ b/denoise/nl-bayes/man/nl-bayes.1 @@ -0,0 +1,46 @@ +.TH "IPOL tools User Manual" 1 "03 Feb 2018" "IPOL documentation" + +.SH NAME +nl-bayes + +.SH DESCRIPTION +Trimmed-down version of the Non-Local Bayes denoising algorithm. + +.SH SYNOPSIS +nl-bayes sigma [input [output]] [-1 | -2 guide] + +.SH OPTIONS +.TP +sigma +noise std (int, mandatory, recomended = 16) +.TP +input +name input graphics file (jpeg | png | tiff) +.TP +output +name output graphics file (jpeg | png | tiff) +.TP +-1|2 +guide (-1: only hard thresh., -2: provide guide) + +.SH EXAMPLE +nl-bayer 16 noisy.tiff denoised.png + +.SH COPYRIGHT +Copyright : (C) 2017 IPOL Image Processing On Line http://www.ipol.im/ + Licence : GPL v3+, see GPLv3.txt + +.SH SEE ALSO + cjpeg (1), + djpeg (1) + +.SH CONTACTS +.TP +Author: +Nicola Pierazzo +.TP +Author: +Gabriele Facciolo +.TP +Latest version available at: +https://github.com/npd/nl-bayes diff --git a/denoise/nl-bayes/utils.cpp b/denoise/nl-bayes/utils.cpp new file mode 100644 index 0000000..1b4cf08 --- /dev/null +++ b/denoise/nl-bayes/utils.cpp @@ -0,0 +1,141 @@ +// +// Created by Nicola Pierazzo on 21/10/15. +// + +#include +#include +#include +#include +#include "utils.hpp" + +extern "C" { +#include +} + +using std::string; +using std::free; +using std::strcmp; +using std::pair; +using std::sqrt; +using std::vector; +using std::move; +using std::max; +using std::min; + +namespace imgutils { + +const char *pick_option(int *c, char **v, const char *o, const char *d) { + int id = d ? 1 : 0; + for (int i = 0; i < *c - id; i++) { + if (v[i][0] == '-' && 0 == strcmp(v[i] + 1, o)) { + char *r = v[i + id] + 1 - id; + for (int j = i; j < *c - id; j++) + v[j] = v[j + id + 1]; + *c -= id + 1; + return r; + } + } + return d; +} + +Image read_image(const string &filename) { + int w, h, c; + float *data = iio_read_image_float_split(filename.c_str(), &w, &h, &c); + Image im(data, h, w, c); + free(data); + return im; +} + +void save_image(const Image &image, const string &filename) { + iio_save_image_float_split(const_cast(filename.c_str()), + const_cast(image.data()), + image.columns(), image.rows(), image.channels()); +} + +inline int SymmetricCoordinate(int pos, int size) { + if (pos < 0) pos = -pos - 1; + if (pos >= 2 * size) pos %= 2 * size; + if (pos >= size) pos = 2 * size - 1 - pos; + return pos; +} + +pair ComputeTiling(int rows, int columns, int tiles) { + float best_r = sqrt(static_cast(tiles * rows) / columns); + int r_low = static_cast(best_r); + int r_up = r_low + 1; + if (r_low < 1) return {1, tiles}; + if (r_up > tiles) return {tiles, 1}; + while (tiles % r_low != 0) --r_low; + while (tiles % r_up != 0) ++r_up; + if (r_up * r_low * columns > tiles * rows) { + return {r_low, tiles / r_low}; + } else { + return {r_up, tiles / r_up}; + } +} + +vector SplitTiles(const Image &src, int pad_before, int pad_after, + pair tiling) { + vector result; + for (int tr = 0; tr < tiling.first; ++tr) { + int rstart = src.rows() * tr / tiling.first - pad_before; + int rend = src.rows() * (tr + 1) / tiling.first + pad_after; + for (int tc = 0; tc < tiling.second; ++tc) { + int cstart = src.columns() * tc / tiling.second - pad_before; + int cend = src.columns() * (tc + 1) / tiling.second + pad_after; + Image tile(rend - rstart, cend - cstart, src.channels()); + for (int ch = 0; ch < src.channels(); ++ch) { + for (int row = rstart; row < rend; ++row) { + for (int col = cstart; col < cend; ++col) { + tile.val(col - cstart, row - rstart, ch) = src.val( + SymmetricCoordinate(col, src.columns()), + SymmetricCoordinate(row, src.rows()), + ch); + } + } + } + result.push_back(move(tile)); + } + } + return result; +} + +Image MergeTiles(const vector> &src, pair shape, + int pad_before, int pad_after, pair tiling) { + int channels = src[0].first.channels(); + Image result(shape.first, shape.second, channels); + Image weights(shape.first, shape.second); + auto tile = src.begin(); + for (int tr = 0; tr < tiling.first; ++tr) { + int rstart = shape.first * tr / tiling.first - pad_before; + int rend = shape.first * (tr + 1) / tiling.first + pad_after; + for (int tc = 0; tc < tiling.second; ++tc) { + int cstart = shape.second * tc / tiling.second - pad_before; + int cend = shape.second * (tc + 1) / tiling.second + pad_after; + for (int ch = 0; ch < channels; ++ch) { + for (int row = max(0, rstart); row < min(shape.first, rend); ++row) { + for (int col = max(0, cstart); col < min(shape.second, cend); ++col) { + result.val(col, row, ch) += + tile->first.val(col - cstart, row - rstart, ch); + } + } + } + for (int row = max(0, rstart); row < min(shape.first, rend); ++row) { + for (int col = max(0, cstart); col < min(shape.second, cend); ++col) { + weights.val(col, row) += tile->second.val(col - cstart, row - rstart); + } + } + ++tile; + } + } + for (int ch = 0; ch < channels; ++ch) { + for (int row = 0; row < shape.first; ++row) { + for (int col = 0; col < shape.second; ++col) { + result.val(col, row, ch) /= weights.val(col, row); + } + } + } + return result; +} + +} // namespace imgutils diff --git a/denoise/nl-bayes/utils.hpp b/denoise/nl-bayes/utils.hpp new file mode 100644 index 0000000..4d01367 --- /dev/null +++ b/denoise/nl-bayes/utils.hpp @@ -0,0 +1,26 @@ +// +// Created by Nicola Pierazzo on 21/10/15. +// + +#ifndef IMGUTILS_UTILS_HPP +#define IMGUTILS_UTILS_HPP + +#include "Image.hpp" +#include + +namespace imgutils { + +const char *pick_option(int *c, char **v, const char *o, const char *d); +Image read_image(const std::string& filename); +void save_image(const Image& image, const std::string& filename); + +std::pair ComputeTiling(int rows, int columns, int tiles); +std::vector SplitTiles(const Image &src, int pad_before, int pad_after, + std::pair tiling); +Image MergeTiles(const std::vector> &src, + std::pair shape, int pad_before, int pad_after, + std::pair tiling); + +} // namespace imgutils + +#endif //IMGUTILS_UTILS_HPP diff --git a/denoise/nlmeans/CMakeLists.txt b/denoise/nlmeans/CMakeLists.txt new file mode 100644 index 0000000..f8daea0 --- /dev/null +++ b/denoise/nlmeans/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 2.8) +project(nlmeans) + +# The build type "Release" adds some optimizations +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE "Release") +endif () + +# Are we using gcc? +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC on MacOs needs this option to use the clang assembler + if (APPLE) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wa,-q") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-q") + endif () + # Optimize to the current CPU and enable warnings + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native -Wall -Wextra") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra") +endif () + +# Enable C99 +if (CMAKE_VERSION VERSION_LESS "3.1") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") +else () + set (CMAKE_C_STANDARD 99) +endif () + +# Enable OpenMP +find_package (OpenMP REQUIRED) +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + +# Link LibTIFF, LibJPEG, LibPNG, OpenMP +find_path (IIO_INCLUDE_DIR iio.h) +find_library (IIO_LIBRARIES NAMES iio) +include_directories (SYSTEM ${iio_INCLUDE_DIR}) +link_libraries (${IIO_LIBRARIES}) +if (NOT IIO_INCLUDE_DIR) + message (FATAL_ERROR "IIO not found.") +endif () + +set(SOURCE_FILES nlmeans.cpp libauxiliar.cpp libauxiliar.h libdenoising.cpp libdenoising.h) + +add_executable(nlmeans ${SOURCE_FILES}) + + diff --git a/denoise/nlmeans/README.txt b/denoise/nlmeans/README.txt new file mode 100644 index 0000000..9b9cdd7 --- /dev/null +++ b/denoise/nlmeans/README.txt @@ -0,0 +1,65 @@ +% Non local means denoising + + +# ABOUT + +* Author : Antoni Buades +* Copyright : (C) 2009, 2010 IPOL Image Processing On Line http://www.ipol.im/ +* Licence + +- nlmeans_ipol.cpp, libdenoising.cpp and libdenoising.h +may be linked to the EP patent 1,749,278 by A. Buades, T. Coll and J.M. Morel. +They are provided for scientific and education only. + +- All the other files are distributed under the terms of the + LGPLv3 license. + + +# OVERVIEW + +This source code provides an implementation of the NL-means denoising algorithm as described in IPOL + http://www.ipol.im/pub/algo/bcm_non_local_means_denoising/ + +This program reads and writes PNG images, but can be easily +adapted to any other file format. + +Only 8bit RGB PNG images are handled. + + +# REQUIREMENTS + +The code is written in ANSI C and C++, and should compile on any +system with an ANSI C/C++ compiler. + +The libpng header and libraries are required on the system for +compilation and execution. + +The implementation uses OPENMP which not supported by old versions of gcc (older than the 4.2). + + +# COMPILATION + +Simply use the provided makefile, with the command `make`. + + +# USAGE + +usage: nlmeans_ipol image sigma noisy denoised + +`nlmeans_ipol ` takes 4 parameter: `nlmeans_ipol in.png sigma noisy.png denoised.png` +* `sigma` : the noise standard deviation +* `in.png` : initial noise free image +* `noisy.png` : noisy image used by the denoising algorithm +* `denoised.png` : denoised image + + + +# ABOUT THIS FILE + +Copyright 2009, 2010 IPOL Image Processing On Line http://www.ipol.im/ +Author: Antoni Buades + +Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without any warranty. diff --git a/denoise/nlmeans/VERSION b/denoise/nlmeans/VERSION new file mode 100644 index 0000000..0808d7b --- /dev/null +++ b/denoise/nlmeans/VERSION @@ -0,0 +1 @@ +0.20180203 diff --git a/denoise/nlmeans/libauxiliar.cpp b/denoise/nlmeans/libauxiliar.cpp new file mode 100644 index 0000000..c64fe21 --- /dev/null +++ b/denoise/nlmeans/libauxiliar.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2009-2011, A. Buades , + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "libauxiliar.h" +#include +#include +#include + +using namespace std; + +const char *pick_option(int *c, char **v, const char *o, const char *d) { + int id = d ? 1 : 0; + for (int i = 0; i < *c - id; i++) { + if (v[i][0] == '-' && 0 == strcmp(v[i] + 1, o)) { + char *r = v[i + id] + 1 - id; + for (int j = i; j < *c - id; j++) + v[j] = v[j + id + 1]; + *c -= id + 1; + return r; + } + } + return d; +} + +void fpClear(float *fpI, float fValue, int iLength) { + for (int ii = 0; ii < iLength; ii++) fpI[ii] = fValue; +} + +// LUT tables +void wxFillExpLut(float *lut, int size) { + for (int i = 0; i < size; i++) + lut[i] = expf(-(float) i / LUTPRECISION); +} + +float wxSLUT(float dif, float *lut) { + if (dif >= (float) LUTMAXM1) return 0.0; + + int x = (int) floor((double) dif * (float) LUTPRECISION); + + float y1 = lut[x]; + float y2 = lut[x + 1]; + + return y1 + (y2 - y1) * (dif * LUTPRECISION - x); +} + +float fiL2FloatDist(float *u0, + float *u1, + int i0, + int j0, + int i1, + int j1, + int radius, + int width0, + int width1) { + float dist = 0.0; + for (int s = -radius; s <= radius; s++) { + int l = (j0 + s) * width0 + (i0 - radius); + float *ptr0 = &u0[l]; + l = (j1 + s) * width1 + (i1 - radius); + float *ptr1 = &u1[l]; + for (int r = -radius; r <= radius; r++, ptr0++, ptr1++) { + float dif = (*ptr0 - *ptr1); + dist += dif * dif; + } + } + return dist; +} + +float fiL2FloatDist(float **u0, + float **u1, + int i0, + int j0, + int i1, + int j1, + int radius, + int channels, + int width0, + int width1) { + float dif = 0.0f; + for (int ii = 0; ii < channels; ii++) { + dif += + fiL2FloatDist(u0[ii], u1[ii], i0, j0, i1, j1, radius, width0, width1); + } + return dif; +} diff --git a/denoise/nlmeans/libauxiliar.h b/denoise/nlmeans/libauxiliar.h new file mode 100644 index 0000000..a004070 --- /dev/null +++ b/denoise/nlmeans/libauxiliar.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2009-2011, A. Buades , + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#ifndef _LIBAUXILIAR_H_ +#define _LIBAUXILIAR_H_ + + +///// LUT tables +#define LUTMAX 30.0 +#define LUTMAXM1 29.0 +#define LUTPRECISION 1000.0 + +const char *pick_option(int *c, char **v, const char *o, const char *d); + +void wxFillExpLut(float *lut, int size); // Fill exp(-x) lut + +float wxSLUT(float dif, float *lut); // look at LUT + +void fpClear(float *fpI, float fValue, int iLength); + +float fiL2FloatDist(float *u0, + float *u1, + int i0, + int j0, + int i1, + int j1, + int radius, + int width0, + int width1); + +float fiL2FloatDist(float **u0, + float **u1, + int i0, + int j0, + int i1, + int j1, + int radius, + int channels, + int width0, + int width1); + +#endif diff --git a/denoise/nlmeans/libdenoising.cpp b/denoise/nlmeans/libdenoising.cpp new file mode 100644 index 0000000..67bd38e --- /dev/null +++ b/denoise/nlmeans/libdenoising.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2009-2011, A. Buades + * All rights reserved. + * + * + * Patent warning: + * + * This file implements algorithms possibly linked to the patents + * + * # A. Buades, T. Coll and J.M. Morel, Image data processing method by + * reducing image noise, and camera integrating means for implementing + * said method, EP Patent 1,749,278 (Feb. 7, 2007). + * + * This file is made available for the exclusive aim of serving as + * scientific tool to verify the soundness and completeness of the + * algorithm description. Compilation, execution and redistribution + * of this file may violate patents rights in certain countries. + * The situation being different for every country and changing + * over time, it is your responsibility to determine which patent + * rights restrictions apply to you before you compile, use, + * modify, or redistribute this file. A patent lawyer is qualified + * to make this determination. + * If and only if they don't conflict with any patent terms, you + * can benefit from the following license terms attached to this + * file. + * + * License: + * + * This program is provided for scientific and educational only: + * you can use and/or modify it for these purposes, but you are + * not allowed to redistribute this work or derivative works in + * source or executable form. A license must be obtained from the + * patent right holders for any other use. + * + * + */ +#include "libdenoising.h" +#include +#include + +using std::max; +using std::min; + +#define fTiny 0.00000001f + +void nlmeans_ipol(int iDWin, // Half size of patch + int iDBloc, // Half size of research window + float fSigma, // Noise parameter + float fFiltPar, // Filtering parameter + float **fpI, // Input + float **fpO, // Output + int iChannels, int iWidth, int iHeight) { + // length of each channel + int iwxh = iWidth * iHeight; + + // length of comparison window + int ihwl = (2 * iDWin + 1); + int iwl = (2 * iDWin + 1) * (2 * iDWin + 1); + int icwl = iChannels * iwl; + + // filtering parameter + float fSigma2 = fSigma * fSigma; + float fH = fFiltPar * fSigma; + float fH2 = fH * fH; + + // multiply by size of patch, since distances are not normalized + fH2 *= (float) icwl; + + // tabulate exp(-x), faster than using directly function expf + int iLutLength = (int) rintf((float) LUTMAX * (float) LUTPRECISION); + float *fpLut = new float[iLutLength]; + wxFillExpLut(fpLut, iLutLength); + + // auxiliary variable + // number of denoised values per pixel + float *fpCount = new float[iwxh]; + fpClear(fpCount, 0.0f, iwxh); + + // clear output + for (int ii = 0; ii < iChannels; ii++) fpClear(fpO[ii], 0.0f, iwxh); + + // PROCESS STARTS + // for each pixel (x,y) +#pragma omp parallel shared(fpI, fpO) + { +#pragma omp for schedule(dynamic) nowait + for (int y = 0; y < iHeight; y++) { + // auxiliary variable + // denoised patch centered at a certain pixel + float **fpODenoised = new float *[iChannels]; + for (int ii = 0; ii < iChannels; ii++) fpODenoised[ii] = new float[iwl]; + for (int x = 0; x < iWidth; x++) { + // reduce the size of the comparison window if we are near the boundary + int iDWin0 = + min(iDWin, min(iWidth - 1 - x, min(iHeight - 1 - y, min(x, y)))); + // research zone depending on the boundary and the size of the window + int imin = max(x - iDBloc, iDWin0); + int jmin = max(y - iDBloc, iDWin0); + int imax = min(x + iDBloc, iWidth - 1 - iDWin0); + int jmax = min(y + iDBloc, iHeight - 1 - iDWin0); + + // clear current denoised patch + for (int ii = 0; ii < iChannels; ii++) + fpClear(fpODenoised[ii], + 0.0f, + iwl); + + // maximum of weights. Used for reference patch + float fMaxWeight = 0.0f; + + // sum of weights + float fTotalWeight = 0.0f; + + for (int j = jmin; j <= jmax; j++) + for (int i = imin; i <= imax; i++) + if (i != x || j != y) { + float fDif = fiL2FloatDist(fpI, + fpI, + x, + y, + i, + j, + iDWin0, + iChannels, + iWidth, + iWidth); + + // dif^2 - 2 * fSigma^2 * N dif is not normalized + fDif = max(fDif - 2.0f * (float) icwl * fSigma2, 0.0f); + fDif = fDif / fH2; + float fWeight = wxSLUT(fDif, fpLut); + if (fWeight > fMaxWeight) fMaxWeight = fWeight; + fTotalWeight += fWeight; + + for (int is = -iDWin0; is <= iDWin0; is++) { + int aiindex = (iDWin + is) * ihwl + iDWin; + int ail = (j + is) * iWidth + i; + + for (int ir = -iDWin0; ir <= iDWin0; ir++) { + + int iindex = aiindex + ir; + int il = ail + ir; + + for (int ii = 0; ii < iChannels; ii++) + fpODenoised[ii][iindex] += fWeight * fpI[ii][il]; + + } + } + } + + // current patch with fMaxWeight + for (int is = -iDWin0; is <= iDWin0; is++) { + int aiindex = (iDWin + is) * ihwl + iDWin; + int ail = (y + is) * iWidth + x; + for (int ir = -iDWin0; ir <= iDWin0; ir++) { + int iindex = aiindex + ir; + int il = ail + ir; + for (int ii = 0; ii < iChannels; ii++) + fpODenoised[ii][iindex] += fMaxWeight * fpI[ii][il]; + } + } + fTotalWeight += fMaxWeight; + + // normalize average value when fTotalweight is not near zero + if (fTotalWeight > fTiny) { + for (int is = -iDWin0; is <= iDWin0; is++) { + int aiindex = (iDWin + is) * ihwl + iDWin; + int ail = (y + is) * iWidth + x; + for (int ir = -iDWin0; ir <= iDWin0; ir++) { + int iindex = aiindex + ir; + int il = ail + ir; + fpCount[il]++; + for (int ii = 0; ii < iChannels; ii++) { + fpO[ii][il] += fpODenoised[ii][iindex] / fTotalWeight; + } + } + } + } + } + + for (int ii = 0; ii < iChannels; ii++) delete[] fpODenoised[ii]; + delete[] fpODenoised; + } + } + + for (int ii = 0; ii < iwxh; ii++) + if (fpCount[ii] > 0.0) { + for (int jj = 0; jj < iChannels; jj++) fpO[jj][ii] /= fpCount[ii]; + } else { + for (int jj = 0; jj < iChannels; jj++) fpO[jj][ii] = fpI[jj][ii]; + } + // delete memory + delete[] fpLut; + delete[] fpCount; +} diff --git a/denoise/nlmeans/libdenoising.h b/denoise/nlmeans/libdenoising.h new file mode 100644 index 0000000..110285d --- /dev/null +++ b/denoise/nlmeans/libdenoising.h @@ -0,0 +1,56 @@ + +/* + * Copyright (c) 2009-2011, A. Buades + * All rights reserved. + * + * + * Patent warning: + * + * This file implements algorithms possibly linked to the patents + * + * # A. Buades, T. Coll and J.M. Morel, Image data processing method by + * reducing image noise, and camera integrating means for implementing + * said method, EP Patent 1,749,278 (Feb. 7, 2007). + * + * This file is made available for the exclusive aim of serving as + * scientific tool to verify the soundness and completeness of the + * algorithm description. Compilation, execution and redistribution + * of this file may violate patents rights in certain countries. + * The situation being different for every country and changing + * over time, it is your responsibility to determine which patent + * rights restrictions apply to you before you compile, use, + * modify, or redistribute this file. A patent lawyer is qualified + * to make this determination. + * If and only if they don't conflict with any patent terms, you + * can benefit from the following license terms attached to this + * file. + * + * License: + * + * This program is provided for scientific and educational only: + * you can use and/or modify it for these purposes, but you are + * not allowed to redistribute this work or derivative works in + * source or executable form. A license must be obtained from the + * patent right holders for any other use. + * + * + */ + +#ifndef _LIBDENOISING_H_ +#define _LIBDENOISING_H_ + +#include "libauxiliar.h" + +/** + * @file libdenoising.cpp + * @brief Denoising functions + */ +void nlmeans_ipol(int iDWin, // Half size of comparison window + int iDBloc, // Half size of research window + float fSigma, // Noise parameter + float fFiltPar, // Filtering parameter + float **fpI, // Input + float **fpO, // Output + int iChannels, int iWidth,int iHeight); + +#endif diff --git a/denoise/nlmeans/man/nlmeans.1 b/denoise/nlmeans/man/nlmeans.1 new file mode 100644 index 0000000..69dcb50 --- /dev/null +++ b/denoise/nlmeans/man/nlmeans.1 @@ -0,0 +1,43 @@ +.TH "IPOL tools User Manual" 1 "03 Feb 2018" "IPOL documentation" + +.SH NAME +nlmeans + +.SH DESCRIPTION +Non local means denoising. + +.SH SYNOPSIS +nlmeans input sigma output + +.SH OPTIONS +.TP +input +name input graphics file (jpeg | png | tiff) +.TP +sigma +noise std (int, mandatory, recomended = 16) +.TP +output +name output graphics file (jpeg | png | tiff) + +.SH EXAMPLE +nlmeans noisy.tiff 16 denoised.png + +.SH COPYRIGHT +Copyright : (C) 2017 IPOL Image Processing On Line http://www.ipol.im/ + Licence : GPL v3+, see GPLv3.txt + +.SH SEE ALSO + cjpeg (1), + djpeg (1) + +.SH CONTACTS +.TP +Author: +Nicola Pierazzo +.TP +Author: +Gabriele Facciolo +.TP +Latest version available at: +https://github.com/npd/nlmeans diff --git a/denoise/nlmeans/nlmeans.cpp b/denoise/nlmeans/nlmeans.cpp new file mode 100644 index 0000000..a329d55 --- /dev/null +++ b/denoise/nlmeans/nlmeans.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2009-2011, A. Buades + * All rights reserved. + * + * + * Patent warning: + * + * This file implements algorithms possibly linked to the patents + * + * # A. Buades, T. Coll and J.M. Morel, Image data processing method by + * reducing image noise, and camera integrating means for implementing + * said method, EP Patent 1,749,278 (Feb. 7, 2007). + * + * This file is made available for the exclusive aim of serving as + * scientific tool to verify the soundness and completeness of the + * algorithm description. Compilation, execution and redistribution + * of this file may violate patents rights in certain countries. + * The situation being different for every country and changing + * over time, it is your responsibility to determine which patent + * rights restrictions apply to you before you compile, use, + * modify, or redistribute this file. A patent lawyer is qualified + * to make this determination. + * If and only if they don't conflict with any patent terms, you + * can benefit from the following license terms attached to this + * file. + * + * License: + * + * This program is provided for scientific and educational only: + * you can use and/or modify it for these purposes, but you are + * not allowed to redistribute this work or derivative works in + * source or executable form. A license must be obtained from the + * patent right holders for any other use. + * + * + */ + +#include +#include +#include "libdenoising.h" + +extern "C" { +#include +} + +using std::cerr; +using std::endl; + +/** + * @mainpage Non Local Means denoising + * + * README.txt: + * @verbinclude README.txt + */ + +/** + * @file nlmeansIpol.cpp + * @brief Main executable file + * + * + * + * @author Antoni Buades + */ + +// usage: nlmeans_ipol image sigma noisy denoised + +int main(int argc, char **argv) { + if (argc != 4) { + cerr << "usage: " << argv[0] << " input sigma output" << endl; + return EXIT_FAILURE; + } + + // read input + int d_w, d_h, d_c; + float *noisy = iio_read_image_float_split(argv[1], &d_w, &d_h, &d_c); + float fSigma = atof(argv[2]); + + if (d_c == 2) { + d_c = 1; // we do not use the alpha channel + } + if (d_c > 3) { + d_c = 3; // we do not use the alpha channel + } + + int d_wh = d_w * d_h; + int d_whc = d_c * d_w * d_h; + + // denoise + float **fpI = new float *[d_c]; + float **fpO = new float *[d_c]; + float *denoised = new float[d_whc]; + + for (int ii = 0; ii < d_c; ii++) { + fpI[ii] = &noisy[ii * d_wh]; + fpO[ii] = &denoised[ii * d_wh]; + } + + int bloc, win; + float fFiltPar; + + if (d_c == 1) { + if (fSigma <= 0.0f) { + cerr << "error: sigma must be > 0" << endl; + return EXIT_FAILURE; + } else if (fSigma <= 15.0f) { + win = 1; + bloc = 10; + fFiltPar = 0.4f; + } else if (fSigma <= 30.0f) { + win = 2; + bloc = 10; + fFiltPar = 0.4f; + } else if (fSigma <= 45.0f) { + win = 3; + bloc = 17; + fFiltPar = 0.35f; + } else if (fSigma <= 75.0f) { + win = 4; + bloc = 17; + fFiltPar = 0.35f; + } else if (fSigma <= 100.0f) { + win = 5; + bloc = 17; + fFiltPar = 0.30f; + } else { + cerr << "error: algorithm parametrized only for values of sigma less than 100.0" << endl; + return EXIT_FAILURE; + } + } else { + if (fSigma <= 0.0f) { + cerr << "error: sigma must be > 0" << endl; + return EXIT_FAILURE; + } else if (fSigma <= 25.0f) { + win = 1; + bloc = 10; + fFiltPar = 0.55f; + } else if (fSigma <= 55.0f) { + win = 2; + bloc = 17; + fFiltPar = 0.4f; + } else if (fSigma <= 100.0f) { + win = 3; + bloc = 17; + fFiltPar = 0.35f; + } else { + cerr << "error: algorithm parametrized only for values of sigma less than 100.0" << endl; + return EXIT_FAILURE; + } + } + + nlmeans_ipol(win, bloc, fSigma, fFiltPar, fpI, fpO, d_c, d_w, d_h); + + delete[] fpI; + delete[] fpO; + // save denoised images + iio_save_image_float_split(argv[3], denoised, d_w, d_h, d_c); + delete[] denoised; + + return EXIT_SUCCESS; +} + + +