diff --git a/.gitattributes b/.gitattributes index 60b33192fcd04f9c72f7932bcdd182a7d6f5dafb..8ebe15bf32a343bd4819244ca876d490d319eb76 100644 --- a/.gitattributes +++ b/.gitattributes @@ -43,3 +43,24 @@ sd_feed/assets/pinterest.png filter=lfs diff=lfs merge=lfs -text sd-3dmodel-loader/models/Samba[[:space:]]Dancing.fbx filter=lfs diff=lfs merge=lfs -text sd-3dmodel-loader/models/pose.vrm filter=lfs diff=lfs merge=lfs -text sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_packed_assets.data filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/flower_1.gif filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/flower_1.mp4 filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/flower_11.mp4 filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/girl_org.gif filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/girl_to_jc.gif filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/girl_to_jc.mp4 filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/girl_to_wc.gif filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/girl_to_wc.mp4 filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/gold_1.gif filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/macaroni_1.gif filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/tree_2.gif filter=lfs diff=lfs merge=lfs -text +SD-CN-Animation/examples/tree_2.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample1.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample2.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample3.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample4.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample5.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample6.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample_anyaheh.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample_autotag.mp4 filter=lfs diff=lfs merge=lfs -text +ebsynth_utility/imgs/sample_clipseg.mp4 filter=lfs diff=lfs merge=lfs -text diff --git a/Abysz-LAB-Ext/LICENSE b/Abysz-LAB-Ext/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f288702d2fa16d3cdf0035b15a9fcbc552cd88e7 --- /dev/null +++ b/Abysz-LAB-Ext/LICENSE @@ -0,0 +1,674 @@ + 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/Abysz-LAB-Ext/README.md b/Abysz-LAB-Ext/README.md new file mode 100644 index 0000000000000000000000000000000000000000..dc14561ebd1e40a9c942b95994761435366fba6f --- /dev/null +++ b/Abysz-LAB-Ext/README.md @@ -0,0 +1,53 @@ +# Abysz deflicking & temporal coherence lab. +## Automatic1111 Extension. Beta 0.1.9 + +![0 1 9](https://user-images.githubusercontent.com/112580728/228404036-5dbf11f1-51e3-4fb0-9c72-c3c2c0deb00e.png) +| +![ABYSZLAB09b](https://user-images.githubusercontent.com/112580728/226314389-ac838672-4af0-4d94-bde8-26fd83610a5f.png) + +## How DFI works: + +https://user-images.githubusercontent.com/112580728/226049549-e61bddb3-88ea-4953-893d-9993dd165180.mp4 + +# Requirements + +OpenCV: ```pip install opencv-python``` + +Imagemagick library: https://imagemagick.org/script/download.php + +## Basic guide: +Differential frame interpolation analyzes the stability of the original video, and processes the generated video with that information. Example, if your original background is static, it will force the generated video to respect that, acting as a complex deflicker. It is an aggressive process, for which we need and will have a lot of control. + +Gui version 0.0.6 includes the following parameters. + +**Frame refresh frequency:** Every how many frames the interpolation is reduced. It allows to keep more information of the generated video, and avoid major ghosting. + +**Refresh Strength:** Opacity % of the interpolated information. 0 refreshes the entire frame, with no changes. Here you control how much change you allow overall. + +**DFI Strength:** Amount of information that tries to force. 4-6 recommended. + +**DFI Deghost:** A variable that generally reduces the areas affected by DFI. This can reduce ghosting without changing DFI strength. + +**Smooth:** Smoothes the interpolation. High values reduce the effectiveness of the process. + +**Source denoise:** Improves scanning in noisy sources. + +(DEFLICKERS PLAYGROUND ADDED) +(FUSE AND VIDEO EXTRACT ADDED) + +# USE STRATEGIES: + +### Basic: +The simplest use is to find the balance between deflicking and deghosting. However, this is not efficient. + +## Multipass: +The most efficient way to use this tool is to allow a certain amount of corruption and ghosting, in exchange for more stable video. Once we have that base, we must use a second step in Stable Diffusion, at low denoising (1-4). In most cases, this brings back much of the detail, but retains the stability we've gained. + +# Multibatch-controlnet: +The best, best way to use this tool is to use our "stabilized" video in img2img, and the original (REAL) video in controlnet HED. Then use a parallel batch to retrieve details. This considerably improves the multipass technique. Unfortunately, that function is not available in the controlnet gui as of this writing. + +# TODO +Automatic1111 extension. Given my limited knowledge of programming, I had trouble getting my script to interact within A1111. I hope soon to solve details to integrate this tool. +Also, there are many important utilities that are in development, waiting to be added soon, such as polar rendering (like "front/back", but more complex), gif viewer, source analysis, preprocessing, etc. + + diff --git a/Abysz-LAB-Ext/__init__.py b/Abysz-LAB-Ext/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Abysz-LAB-Ext/instructions.txt b/Abysz-LAB-Ext/instructions.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/Abysz-LAB-Ext/scripts/Abysz_Lab.py b/Abysz-LAB-Ext/scripts/Abysz_Lab.py new file mode 100644 index 0000000000000000000000000000000000000000..3d4f5483dfad26847099ad96ebc7d066d05f3cac --- /dev/null +++ b/Abysz-LAB-Ext/scripts/Abysz_Lab.py @@ -0,0 +1,1202 @@ +import gradio as gr +import subprocess +import os +import imageio +import numpy as np +from gradio.outputs import Image +from PIL import Image +import sys +import cv2 +import shutil +import time +import math + +from modules import shared +from modules import scripts +from modules import script_callbacks + + +class Script(scripts.Script): + def title(self): + return "Abysz LAB" + + def show(self, is_img2img): + return scripts.AlwaysVisible + + def ui(self, is_img2img): + return [] + +def main(ruta_entrada_1, ruta_entrada_2, ruta_salida, denoise_blur, dfi_strength, dfi_deghost, test_mode, inter_denoise, inter_denoise_size, inter_denoise_speed, fine_blur, frame_refresh_frequency, refresh_strength, smooth, frames_limit): + + maskD = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'MaskD') + maskS = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'MaskS') + #output = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'Output') + source = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'Source') + #gen = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'Gen') + + # verificar si las carpetas existen y eliminarlas si es el caso + if os.path.exists(source): # verificar si existe la carpeta source + shutil.rmtree(source) # eliminar la carpeta source y su contenido + if os.path.exists(maskS): # verificar si existe la carpeta maskS + shutil.rmtree(maskS) # eliminar la carpeta maskS y su contenido + if os.path.exists(maskD): # verificar si existe la carpeta maskS + shutil.rmtree(maskD) # eliminar la carpeta maskS y su contenido + + os.makedirs(source, exist_ok=True) + os.makedirs(maskS, exist_ok=True) + os.makedirs(ruta_salida, exist_ok=True) + os.makedirs(maskD, exist_ok=True) + #os.makedirs(gen, exist_ok=True) + + + def copy_images(ruta_entrada_1, ruta_entrada_2, frames_limit=0): + # Copiar todas las imágenes de la carpeta ruta_entrada_1 a la carpeta Source + count = 0 + + archivos = os.listdir(ruta_entrada_1) + archivos_ordenados = sorted(archivos) + + for i, file in enumerate(archivos_ordenados): + if file.endswith(".jpg") or file.endswith(".jpeg") or file.endswith(".png"): + img = Image.open(os.path.join(ruta_entrada_1, file)) + rgb_img = img.convert('RGB') + rgb_img.save(os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/Source", "{:04d}.jpeg".format(i+1)), "jpeg", quality=100) + count += 1 + if frames_limit > 0 and count >= frames_limit: + break + + + # Llamar a la función copy_images para copiar las imágenes + copy_images(ruta_entrada_1,ruta_salida, frames_limit) + + def sresize(ruta_entrada_2): + gen_folder = ruta_entrada_2 + + # Carpeta donde se encuentran las imágenes de FULL + full_folder = "./extensions/Abysz-LAB-Ext/scripts/Run/Source" + + # Obtener la primera imagen en la carpeta Gen + gen_images = os.listdir(gen_folder) + gen_image_path = os.path.join(gen_folder, gen_images[0]) + gen_image = cv2.imread(gen_image_path) + gen_height, gen_width = gen_image.shape[:2] + gen_aspect_ratio = gen_width / gen_height + + # Recorrer todas las imágenes en la carpeta FULL + for image_name in sorted(os.listdir(full_folder)): + image_path = os.path.join(full_folder, image_name) + image = cv2.imread(image_path) + height, width = image.shape[:2] + aspect_ratio = width / height + + if aspect_ratio != gen_aspect_ratio: + if aspect_ratio > gen_aspect_ratio: + # La imagen es más ancha que la imagen de Gen + crop_width = int(height * gen_aspect_ratio) + x = int((width - crop_width) / 2) + image = image[:, x:x+crop_width] + else: + # La imagen es más alta que la imagen de Gen + crop_height = int(width / gen_aspect_ratio) + y = int((height - crop_height) / 2) + image = image[y:y+crop_height, :] + + # Redimensionar la imagen de FULL a la resolución de la imagen de Gen + image = cv2.resize(image, (gen_width, gen_height)) + + # Guardar la imagen redimensionada en la carpeta FULL + cv2.imwrite(os.path.join(full_folder, image_name), image) + + sresize(ruta_entrada_2) + + def s_g_rename(ruta_entrada_2): + + gen_dir = ruta_entrada_2 # ruta de la carpeta "Source" + + # Obtener una lista de los nombres de archivo en la carpeta ruta_entrada_2 + files2 = os.listdir(gen_dir) + files2 = sorted(files2) # ordenar alfabéticamente la lista + # Renombrar cada archivo + for i, file_name in enumerate(files2): + old_path = os.path.join(gen_dir, file_name) # ruta actual del archivo + new_file_name = f"{i+1:04d}rename" # nuevo nombre de archivo con formato %04d + new_path = os.path.join(gen_dir, new_file_name + os.path.splitext(file_name)[1]) # nueva ruta del archivo + try: + os.rename(old_path, new_path) + except FileExistsError: + print(f"El archivo {new_file_name} ya existe. Se omite su renombre.") + + # Obtener una lista de los nombres de archivo en la carpeta ruta_entrada_2 + files2 = os.listdir(gen_dir) + files2 = sorted(files2) # ordenar alfabéticamente la lista + # Renombrar cada archivo + for i, file_name in enumerate(files2): + old_path = os.path.join(gen_dir, file_name) # ruta actual del archivo + new_file_name = f"{i+1:04d}" # nuevo nombre de archivo con formato %04d + new_path = os.path.join(gen_dir, new_file_name + os.path.splitext(file_name)[1]) # nueva ruta del archivo + try: + os.rename(old_path, new_path) + except FileExistsError: + print(f"El archivo {new_file_name} ya existe. Se omite su renombre.") + + s_g_rename(ruta_entrada_2) + + # Obtener el primer archivo de la carpeta ruta_entrada_2 + gen_files = os.listdir(ruta_entrada_2) + if gen_files: + first_gen_file = gen_files[0] + + # Copiar el archivo a la carpeta "Output" y reemplazar si ya existe + #output_file = "Output" + first_gen_file + #shutil.copyfile(ruta_entrada_2 + first_gen_file, output_file) + output_file = os.path.join(ruta_salida, first_gen_file) + shutil.copyfile(os.path.join(ruta_entrada_2, first_gen_file), output_file) + #subprocess call + def denoise(denoise_blur): + if denoise_blur < 1: # Condición 1: strength debe ser mayor a 1 + return + + denoise_kernel = denoise_blur + # Obtener la lista de nombres de archivos en la carpeta source + files = os.listdir("./extensions/Abysz-LAB-Ext/scripts/Run/Source") + + # Crear una carpeta destino si no existe + #if not os.path.exists("dest"): + # os.mkdir("dest") + + # Recorrer cada archivo en la carpeta source + for file in files: + # Leer la imagen con opencv + img = cv2.imread(os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/Source", file)) + + # Aplicar el filtro de blur con un tamaño de kernel 5x5 + dst = cv2.bilateralFilter(img, denoise_kernel, 31, 31) + + # Eliminar el archivo original + #os.remove(os.path.join("SourceDFI", file)) + + # Guardar la imagen resultante en la carpeta destino con el mismo nombre + cv2.imwrite(os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/Source", file), dst) + + denoise(denoise_blur) + + # Definir la carpeta donde están los archivos + carpeta = './extensions/Abysz-LAB-Ext/scripts/Run/Source' + + # Crear la carpeta MaskD si no existe + os.makedirs('./extensions/Abysz-LAB-Ext/scripts/Run/MaskD', exist_ok=True) + + # Inicializar contador + contador = 1 + + umbral_size = dfi_strength + # Iterar a través de los archivos de imagen en la carpeta Source + for filename in sorted(os.listdir(carpeta)): + # Cargar la imagen actual y la siguiente en escala de grises + if contador > 1: + siguiente = cv2.imread(os.path.join(carpeta, filename), cv2.IMREAD_GRAYSCALE) + diff = cv2.absdiff(anterior, siguiente) + + # Aplicar un umbral y guardar la imagen resultante en la carpeta MaskD. Menos es más. + umbral = umbral_size + umbralizado = cv2.threshold(diff, umbral, 255, cv2.THRESH_BINARY_INV)[1] # Invertir los colores + cv2.imwrite(os.path.join('./extensions/Abysz-LAB-Ext/scripts/Run/MaskD', f'{contador-1:04d}.png'), umbralizado) + + anterior = cv2.imread(os.path.join(carpeta, filename), cv2.IMREAD_GRAYSCALE) + contador += 1 + + #Actualmente, el tipo de umbralización es cv2.THRESH_BINARY_INV, que invierte los colores de la imagen umbralizada. + #Puedes cambiarlo a otro tipo de umbralización, + #como cv2.THRESH_BINARY, cv2.THRESH_TRUNC, cv2.THRESH_TOZERO o cv2.THRESH_TOZERO_INV. + + + # Obtener la lista de los nombres de los archivos en la carpeta MaskD + files = os.listdir("./extensions/Abysz-LAB-Ext/scripts/Run/MaskD") + # Definir la carpeta donde están los archivos + carpeta = "./extensions/Abysz-LAB-Ext/scripts/Run/MaskD" + blur_kernel = smooth + + # Iterar sobre cada archivo + for file in files: + if dfi_deghost == 0: + + continue + # Leer la imagen de la carpeta MaskD + #img = cv2.imread("MaskD" + file) + img = cv2.imread(os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/MaskD", file)) + + # Invertir la imagen usando la función bitwise_not() + img_inv = cv2.bitwise_not(img) + + kernel_size = dfi_deghost + + # Dilatar la imagen usando la función dilate() + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size)) # Puedes cambiar el tamaño y la forma del kernel según tus preferencias + img_dil = cv2.dilate(img_inv, kernel) + + # Volver a invertir la imagen usando la función bitwise_not() + img_out = cv2.bitwise_not(img_dil) + + # Sobrescribir la imagen en la carpeta MaskD con el mismo nombre que el original + #cv2.imwrite("MaskD" + file, img_out) + #cv2.imwrite(os.path.join("MaskD", file, img_out)) + filename = os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/MaskD", file) + cv2.imwrite(filename, img_out) + + # Iterar a través de los archivos de imagen en la carpeta MaskD + if smooth > 1: + for imagen in os.listdir(carpeta): + if imagen.endswith(".jpg") or imagen.endswith(".png") or imagen.endswith(".jpeg"): + # Leer la imagen + img = cv2.imread(os.path.join(carpeta, imagen)) + # Aplicar el filtro + img = cv2.GaussianBlur(img, (blur_kernel,blur_kernel),0) + # Guardar la imagen con el mismo nombre + cv2.imwrite(os.path.join(carpeta, imagen), img) + + + # INICIO DEL BATCH Obtener el nombre del archivo en MaskD sin ninguna extensión + # Agregar una variable de contador de bucles + loop_count = 0 + + # Agregar un bucle while para ejecutar el código en bucle infinito + while True: + + mask_files = sorted(os.listdir(maskD)) + if not mask_files: + print(f"No frames left") + # Eliminar las carpetas Source, MaskS y MaskD si no hay más archivos para procesar + shutil.rmtree(maskD) + shutil.rmtree(maskS) + shutil.rmtree(source) + break + + extra_mod = fine_blur + + mask = mask_files[0] + maskname = os.path.splitext(mask)[0] + + maskp_path = os.path.join(maskD, mask) + + img = cv2.imread(maskp_path, cv2.IMREAD_GRAYSCALE) # leer la imagen en escala de grises + n_white_pix = np.sum(img == 255) # contar los píxeles que son iguales a 255 (blanco) + total_pix = img.size # obtener el número total de píxeles en la imagen + percentage = (n_white_pix / total_pix) * 100 # calcular el porcentaje de píxeles blancos + percentage = round(percentage, 1) # redondear el porcentaje a 1 decimal + + # calcular la variable extra + extra = 100 - percentage # restar el porcentaje a 100 + extra = extra / 3 # dividir el resultado por 3 + extra = math.ceil(extra) # redondear hacia arriba al entero más cercano + if extra % 2 == 0: # verificar si el número es par + extra = extra + 1 # sumarle 1 para hacerlo impar + + # Dynamic Blur + imgb = cv2.imread(maskp_path) # leer la imagen con opencv + img_blur = cv2.GaussianBlur(imgb, (extra,extra),0) + + # guardar la imagen modificada con el mismo nombre y ruta + cv2.imwrite(maskp_path, img_blur) + + # Obtener la ruta de la imagen en la subcarpeta de output que tiene el mismo nombre que la imagen en MaskD + output_files = [f for f in os.listdir(ruta_salida) if os.path.splitext(f)[0] == maskname] + if not output_files: + print(f"No se encontró en {ruta_salida} una imagen con el mismo nombre que {maskname}.") + exit(1) + + output_file = os.path.join(ruta_salida, output_files[0]) + + # Aplicar el comando magick composite con las opciones deseadas + composite_command = f"magick composite -compose CopyOpacity {os.path.join(maskD, mask)} {output_file} {os.path.join(maskS, 'result.png')}" + os.system(composite_command) + + # Obtener el nombre del archivo en output sin ninguna extensión + name = os.path.splitext(os.path.basename(output_file))[0] + + # Renombrar el archivo result.png con el nombre del archivo en output y la extensión .png + os.rename(os.path.join(maskS, 'result.png'), os.path.join(maskS, f"{name}.png")) + + #Guardar el directorio actual en una variable + original_dir = os.getcwd() + + #Cambiar al directorio de la carpeta MaskS + os.chdir(maskS) + + #Iterar a través de los archivos de imagen en la carpeta MaskS + for imagen in sorted(os.listdir(".")): + # Obtener el nombre de la imagen sin la extensión + nombre, extension = os.path.splitext(imagen) + # Obtener solo el número de la imagen + numero = ''.join(filter(str.isdigit, nombre)) + # Definir el nombre de la siguiente imagen + siguiente = f"{int(numero)+1:0{len(numero)}}{extension}" + # Renombrar la imagen + os.rename(imagen, siguiente) + + # Volver al directorio original + os.chdir(original_dir) + + # Establecer un valor predeterminado para disolución + if frame_refresh_frequency < 1: + dissolve = percentage + else: + dissolve = 100 if loop_count % frame_refresh_frequency != 0 else refresh_strength + + + # Obtener el nombre del archivo en MaskS sin la extensión + maskS_files = [f for f in os.listdir(maskS) if os.path.isfile(os.path.join(maskS, f)) and f.endswith('.png')] + if maskS_files: + filename = os.path.splitext(maskS_files[0])[0] + else: + print(f"No se encontraron archivos de imagen en la carpeta '{maskS}'") + filename = ''[0] + + # Salir del bucle si no hay más imágenes que procesar + if not filename: + break + + # Obtener la extensión del archivo en Gen con el mismo nombre + gen_files = [f for f in os.listdir(ruta_entrada_2) if os.path.isfile(os.path.join(ruta_entrada_2, f)) and f.startswith(filename)] + if gen_files: + ext = os.path.splitext(gen_files[0])[1] + else: + print(f"No se encontró ningún archivo con el nombre '{filename}' en la carpeta '{ruta_entrada_2}'") + ext = '' + + # Componer la imagen de MaskS y Gen con disolución (si está definido) y guardarla en la carpeta de salida + os.system(f"magick composite {'-dissolve ' + str(dissolve) + '%' if dissolve is not None else ''} {maskS}/{filename}.png {ruta_entrada_2}/{filename}{ext} {ruta_salida}/{filename}{ext}") + + denoise_loop = inter_denoise_speed + kernel1 = inter_denoise + kernel2 = inter_denoise_size + + # Demo plus bilateral + if loop_count % denoise_loop == 0: + # listar archivos en la carpeta de salida + archivos = os.listdir(ruta_salida) + # obtener el último archivo + ultimo_archivo = os.path.join(ruta_salida, archivos[-1]) + # cargar imagen con opencv + imagen = cv2.imread(ultimo_archivo) + # aplicar filtro bilateral + imagen_filtrada = cv2.bilateralFilter(imagen, kernel1, kernel2, kernel2) + # sobreescribir el original + cv2.imwrite(ultimo_archivo, imagen_filtrada) + + # Obtener el nombre del archivo más bajo en la carpeta MaskD + maskd_files = [f for f in os.listdir(maskD) if os.path.isfile(os.path.join(maskD, f)) and f.startswith('')] + if maskd_files: + maskd_file = os.path.join(maskD, sorted(maskd_files)[0]) + os.remove(maskd_file) + + # Obtener el nombre del archivo más bajo en la carpeta MaskS + masks_files = [f for f in os.listdir(maskS) if os.path.isfile(os.path.join(maskS, f)) and f.startswith('')] + if masks_files: + masks_file = os.path.join(maskS, sorted(masks_files)[0]) + os.remove(masks_file) + + # Aumentar el contador de bucles + loop_count += 1 + +def dyndef(ruta_entrada_3, ruta_salida_1, ddf_strength): + if ddf_strength <= 0: # Condición 1: strength debe ser mayor a 0 + return + imgs = [] + files = sorted(os.listdir(ruta_entrada_3)) + + for file in files: + img = cv2.imread(os.path.join(ruta_entrada_3, file)) + imgs.append(img) + + for idx in range(len(imgs)-1, 0, -1): + current_img = imgs[idx] + prev_img = imgs[idx-1] + alpha = ddf_strength + + current_img = cv2.addWeighted(current_img, alpha, prev_img, 1-alpha, 0) + imgs[idx] = current_img + + if not os.path.exists(ruta_salida_1): + os.makedirs(ruta_salida_1) + + output_path = os.path.join(ruta_salida_1, files[idx]) # Usa el mismo nombre que el original + cv2.imwrite(output_path, current_img) + + # Copia el primer archivo de los originales al finalizar el proceso + shutil.copy(os.path.join(ruta_entrada_3, files[0]), os.path.join(ruta_salida_1, files[0])) + + + +def overlay_images(image1_path, image2_path, over_strength): + + opacity = over_strength + + # Abrir las imágenes + image1 = Image.open(image1_path).convert('RGBA') + image2 = Image.open(image2_path).convert('RGBA') + + # Alinear el tamaño de las imágenes + if image1.size != image2.size: + image2 = image2.resize(image1.size) + + # Convertir las imágenes en matrices NumPy + np_image1 = np.array(image1).astype(np.float64) / 255.0 + np_image2 = np.array(image2).astype(np.float64) / 255.0 + + # Aplicar el método de fusión "overlay" a las imágenes + def basic(target, blend, opacity): + return target * opacity + blend * (1-opacity) + + def blender(func): + def blend(target, blend, opacity=1, *args): + res = func(target, blend, *args) + res = basic(res, blend, opacity) + return np.clip(res, 0, 1) + return blend + + class Blend: + @classmethod + def method(cls, name): + return getattr(cls, name) + + normal = basic + + @staticmethod + @blender + def overlay(target, blend, *args): + return (target>0.5) * (1-(2-2*target)*(1-blend)) +\ + (target<=0.5) * (2*target*blend) + + blended_image = Blend.overlay(np_image1, np_image2, opacity) + + # Convertir la matriz de vuelta a una imagen PIL + blended_image = Image.fromarray((blended_image * 255).astype(np.uint8), 'RGBA').convert('RGB') + + # Guardar la imagen resultante + return blended_image + +def overlay_images2(image1_path, image2_path, fuse_strength): + + opacity = fuse_strength + + try: + image1 = Image.open(image1_path).convert('RGBA') + image2 = Image.open(image2_path).convert('RGBA') + except: + print("No more frames to fuse.") + return + + # Alinear el tamaño de las imágenes + if image1.size != image2.size: + image1 = image1.resize(image2.size) + + # Convertir las imágenes en matrices NumPy + np_image1 = np.array(image1).astype(np.float64) / 255.0 + np_image2 = np.array(image2).astype(np.float64) / 255.0 + + # Aplicar el método de fusión "overlay" a las imágenes + def basic(target, blend, opacity): + return target * opacity + blend * (1-opacity) + + def blender(func): + def blend(target, blend, opacity=1, *args): + res = func(target, blend, *args) + res = basic(res, blend, opacity) + return np.clip(res, 0, 1) + return blend + + class Blend: + @classmethod + def method(cls, name): + return getattr(cls, name) + + normal = basic + + @staticmethod + @blender + def overlay(target, blend, *args): + return (target>0.5) * (1-(2-2*target)*(1-blend)) +\ + (target<=0.5) * (2*target*blend) + + blended_image = Blend.overlay(np_image1, np_image2, opacity) + + # Convertir la matriz de vuelta a una imagen PIL + blended_image = Image.fromarray((blended_image * 255).astype(np.uint8), 'RGBA').convert('RGB') + + # Guardar la imagen resultante + return blended_image + +def overlay_run(ruta_entrada_3, ruta_salida_1, ddf_strength, over_strength): + if over_strength <= 0: # Condición 1: strength debe ser mayor a 0 + return + + # Si ddf_strength y/o over_strength son mayores a 0, utilizar ruta_salida_1 en lugar de ruta_entrada_3 + if ddf_strength > 0: + ruta_entrada_3 = ruta_salida_1 + + if not os.path.exists("overtemp"): + os.makedirs("overtemp") + + if not os.path.exists(ruta_salida_1): + os.makedirs(ruta_salida_1) + + gen_path = ruta_entrada_3 + images = sorted(os.listdir(gen_path)) + image1_path = os.path.join(gen_path, images[0]) + image2_path = os.path.join(gen_path, images[1]) + + + fused_image = overlay_images(image1_path, image2_path, over_strength) + fuseover_path = "overtemp" + filename = os.path.basename(image1_path) + fused_image.save(os.path.join(fuseover_path, filename)) + + + # Obtener una lista de todos los archivos en la carpeta "Gen" + gen_files = sorted(os.listdir(ruta_entrada_3)) + + for i in range(len(gen_files) - 1): + image1_path = os.path.join(ruta_entrada_3, gen_files[i]) + image2_path = os.path.join(ruta_entrada_3, gen_files[i+1]) + blended_image = overlay_images(image1_path, image2_path, over_strength) + blended_image.save(os.path.join("overtemp", gen_files[i+1])) + + + # Definimos la ruta de la carpeta "overtemp" + ruta_overtemp = "overtemp" + + # Movemos todos los archivos de la carpeta "overtemp" a la carpeta "ruta_salida" + for archivo in os.listdir(ruta_overtemp): + origen = os.path.join(ruta_overtemp, archivo) + destino = os.path.join(ruta_salida_1, archivo) + shutil.move(origen, destino) + + # Ajustar contraste y brillo para cada imagen en la carpeta de entrada + if over_strength >= 0.4: + for nombre_archivo in os.listdir(ruta_salida_1): + # Cargar imagen + ruta_archivo = os.path.join(ruta_salida_1, nombre_archivo) + img = cv2.imread(ruta_archivo) + + # Ajustar contraste y brillo + alpha = 1 # Factor de contraste (mayor que 1 para aumentar el contraste) + beta = 10 # Valor de brillo (entero positivo para aumentar el brillo) + img_contrast = cv2.convertScaleAbs(img, alpha=alpha, beta=beta) + + # Guardar imagen resultante en la carpeta de salida + ruta_salida = os.path.join(ruta_salida_1, nombre_archivo) + cv2.imwrite(ruta_salida, img_contrast) + +def over_fuse(ruta_entrada_4, ruta_entrada_5, ruta_salida_2, fuse_strength): + # Obtener una lista de todos los archivos en la carpeta "Gen" + gen_files = os.listdir(ruta_entrada_4) + + # Ordenar la lista de archivos alfabéticamente + gen_files.sort() + + # Obtener una lista de todos los archivos en la carpeta "Source" + source_files = os.listdir(ruta_entrada_5) + + # Ordenar la lista de archivos alfabéticamente + source_files.sort() + + if not os.path.exists(ruta_salida_2): + os.makedirs(ruta_salida_2) + + for i in range(len(gen_files)): + image1_path = os.path.join(ruta_entrada_4, gen_files[i]) + image2_path = os.path.join(ruta_entrada_5, source_files[i]) + blended_image = overlay_images2(image1_path, image2_path, fuse_strength) + try: + blended_image.save(os.path.join(ruta_salida_2, gen_files[i])) + except Exception as e: + print("Error al guardar la imagen:", str(e)) + print("No more frames to fuse") + break + + +def norm(ruta_entrada_3, ruta_salida_1, ddf_strength, over_strength, norm_strength): + if norm_strength <= 0: # Condición 1: Norm_strength debe ser mayor a 0 + return + + # Si ddf_strength y/o over_strength son mayores a 0, utilizar ruta_salida_1 en lugar de ruta_entrada_3 + if ddf_strength > 0 or over_strength > 0: + ruta_entrada_3 = ruta_salida_1 + + # Crear la carpeta GenOverNorm si no existe + if not os.path.exists("normtemp"): + os.makedirs("normtemp") + + if not os.path.exists(ruta_salida_1): + os.makedirs(ruta_salida_1) + + # Obtener una lista de todas las imágenes en la carpeta FuseOver + img_list = os.listdir(ruta_entrada_3) + img_list.sort() # Ordenar la lista en orden ascendente + + # Iterar a través de las imágenes + for i in range(len(img_list)-1): + # Cargar las dos imágenes a fusionar + img1 = cv2.imread(os.path.join(ruta_entrada_3, img_list[i])) + img2 = cv2.imread(os.path.join(ruta_entrada_3, img_list[i+1])) + + # Calcular la luminosidad promedio de cada imagen + avg1 = np.mean(cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)) + avg2 = np.mean(cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)) + + # Calcular los pesos para cada imagen + weight1 = avg1 / (avg1 + avg2) + weight2 = avg2 / (avg1 + avg2) + + # Fusionar las imágenes utilizando los pesos + result = cv2.addWeighted(img1, weight1, img2, weight2, 0) + + # Guardar la imagen resultante en la carpeta GenOverNorm con el mismo nombre que la imagen original + cv2.imwrite(os.path.join("normtemp", img_list[i+1]), result) + + # Copiar la primera imagen en la carpeta GenOverNorm para mantener la secuencia completa + img0 = cv2.imread(os.path.join(ruta_entrada_3, img_list[0])) + cv2.imwrite(os.path.join("normtemp", img_list[0]), img0) + + # Definimos la ruta de la carpeta "overtemp" + ruta_overtemp = "normtemp" + + # Movemos todos los archivos de la carpeta "overtemp" a la carpeta "ruta_salida" + for archivo in os.listdir(ruta_overtemp): + origen = os.path.join(ruta_overtemp, archivo) + destino = os.path.join(ruta_salida_1, archivo) + shutil.move(origen, destino) + +def deflickers(ruta_entrada_3, ruta_salida_1, ddf_strength, over_strength, norm_strength): + dyndef(ruta_entrada_3, ruta_salida_1, ddf_strength) + overlay_run(ruta_entrada_3, ruta_salida_1, ddf_strength, over_strength) + norm(ruta_entrada_3, ruta_salida_1, ddf_strength, over_strength, norm_strength) + +def extract_video(ruta_entrada_6, ruta_salida_3, fps_count): + + # Ruta del archivo de video + filename = ruta_entrada_6 + + # Directorio donde se guardarán los frames extraídos + output_dir = ruta_salida_3 + + # Abrir el archivo de video + cap = cv2.VideoCapture(filename) + + # Obtener los FPS originales del video + fps = cap.get(cv2.CAP_PROP_FPS) + + # Si fps_count es 0, utilizar los FPS originales + if fps_count == 0: + fps_count = fps + + # Calcular el tiempo entre cada frame a extraer en milisegundos + frame_time = int(round(1000 / fps_count)) + + # Crear el directorio de salida si no existe + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Inicializar el contador de frames + frame_count = 0 + + # Inicializar el tiempo del último frame extraído + last_frame_time = 0 + + # Iterar sobre los frames del video + while True: + # Leer el siguiente frame + ret, frame = cap.read() + + # Si no se pudo leer un frame, salir del loop + if not ret: + break + + # Calcular el tiempo actual del frame en milisegundos + current_frame_time = int(round(cap.get(cv2.CAP_PROP_POS_MSEC))) + + # Si todavía no ha pasado suficiente tiempo desde el último frame extraído, saltar al siguiente frame + if current_frame_time - last_frame_time < frame_time: + continue + + # Incrementar el contador de frames + frame_count += 1 + + # Construir el nombre del archivo de salida + output_filename = os.path.join(output_dir, 'frame_{:04d}.jpeg'.format(frame_count)) + + # Guardar el frame como una imagen + cv2.imwrite(output_filename, frame) + + # Actualizar el tiempo del último frame extraído + last_frame_time = current_frame_time + + # Cerrar el archivo de video + cap.release() + + # Mostrar información sobre el proceso finalizado + print("Extracted {} frames.".format(frame_count)) + +def test_dfi(ruta_entrada_1, ruta_entrada_2, denoise_blur, dfi_strength, dfi_deghost, test_mode, smooth): + + + maskD = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'MaskDT') + #maskS = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'MaskST') + #output = os.path.join(os.getcwd(), 'extensions', 'Abysz-lab', 'scripts', 'Run', 'Output') + source = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'SourceT') + #gen = os.path.join(os.getcwd(), 'extensions', 'Abysz-LAB-Ext', 'scripts', 'Run', 'GenT') + + # verificar si las carpetas existen y eliminarlas si es el caso + if os.path.exists(source): # verificar si existe la carpeta source + shutil.rmtree(source) # eliminar la carpeta source y su contenido + #if os.path.exists(maskS): # verificar si existe la carpeta maskS + # shutil.rmtree(maskS) # eliminar la carpeta maskS y su contenido + if os.path.exists(maskD): # verificar si existe la carpeta maskS + shutil.rmtree(maskD) # eliminar la carpeta maskS y su contenido + #if os.path.exists(gen): # verificar si existe la carpeta maskS + # shutil.rmtree(gen) # eliminar la carpeta maskS y su contenido + #if os.path.exists(output): # verificar si existe la carpeta maskS + # shutil.rmtree(output) # eliminar la carpeta maskS y su contenido + + + os.makedirs(source, exist_ok=True) + #os.makedirs(maskS, exist_ok=True) + #os.makedirs(output, exist_ok=True) + os.makedirs(maskD, exist_ok=True) + #os.makedirs(gen, exist_ok=True) + + + def copy_images(ruta_entrada_1, ruta_entrada_2): + if test_mode == 0: + # Usar el primer formato + indices = [10, 11, 20, 21, 30, 31] # Los índices de las imágenes que quieres copiar + else: + test_frames = test_mode + # Usar el segundo formato + indices = list(range(test_frames)) # Los primeros 30 índices + # Copiar todas las imágenes de la carpeta ruta_entrada_1 a la carpeta Source + for i in indices: + file = os.listdir(ruta_entrada_1)[i] # Obtener el nombre del archivo en el índice i + if file.endswith(".jpg") or file.endswith(".jpeg") or file.endswith(".png"): # Verificar que sea una imagen + img = Image.open(os.path.join(ruta_entrada_1, file)) # Abrir la imagen + rgb_img = img.convert('RGB') # Convertir a RGB + rgb_img.save(os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/SourceT", "{:04d}.jpeg".format(i+1)), "jpeg", quality=100) # Guardar la imagen en la carpeta destino + + # Llamar a la función copy_images para copiar las imágenes + copy_images(ruta_entrada_1, ruta_entrada_2) + + # Carpeta donde se encuentran las imágenes de Gen + def sresize(ruta_entrada_2): + gen_folder = ruta_entrada_2 + + # Carpeta donde se encuentran las imágenes de FULL + full_folder = "./extensions/Abysz-LAB-Ext/scripts/Run/SourceT" + + # Obtener la primera imagen en la carpeta Gen + gen_images = os.listdir(gen_folder) + gen_image_path = os.path.join(gen_folder, gen_images[0]) + gen_image = cv2.imread(gen_image_path) + gen_height, gen_width = gen_image.shape[:2] + gen_aspect_ratio = gen_width / gen_height + + # Recorrer todas las imágenes en la carpeta FULL + for image_name in os.listdir(full_folder): + image_path = os.path.join(full_folder, image_name) + image = cv2.imread(image_path) + height, width = image.shape[:2] + aspect_ratio = width / height + + if aspect_ratio != gen_aspect_ratio: + if aspect_ratio > gen_aspect_ratio: + # La imagen es más ancha que la imagen de Gen + crop_width = int(height * gen_aspect_ratio) + x = int((width - crop_width) / 2) + image = image[:, x:x+crop_width] + else: + # La imagen es más alta que la imagen de Gen + crop_height = int(width / gen_aspect_ratio) + y = int((height - crop_height) / 2) + image = image[y:y+crop_height, :] + + # Redimensionar la imagen de FULL a la resolución de la imagen de Gen + image = cv2.resize(image, (gen_width, gen_height)) + + # Guardar la imagen redimensionada en la carpeta FULL + cv2.imwrite(os.path.join(full_folder, image_name), image) + + sresize(ruta_entrada_2) + + def denoise(denoise_blur): + if denoise_blur < 1: + return + + denoise_kernel = denoise_blur + # Obtener la lista de nombres de archivos en la carpeta source + files = os.listdir("./extensions/Abysz-LAB-Ext/scripts/Run/SourceT") + + # Crear una carpeta destino si no existe + #if not os.path.exists("dest"): + # os.mkdir("dest") + + # Recorrer cada archivo en la carpeta source + for file in files: + # Leer la imagen con opencv + img = cv2.imread(os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/SourceT", file)) + + # Aplicar el filtro de blur con un tamaño de kernel 5x5 + dst = cv2.bilateralFilter(img, denoise_kernel, 31, 31) + + # Eliminar el archivo original + #os.remove(os.path.join("SourceDFI", file)) + + # Guardar la imagen resultante en la carpeta destino con el mismo nombre + cv2.imwrite(os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/SourceT", file), dst) + + denoise(denoise_blur) + + + # Definir la carpeta donde están los archivos + carpeta = './extensions/Abysz-LAB-Ext/scripts/Run/SourceT' + + # Crear la carpeta MaskD si no existe + os.makedirs('./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT', exist_ok=True) + + # Inicializar número de imagen + numero = 1 + + umbral_size = dfi_strength + # Iterar a través de los archivos de imagen en la carpeta Source + for filename in sorted(os.listdir(carpeta)): + if test_mode == 0: + # Cargar la imagen actual en escala de grises + actual = cv2.imread(os.path.join(carpeta, filename), cv2.IMREAD_GRAYSCALE) + + # Si el número de imagen es par, procesar la imagen actual y la anterior + if numero % 2 == 0: + diff = cv2.absdiff(anterior, actual) + + # Aplicar un umbral y guardar la imagen resultante en la carpeta MaskD con el mismo nombre que el original. Menos es más. + umbral = umbral_size + umbralizado = cv2.threshold(diff, umbral, 255, cv2.THRESH_BINARY_INV)[1] # Invertir los colores + cv2.imwrite(os.path.join('./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT', filename), umbralizado) + + # Guardar la imagen actual como anterior para el siguiente ciclo + anterior = actual + + # Incrementar el número de imagen para alternar entre pares e impares + numero += 1 + + else: + + # Iterar a través de los archivos de imagen en la carpeta Source + for filename in sorted(os.listdir(carpeta)): + # Cargar la imagen actual y la siguiente en escala de grises + + if numero > 1: + siguiente = cv2.imread(os.path.join(carpeta, filename), cv2.IMREAD_GRAYSCALE) + diff = cv2.absdiff(anterior, siguiente) + + # Aplicar un umbral y guardar la imagen resultante en la carpeta MaskD. Menos es más. + umbral = umbral_size + umbralizado = cv2.threshold(diff, umbral, 255, cv2.THRESH_BINARY_INV)[1] # Invertir los colores + cv2.imwrite(os.path.join('./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT', filename), umbralizado) + + anterior = cv2.imread(os.path.join(carpeta, filename), cv2.IMREAD_GRAYSCALE) + numero += 1 + + + + # Obtener la lista de los nombres de los archivos en la carpeta MaskD + files = os.listdir("./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT") + # Definir la carpeta donde están los archivos + carpeta = "./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT" + blur_kernel = smooth + + # Iterar sobre cada archivo + for file in files: + if dfi_deghost == 0: + + continue + # Leer la imagen de la carpeta MaskD + #img = cv2.imread("MaskD" + file) + img = cv2.imread(os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT", file)) + + # Invertir la imagen usando la función bitwise_not() + img_inv = cv2.bitwise_not(img) + + kernel_size = dfi_deghost + + # Dilatar la imagen usando la función dilate() + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size)) # Puedes cambiar el tamaño y la forma del kernel según tus preferencias + img_dil = cv2.dilate(img_inv, kernel) + + # Volver a invertir la imagen usando la función bitwise_not() + img_out = cv2.bitwise_not(img_dil) + + # Sobrescribir la imagen en la carpeta MaskD con el mismo nombre que el original + #cv2.imwrite("MaskD" + file, img_out) + #cv2.imwrite(os.path.join("MaskD", file, img_out)) + filename = os.path.join("./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT", file) + cv2.imwrite(filename, img_out) + + # Iterar a través de los archivos de imagen en la carpeta MaskD + if smooth > 1: + for imagen in os.listdir(carpeta): + if imagen.endswith(".jpg") or imagen.endswith(".png") or imagen.endswith(".jpeg"): + # Leer la imagen + img = cv2.imread(os.path.join(carpeta, imagen)) + # Aplicar el filtro + img = cv2.GaussianBlur(img, (blur_kernel,blur_kernel),0) + # Guardar la imagen con el mismo nombre + cv2.imwrite(os.path.join(carpeta, imagen), img) + + if test_mode == 0: + nombres = os.listdir("./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT") # obtener los nombres de los archivos en la carpeta MaskDT + ancho = 0 # variable para guardar el ancho acumulado de las ventanas + for i, nombre in enumerate(nombres): # recorrer cada nombre de archivo + imagen = cv2.imread("./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT/" + nombre) # leer la imagen correspondiente + h, w, c = imagen.shape # obtener el alto, ancho y canales de la imagen + aspect_ratio = w / h # calcular la relación de aspecto + cv2.namedWindow(nombre, cv2.WINDOW_NORMAL) # crear una ventana con el nombre del archivo + ancho_ventana = 630 # definir un ancho fijo para las ventanas + alto_ventana = int(ancho_ventana / aspect_ratio) # calcular el alto proporcional al ancho y a la relación de aspecto + cv2.resizeWindow(nombre, ancho_ventana, alto_ventana) # cambiar el tamaño de la ventana según las dimensiones calculadas + cv2.moveWindow(nombre, ancho, 0) # mover la ventana a una posición horizontal según el ancho acumulado + cv2.imshow(nombre, imagen) # mostrar la imagen en la ventana + cv2.setWindowProperty(nombre,cv2.WND_PROP_TOPMOST,1.0) # poner la ventana en primer plano con un valor double + ancho += ancho_ventana + 10 # aumentar el ancho acumulado en 410 píxeles para la siguiente ventana + cv2.waitKey(4000) # esperar a que se presione una tecla para cerrar todas las ventanas + cv2.destroyAllWindows() # cerrar todas las ventanas abiertas por OpenCV + + + else: + + # Directorio de entrada de imágenes + ruta_entrada = "./extensions/Abysz-LAB-Ext/scripts/Run/MaskDT" + + # Obtener el tamaño de la primera imagen en el directorio de entrada + img_path = os.path.join(ruta_entrada, os.listdir(ruta_entrada)[0]) + img = cv2.imread(img_path) + img_size = (img.shape[1], img.shape[0]) + + # Fps del video + fps = 10 + + # Crear objeto VideoWriter + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + video_salida = cv2.VideoWriter('output.mp4', fourcc, fps, img_size) + + # Crear ventana con nombre "video" + cv2.namedWindow("video") + + # Establecer la ventana en primer plano + cv2.setWindowProperty("video", cv2.WND_PROP_TOPMOST,1.0) + + # Crear ventana de visualización + # Leer imágenes en el directorio y agregarlas al video de salida + for file in sorted(os.listdir(ruta_entrada)): + if file.endswith(".jpg") or file.endswith(".jpeg") or file.endswith(".png"): # Verificar que sea una imagen + img = cv2.imread(os.path.join(ruta_entrada, file)) # Leer la imagen + #img_resized = cv2.resize(img, img_size) # Redimensionar la imagen + video_salida.write(img) # Agregar la imagen al video + + # Liberar el objeto VideoWriter + video_salida.release() + + # Crear objeto VideoCapture para leer el archivo de video recién creado + video_capture = cv2.VideoCapture('output.mp4') + + # Crear ventana con nombre "video" + cv2.namedWindow("video") + + # Establecer la ventana en primer plano + cv2.setWindowProperty("video", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_NORMAL) + + # Mostrar el video en una ventana + while True: + ret, img = video_capture.read() + if ret: + cv2.imshow('video', img) + cv2.waitKey(int(1000/fps)) + else: + break + + # Liberar el objeto VideoCapture y cerrar la ventana de visualización + video_capture.release() + cv2.destroyAllWindows() + +def dfi_video(ruta_salida): + # Directorio de entrada de imágenes + ruta_entrada = ruta_salida + + # Obtener el tamaño de la primera imagen en el directorio de entrada + img_path = os.path.join(ruta_entrada, os.listdir(ruta_entrada)[0]) + img = cv2.imread(img_path) + img_size = (img.shape[1], img.shape[0]) + + # Fps del video + fps = 15 + + # Crear objeto VideoWriter + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + video_salida = cv2.VideoWriter('output.mp4', fourcc, fps, img_size) + + # Crear ventana con nombre "video" + cv2.namedWindow("video") + + # Establecer la ventana en primer plano + cv2.setWindowProperty("video", cv2.WND_PROP_TOPMOST,1.0) + + # Crear ventana de visualización + # Leer imágenes en el directorio y agregarlas al video de salida + for file in sorted(os.listdir(ruta_entrada)): + if file.endswith(".jpg") or file.endswith(".jpeg") or file.endswith(".png"): # Verificar que sea una imagen + img = cv2.imread(os.path.join(ruta_entrada, file)) # Leer la imagen + #img_resized = cv2.resize(img, img_size) # Redimensionar la imagen + video_salida.write(img) # Agregar la imagen al video + + # Liberar el objeto VideoWriter + video_salida.release() + + # Crear objeto VideoCapture para leer el archivo de video recién creado + video_capture = cv2.VideoCapture('output.mp4') + + # Crear ventana con nombre "video" + cv2.namedWindow("video") + + # Establecer la ventana en primer plano + cv2.setWindowProperty("video", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_NORMAL) + + # Mostrar el video en una ventana + while True: + ret, img = video_capture.read() + if ret: + cv2.imshow('video', img) + cv2.waitKey(int(1000/fps)) + else: + break + + # Liberar el objeto VideoCapture y cerrar la ventana de visualización + video_capture.release() + cv2.destroyAllWindows() + + +def add_tab(): + print('LAB') + with gr.Blocks(analytics_enabled=False) as demo: + with gr.Tabs(): + with gr.Tab("Main"): + with gr.Row(): + with gr.Column(): + with gr.Column(): + gr.Markdown("# Abysz LAB 0.1.9 Temporal coherence tools") + gr.Markdown("## DFI Render") + with gr.Column(): + ruta_entrada_1 = gr.Textbox(label="Original/reference frames folder", placeholder="RAW frames, or generated ones. (Read the strategies in the guide)") + ruta_entrada_2 = gr.Textbox(label="Generated frames folder", placeholder="The frames of AI generated video") + ruta_salida = gr.Textbox(label="Output folder", placeholder="Remember that each generation overwrites previous frames in the same folder.") + with gr.Accordion("Info", open=False): + gr.Markdown("This process detects static areas between frames (white) and moving areas (black). Use preview map and you will understand this. Basically, it will force the white areas to stay the same on the next frame.") + gr.Markdown("DFI Tolerance adjusts how stiff this process is. Higher = more rigidity + corruption. Lower = more flexible, less corruption, but allows more flick. ") + gr.Markdown("As complement, you can clean the map, to reduce detail and noise, or fatten/expand the areas detected by DFI. It is better that you use preview many times to experience how it works.") + gr.Markdown("### IMPORTANT: The general algorithm is optimized to maintain a balance between deflicking and corruption, so that it is easier to use StableDiffusion at low denoising to reconstruct lost detail while preserving the stability gained.") + with gr.Row(): + denoise_blur = gr.Slider(minimum=0, maximum=30, value=0, step=1, label="Map Denoise") + dfi_strength = gr.Slider(minimum=0.5, maximum=20, value=5, step=0.5, label="DFI Tolerance") + dfi_deghost = gr.Slider(minimum=0, maximum=50, value=0, step=1, label="DFI Expand") + with gr.Accordion("Info", open=False): + gr.Markdown("Here you can preview examples of the motion map for those parameters. It is useful, for example, to adjust denoise if you see that it detects unnecessary graininess. Keep in mind that what you see represents movement between two frames.") + gr.Markdown("A good balance point is to throttle DFI until you find just a few things in areas that should be static. If you force it to be TOO clean, it will mostly increase the overall corruption.") + with gr.Row(): + dfi_test = gr.Button(value="Preview DFI Map") + test_mode = gr.Slider(minimum=0, maximum=100, value=0, step=1, label="Preview amount. 0 = Quick shot") + with gr.Accordion("Advanced", open=False): + with gr.Accordion("Info", open=False): + gr.Markdown("**Inter Denoise:** Reduces render pixelation generated by corruption. However, be careful. It's resource hungry, and might remove excess detail. Not recommended to change size or FPD, but to use Stable Diffusion to remove the pixelation later.") + gr.Markdown("**Inter Blur:** Fine tunes the dynamic blur algorithm for DFI map. Lower = Stronger blur effects. Between 2-3 recommended.") + gr.Markdown("**Corruption Refresh:** To reduce the distortion generated by the process, you can recover original information every X number of frames. Lower number = faster refresh.") + gr.Markdown("**Corruption Preserve:** Here you decide how much corruption keep in each corruption refresh. Low values will recover more of the original frame, with its changes and flickering, in exchange for reducing corruption. You must find the balance that works best for your goal.") + gr.Markdown("**Smooth:** This smoothes the edges of the interpolated areas. Low values are currently recommended until the algorithm is updated.") + with gr.Row(): + inter_denoise = gr.Slider(minimum=1, maximum=25, value=9, step=1, label="Inter Denoise") + inter_denoise_size = gr.Slider(minimum=1, maximum=25, value=9, step=2, label="Inter Denoise Size") + inter_denoise_speed = gr.Slider(minimum=1, maximum=15, value=3, step=1, label="Inter Denoise FPD") + fine_blur = gr.Slider(minimum=1, maximum=5, value=3, step=0.1, label="Inter Blur") + gr.Markdown("### The new dynamic algorithm will handle these parameters. Activate them only for manual control.") + with gr.Row(): + frame_refresh_frequency = gr.Slider(minimum=0, maximum=30, value=0, step=1, label="Corruption Refresh (Lower = Faster)") + refresh_strength = gr.Slider(minimum=0, maximum=100, value=0, step=5, label="Corruption Preserve") + smooth = gr.Slider(minimum=1, maximum=99, value=1, step=2, label="Smooth") + with gr.Row(): + frames_limit = gr.Number(label="Frames to render. 0=ALL") + run_button = gr.Button(value="Run DFI", variant="primary") + output_placeholder = gr.Textbox(label="Status", placeholder="STAND BY...") + video_dfi = gr.Button(value="Show output folder video") + with gr.Column(): + with gr.Column(): + gr.Markdown("# |") + gr.Markdown("## Deflickers Playground") + with gr.Column(): + ruta_entrada_3 = gr.Textbox(label="Frames folder", placeholder="Frames to process") + ruta_salida_1 = gr.Textbox(label="Output folder", placeholder="Processed frames") + with gr.Accordion("Info", open=False): + gr.Markdown("I made this series of deflickers based on the standard that Vegas Pro includes. You can use them together or separately. Be careful when mixing them.") + gr.Markdown("**Blend:** Blends a percentage between frames. This can soften transitions and highlights. 50 is half of each frame. 80 or 20 are recommended values.") + gr.Markdown("**Overlay:** Use the overlay image blending mode. Note that it works particularly good at mid-high values, wich will modify the overall contrast. You will have to decide what works for you.") + gr.Markdown("**Normalize:** Calculates the average between frames to merge them. It may be more practical if you don't have a specific Blend deflicker value in mind.") + ddf_strength = gr.Slider(minimum=0, maximum=1, value=0, step=0.01, label="BLEND (0=Off)") + over_strength = gr.Slider(minimum=0, maximum=1, value=0, step=0.01, label="OVERLAY (0=Off)") + norm_strength = gr.Slider(minimum=0, maximum=1, value=0, step=1, label="NORMALIZE (0=Off))") + dfk_button = gr.Button(value="Deflickers") + with gr.Tab("LAB Tools"): + with gr.Column(): + gr.Markdown("## Style Fuse") + with gr.Accordion("Info", open=False): + gr.Markdown("With this you can merge two sets of frames with overlay technique. For example, you can take a style video that is just lights and/or colors, and overlay it on top of another video.") + gr.Markdown("The resulting video will be useful for use in Img2Img Batch and that the AI render preserves these added color and lighting details, along with the details of the original video.") + with gr.Row(): + ruta_entrada_4 = gr.Textbox(label="Style frames", placeholder="Style to fuse") + ruta_entrada_5 = gr.Textbox(label="Video frames", placeholder="Frames to process") + with gr.Row(): + ruta_salida_2 = gr.Textbox(label="Output folder", placeholder="Processed frames") + fuse_strength = gr.Slider(minimum=0.1, maximum=1, value=0.5, step=0.01, label="Fuse Strength") + fuse_button = gr.Button(value="Fuse") + gr.Markdown("## Video extract") + with gr.Row(): + ruta_entrada_6 = gr.Textbox(label="Video path", placeholder="Remember to use same fps as generated video for DFI") + ruta_salida_3 = gr.Textbox(label="Output folder", placeholder="Processed frames") + with gr.Row(): + fps_count = gr.Number(label="Fps. 0=Original") + vidextract_button = gr.Button(value="Extract") + output_placeholder2 = gr.Textbox(label="Status", placeholder="STAND BY...") + with gr.Tab("Guide"): + with gr.Column(): + gr.Markdown("# What DFI does?") + with gr.Accordion("Info", open=False): + gr.Markdown("DFI processing analyzes the motion of the original video, and attempts to force that information into the generated video. Demo on https://github.com/AbyszOne/Abysz-LAB-Ext") + gr.Markdown("In short, this will reduce flicker in areas of the video that don't need to change, but SD does. For example, for a man smoking, leaning against a pole, it will detect that the pole is static, and will try to prevent it from changing as much as possible.") + gr.Markdown("This is an aggressive process that requires a lot of control for each context. Read the recommended strategies.") + gr.Markdown("Although Video to Video is the most efficient way, a DFI One Shot method is under experimental development as well.") + gr.Markdown("# Usage strategies") + with gr.Accordion("Info", open=False): + gr.Markdown("If you get enough understanding of the tool, you can achieve a much more stable and clean enough rendering. However, this is quite demanding.") + gr.Markdown("Instead, a much friendlier and faster way to use this tool is as an intermediate step. For this, you can allow a reasonable degree of corruption in exchange for more general stability. ") + gr.Markdown("You can then clean up the corruption and recover details with a second step in Stable Diffusion at low denoising (0.2-0.4), using the same parameters and seed.") + gr.Markdown("In this way, the final result will have the stability that we have gained, maintaining final detail. If you find a balanced workflow, you will get something at least much more coherent and stable than the raw AI render.") + gr.Markdown("**OPTIONAL:** Although not ideal, you can use the same AI generated video as the source, instead of the RAW. The trick is to use DFI and denoise to wash out map details so that you reduce low/mid changes between frames. If you only need a soft deflick, it is a valid option.") + + dt_inputs=[ruta_entrada_1, ruta_entrada_2, denoise_blur, dfi_strength, dfi_deghost, test_mode, smooth] + run_inputs=[ruta_entrada_1, ruta_entrada_2, ruta_salida, denoise_blur, dfi_strength, dfi_deghost, test_mode, inter_denoise, inter_denoise_size, inter_denoise_speed, fine_blur, frame_refresh_frequency, refresh_strength, smooth, frames_limit] + dfk_inputs=[ruta_entrada_3, ruta_salida_1, ddf_strength, over_strength, norm_strength] + fuse_inputs=[ruta_entrada_4, ruta_entrada_5, ruta_salida_2, fuse_strength] + ve_inputs=[ruta_entrada_6, ruta_salida_3, fps_count] + + dfi_test.click(fn=test_dfi, inputs=dt_inputs, outputs=output_placeholder) + run_button.click(fn=main, inputs=run_inputs, outputs=output_placeholder) + video_dfi.click(fn=dfi_video, inputs=ruta_salida, outputs=output_placeholder) + dfk_button.click(fn=deflickers, inputs=dfk_inputs, outputs=output_placeholder) + fuse_button.click(fn=over_fuse, inputs=fuse_inputs, outputs=output_placeholder2) + vidextract_button.click(fn=extract_video, inputs=ve_inputs, outputs=output_placeholder2) + return [(demo, "Abysz LAB", "demo")] + +script_callbacks.on_ui_tabs(add_tab) diff --git a/Abysz-LAB-Ext/scripts/__pycache__/Abysz_Lab.cpython-310.pyc b/Abysz-LAB-Ext/scripts/__pycache__/Abysz_Lab.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65ea1c5455a65b9716d97e4fd59ff859a860d4a3 Binary files /dev/null and b/Abysz-LAB-Ext/scripts/__pycache__/Abysz_Lab.cpython-310.pyc differ diff --git a/Auto-Photoshop-StableDiffusion-Plugin/scripts/__pycache__/main.cpython-310.pyc b/Auto-Photoshop-StableDiffusion-Plugin/scripts/__pycache__/main.cpython-310.pyc index d0eea9bdcaa77c28d527bafad1bbfa416fd8cc35..c8f15ba890958fd8d66714ff68b4348a5d47d478 100644 Binary files a/Auto-Photoshop-StableDiffusion-Plugin/scripts/__pycache__/main.cpython-310.pyc and b/Auto-Photoshop-StableDiffusion-Plugin/scripts/__pycache__/main.cpython-310.pyc differ diff --git a/Auto-Photoshop-StableDiffusion-Plugin/scripts/__pycache__/test.cpython-310.pyc b/Auto-Photoshop-StableDiffusion-Plugin/scripts/__pycache__/test.cpython-310.pyc index 912709d92bc8c47f5d015a4fb000925b67e200f4..ebb61da59d0022d5495b025a698bea325ddd6ddc 100644 Binary files a/Auto-Photoshop-StableDiffusion-Plugin/scripts/__pycache__/test.cpython-310.pyc and b/Auto-Photoshop-StableDiffusion-Plugin/scripts/__pycache__/test.cpython-310.pyc differ diff --git a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/img2imgapi.cpython-310.pyc b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/img2imgapi.cpython-310.pyc index 8ee5e7a2635a81e0762995c8c2c9d6e6b855079f..b44be1892b595172af20e27eda4c54fc7807ed59 100644 Binary files a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/img2imgapi.cpython-310.pyc and b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/img2imgapi.cpython-310.pyc differ diff --git a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/metadata_to_json.cpython-310.pyc b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/metadata_to_json.cpython-310.pyc index fad790be07cbff9533363df63fdbdc3576f2efd4..1ff41672341bb0df50e1d6d0bf3c7e3aee05e66f 100644 Binary files a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/metadata_to_json.cpython-310.pyc and b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/metadata_to_json.cpython-310.pyc differ diff --git a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/prompt_shortcut.cpython-310.pyc b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/prompt_shortcut.cpython-310.pyc index 5923d1f1447aa24a60e233e7261c3c9ccc1139c5..4d8d111bf2aa81ba00c009d985aaf1bcd8b384ff 100644 Binary files a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/prompt_shortcut.cpython-310.pyc and b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/prompt_shortcut.cpython-310.pyc differ diff --git a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/search.cpython-310.pyc b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/search.cpython-310.pyc index 57bddffcd7ab4a980b7391b7c6030bc07beb15dc..5ac760f08aedc34e7f7e6432876c83cdce565ca7 100644 Binary files a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/search.cpython-310.pyc and b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/search.cpython-310.pyc differ diff --git a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/serverHelper.cpython-310.pyc b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/serverHelper.cpython-310.pyc index 8253878b651927107d974cb14a3f0a519a9dfa29..914f68680951914a792160afe6cd79b033aca49d 100644 Binary files a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/serverHelper.cpython-310.pyc and b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/serverHelper.cpython-310.pyc differ diff --git a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/serverMain.cpython-310.pyc b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/serverMain.cpython-310.pyc index 7470964705d7fcf604146a612b425d37a17489c0..a5fd86c73d102417e5e8c775dca3e1ee6f785211 100644 Binary files a/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/serverMain.cpython-310.pyc and b/Auto-Photoshop-StableDiffusion-Plugin/server/python_server/__pycache__/serverMain.cpython-310.pyc differ diff --git a/SD-CN-Animation/.gitignore b/SD-CN-Animation/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..94f11f5e352ba30394b9d11f42d57c029a65073b --- /dev/null +++ b/SD-CN-Animation/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +out/ +videos/ +FP_Res/ +result.mp4 +*.pth \ No newline at end of file diff --git a/SD-CN-Animation/FloweR/__pycache__/model.cpython-310.pyc b/SD-CN-Animation/FloweR/__pycache__/model.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..736fd7c197d8de045722c66c05e4d06ad284abba Binary files /dev/null and b/SD-CN-Animation/FloweR/__pycache__/model.cpython-310.pyc differ diff --git a/SD-CN-Animation/FloweR/model.py b/SD-CN-Animation/FloweR/model.py new file mode 100644 index 0000000000000000000000000000000000000000..612853c16a2d7f42f8dddb6fb231535913fc3ca5 --- /dev/null +++ b/SD-CN-Animation/FloweR/model.py @@ -0,0 +1,191 @@ +import torch +import torch.nn as nn +import torch.functional as F + +# Define the model +class FloweR(nn.Module): + def __init__(self, input_size = (384, 384), window_size = 4): + super(FloweR, self).__init__() + + self.input_size = input_size + self.window_size = window_size + + # 2 channels for optical flow + # 1 channel for occlusion mask + # 3 channels for next frame prediction + self.out_channels = 6 + + + #INPUT: 384 x 384 x 4 * 3 + + ### DOWNSCALE ### + self.conv_block_1 = nn.Sequential( + nn.Conv2d(3 * self.window_size, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 384 x 384 x 128 + + self.conv_block_2 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 192 x 192 x 128 + + self.conv_block_3 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 96 x 96 x 128 + + self.conv_block_4 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 48 x 48 x 128 + + self.conv_block_5 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 24 x 24 x 128 + + self.conv_block_6 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 12 x 12 x 128 + + self.conv_block_7 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 6 x 6 x 128 + + self.conv_block_8 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 3 x 3 x 128 - 9 input tokens + + ### Transformer part ### + # To be done + + ### UPSCALE ### + self.conv_block_9 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 6 x 6 x 128 + + self.conv_block_10 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 12 x 12 x 128 + + self.conv_block_11 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 24 x 24 x 128 + + self.conv_block_12 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 48 x 48 x 128 + + self.conv_block_13 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 96 x 96 x 128 + + self.conv_block_14 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 192 x 192 x 128 + + self.conv_block_15 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 384 x 384 x 128 + + self.conv_block_16 = nn.Conv2d(128, self.out_channels, kernel_size=3, stride=1, padding='same') + + def forward(self, input_frames): + + if input_frames.size(1) != self.window_size: + raise Exception(f'Shape of the input is not compatable. There should be exactly {self.window_size} frames in an input video.') + + h, w = self.input_size + # batch, frames, height, width, colors + input_frames_permuted = input_frames.permute((0, 1, 4, 2, 3)) + # batch, frames, colors, height, width + + in_x = input_frames_permuted.reshape(-1, self.window_size * 3, self.input_size[0], self.input_size[1]) + + ### DOWNSCALE ### + block_1_out = self.conv_block_1(in_x) # 384 x 384 x 128 + block_2_out = self.conv_block_2(block_1_out) # 192 x 192 x 128 + block_3_out = self.conv_block_3(block_2_out) # 96 x 96 x 128 + block_4_out = self.conv_block_4(block_3_out) # 48 x 48 x 128 + block_5_out = self.conv_block_5(block_4_out) # 24 x 24 x 128 + block_6_out = self.conv_block_6(block_5_out) # 12 x 12 x 128 + block_7_out = self.conv_block_7(block_6_out) # 6 x 6 x 128 + block_8_out = self.conv_block_8(block_7_out) # 3 x 3 x 128 + + ### UPSCALE ### + block_9_out = block_7_out + self.conv_block_9(block_8_out) # 6 x 6 x 128 + block_10_out = block_6_out + self.conv_block_10(block_9_out) # 12 x 12 x 128 + block_11_out = block_5_out + self.conv_block_11(block_10_out) # 24 x 24 x 128 + block_12_out = block_4_out + self.conv_block_12(block_11_out) # 48 x 48 x 128 + block_13_out = block_3_out + self.conv_block_13(block_12_out) # 96 x 96 x 128 + block_14_out = block_2_out + self.conv_block_14(block_13_out) # 192 x 192 x 128 + block_15_out = block_1_out + self.conv_block_15(block_14_out) # 384 x 384 x 128 + + block_16_out = self.conv_block_16(block_15_out) # 384 x 384 x (2 + 1 + 3) + out = block_16_out.reshape(-1, self.out_channels, self.input_size[0], self.input_size[1]) + + ### for future model training ### + device = out.get_device() + + pred_flow = out[:,:2,:,:] * 255 # (-255, 255) + pred_occl = (out[:,2:3,:,:] + 1) / 2 # [0, 1] + pred_next = out[:,3:6,:,:] + + # Generate sampling grids + + # Create grid to upsample input + ''' + d = torch.linspace(-1, 1, 8) + meshx, meshy = torch.meshgrid((d, d)) + grid = torch.stack((meshy, meshx), 2) + grid = grid.unsqueeze(0) ''' + + grid_y, grid_x = torch.meshgrid(torch.arange(0, h), torch.arange(0, w)) + flow_grid = torch.stack((grid_x, grid_y), dim=0).float() + flow_grid = flow_grid.unsqueeze(0).to(device=device) + flow_grid = flow_grid + pred_flow + + flow_grid[:, 0, :, :] = 2 * flow_grid[:, 0, :, :] / (w - 1) - 1 + flow_grid[:, 1, :, :] = 2 * flow_grid[:, 1, :, :] / (h - 1) - 1 + # batch, flow_chanels, height, width + flow_grid = flow_grid.permute(0, 2, 3, 1) + # batch, height, width, flow_chanels + + previous_frame = input_frames_permuted[:, -1, :, :, :] + sampling_mode = "bilinear" if self.training else "nearest" + warped_frame = torch.nn.functional.grid_sample(previous_frame, flow_grid, mode=sampling_mode, padding_mode="reflection", align_corners=False) + alpha_mask = torch.clip(pred_occl * 10, 0, 1) * 0.04 + pred_next = torch.clip(pred_next, -1, 1) + warped_frame = torch.clip(warped_frame, -1, 1) + next_frame = pred_next * alpha_mask + warped_frame * (1 - alpha_mask) + + res = torch.cat((pred_flow / 255, pred_occl * 2 - 1, next_frame), dim=1) + + # batch, channels, height, width + res = res.permute((0, 2, 3, 1)) + # batch, height, width, channels + return res \ No newline at end of file diff --git a/SD-CN-Animation/LICENSE b/SD-CN-Animation/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..a542f7e27f9988ec69bf0dbf37e8f9dcae6b88d8 --- /dev/null +++ b/SD-CN-Animation/LICENSE @@ -0,0 +1,22 @@ +License + +Copyright (c) 2023 Alexey Borsky + +The Software is subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This repository can only be used for personal/research/non-commercial purposes. +However, for commercial requests, please contact us directly at +borsky.alexey@gmail.com. This restriction applies only to the code itself, all +derivative works made using this repository (i.e. images and video) can be +used for any purposes without restrictions. diff --git a/SD-CN-Animation/RAFT/LICENSE b/SD-CN-Animation/RAFT/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ed13d8404f0f1315ee323b2c8d1b2d8f77b5c82f --- /dev/null +++ b/SD-CN-Animation/RAFT/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020, princeton-vl +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +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/SD-CN-Animation/RAFT/__pycache__/corr.cpython-310.pyc b/SD-CN-Animation/RAFT/__pycache__/corr.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b9fbf79c5b7b354d5034934de34c82f1eca901b Binary files /dev/null and b/SD-CN-Animation/RAFT/__pycache__/corr.cpython-310.pyc differ diff --git a/SD-CN-Animation/RAFT/__pycache__/extractor.cpython-310.pyc b/SD-CN-Animation/RAFT/__pycache__/extractor.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5db0877c6f2d26aa940bbb0137e9d4365f490c54 Binary files /dev/null and b/SD-CN-Animation/RAFT/__pycache__/extractor.cpython-310.pyc differ diff --git a/SD-CN-Animation/RAFT/__pycache__/raft.cpython-310.pyc b/SD-CN-Animation/RAFT/__pycache__/raft.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..864290ab87bbffd604ed7097481dc695ccdff1c2 Binary files /dev/null and b/SD-CN-Animation/RAFT/__pycache__/raft.cpython-310.pyc differ diff --git a/SD-CN-Animation/RAFT/__pycache__/update.cpython-310.pyc b/SD-CN-Animation/RAFT/__pycache__/update.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d09af6af1936926827ffc0a17c8b84df6ae56e9 Binary files /dev/null and b/SD-CN-Animation/RAFT/__pycache__/update.cpython-310.pyc differ diff --git a/SD-CN-Animation/RAFT/corr.py b/SD-CN-Animation/RAFT/corr.py new file mode 100644 index 0000000000000000000000000000000000000000..3e00d8f1d73f082ea0fd3bae5bd96279a88517c6 --- /dev/null +++ b/SD-CN-Animation/RAFT/corr.py @@ -0,0 +1,91 @@ +import torch +import torch.nn.functional as F +from RAFT.utils.utils import bilinear_sampler, coords_grid + +try: + import alt_cuda_corr +except: + # alt_cuda_corr is not compiled + pass + + +class CorrBlock: + def __init__(self, fmap1, fmap2, num_levels=4, radius=4): + self.num_levels = num_levels + self.radius = radius + self.corr_pyramid = [] + + # all pairs correlation + corr = CorrBlock.corr(fmap1, fmap2) + + batch, h1, w1, dim, h2, w2 = corr.shape + corr = corr.reshape(batch*h1*w1, dim, h2, w2) + + self.corr_pyramid.append(corr) + for i in range(self.num_levels-1): + corr = F.avg_pool2d(corr, 2, stride=2) + self.corr_pyramid.append(corr) + + def __call__(self, coords): + r = self.radius + coords = coords.permute(0, 2, 3, 1) + batch, h1, w1, _ = coords.shape + + out_pyramid = [] + for i in range(self.num_levels): + corr = self.corr_pyramid[i] + dx = torch.linspace(-r, r, 2*r+1, device=coords.device) + dy = torch.linspace(-r, r, 2*r+1, device=coords.device) + delta = torch.stack(torch.meshgrid(dy, dx), axis=-1) + + centroid_lvl = coords.reshape(batch*h1*w1, 1, 1, 2) / 2**i + delta_lvl = delta.view(1, 2*r+1, 2*r+1, 2) + coords_lvl = centroid_lvl + delta_lvl + + corr = bilinear_sampler(corr, coords_lvl) + corr = corr.view(batch, h1, w1, -1) + out_pyramid.append(corr) + + out = torch.cat(out_pyramid, dim=-1) + return out.permute(0, 3, 1, 2).contiguous().float() + + @staticmethod + def corr(fmap1, fmap2): + batch, dim, ht, wd = fmap1.shape + fmap1 = fmap1.view(batch, dim, ht*wd) + fmap2 = fmap2.view(batch, dim, ht*wd) + + corr = torch.matmul(fmap1.transpose(1,2), fmap2) + corr = corr.view(batch, ht, wd, 1, ht, wd) + return corr / torch.sqrt(torch.tensor(dim).float()) + + +class AlternateCorrBlock: + def __init__(self, fmap1, fmap2, num_levels=4, radius=4): + self.num_levels = num_levels + self.radius = radius + + self.pyramid = [(fmap1, fmap2)] + for i in range(self.num_levels): + fmap1 = F.avg_pool2d(fmap1, 2, stride=2) + fmap2 = F.avg_pool2d(fmap2, 2, stride=2) + self.pyramid.append((fmap1, fmap2)) + + def __call__(self, coords): + coords = coords.permute(0, 2, 3, 1) + B, H, W, _ = coords.shape + dim = self.pyramid[0][0].shape[1] + + corr_list = [] + for i in range(self.num_levels): + r = self.radius + fmap1_i = self.pyramid[0][0].permute(0, 2, 3, 1).contiguous() + fmap2_i = self.pyramid[i][1].permute(0, 2, 3, 1).contiguous() + + coords_i = (coords / 2**i).reshape(B, 1, H, W, 2).contiguous() + corr, = alt_cuda_corr.forward(fmap1_i, fmap2_i, coords_i, r) + corr_list.append(corr.squeeze(1)) + + corr = torch.stack(corr_list, dim=1) + corr = corr.reshape(B, -1, H, W) + return corr / torch.sqrt(torch.tensor(dim).float()) diff --git a/SD-CN-Animation/RAFT/extractor.py b/SD-CN-Animation/RAFT/extractor.py new file mode 100644 index 0000000000000000000000000000000000000000..9a9c759d1243d4694e8656c2f6f8a37e53edd009 --- /dev/null +++ b/SD-CN-Animation/RAFT/extractor.py @@ -0,0 +1,267 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class ResidualBlock(nn.Module): + def __init__(self, in_planes, planes, norm_fn='group', stride=1): + super(ResidualBlock, self).__init__() + + self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, padding=1, stride=stride) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, padding=1) + self.relu = nn.ReLU(inplace=True) + + num_groups = planes // 8 + + if norm_fn == 'group': + self.norm1 = nn.GroupNorm(num_groups=num_groups, num_channels=planes) + self.norm2 = nn.GroupNorm(num_groups=num_groups, num_channels=planes) + if not stride == 1: + self.norm3 = nn.GroupNorm(num_groups=num_groups, num_channels=planes) + + elif norm_fn == 'batch': + self.norm1 = nn.BatchNorm2d(planes) + self.norm2 = nn.BatchNorm2d(planes) + if not stride == 1: + self.norm3 = nn.BatchNorm2d(planes) + + elif norm_fn == 'instance': + self.norm1 = nn.InstanceNorm2d(planes) + self.norm2 = nn.InstanceNorm2d(planes) + if not stride == 1: + self.norm3 = nn.InstanceNorm2d(planes) + + elif norm_fn == 'none': + self.norm1 = nn.Sequential() + self.norm2 = nn.Sequential() + if not stride == 1: + self.norm3 = nn.Sequential() + + if stride == 1: + self.downsample = None + + else: + self.downsample = nn.Sequential( + nn.Conv2d(in_planes, planes, kernel_size=1, stride=stride), self.norm3) + + + def forward(self, x): + y = x + y = self.relu(self.norm1(self.conv1(y))) + y = self.relu(self.norm2(self.conv2(y))) + + if self.downsample is not None: + x = self.downsample(x) + + return self.relu(x+y) + + + +class BottleneckBlock(nn.Module): + def __init__(self, in_planes, planes, norm_fn='group', stride=1): + super(BottleneckBlock, self).__init__() + + self.conv1 = nn.Conv2d(in_planes, planes//4, kernel_size=1, padding=0) + self.conv2 = nn.Conv2d(planes//4, planes//4, kernel_size=3, padding=1, stride=stride) + self.conv3 = nn.Conv2d(planes//4, planes, kernel_size=1, padding=0) + self.relu = nn.ReLU(inplace=True) + + num_groups = planes // 8 + + if norm_fn == 'group': + self.norm1 = nn.GroupNorm(num_groups=num_groups, num_channels=planes//4) + self.norm2 = nn.GroupNorm(num_groups=num_groups, num_channels=planes//4) + self.norm3 = nn.GroupNorm(num_groups=num_groups, num_channels=planes) + if not stride == 1: + self.norm4 = nn.GroupNorm(num_groups=num_groups, num_channels=planes) + + elif norm_fn == 'batch': + self.norm1 = nn.BatchNorm2d(planes//4) + self.norm2 = nn.BatchNorm2d(planes//4) + self.norm3 = nn.BatchNorm2d(planes) + if not stride == 1: + self.norm4 = nn.BatchNorm2d(planes) + + elif norm_fn == 'instance': + self.norm1 = nn.InstanceNorm2d(planes//4) + self.norm2 = nn.InstanceNorm2d(planes//4) + self.norm3 = nn.InstanceNorm2d(planes) + if not stride == 1: + self.norm4 = nn.InstanceNorm2d(planes) + + elif norm_fn == 'none': + self.norm1 = nn.Sequential() + self.norm2 = nn.Sequential() + self.norm3 = nn.Sequential() + if not stride == 1: + self.norm4 = nn.Sequential() + + if stride == 1: + self.downsample = None + + else: + self.downsample = nn.Sequential( + nn.Conv2d(in_planes, planes, kernel_size=1, stride=stride), self.norm4) + + + def forward(self, x): + y = x + y = self.relu(self.norm1(self.conv1(y))) + y = self.relu(self.norm2(self.conv2(y))) + y = self.relu(self.norm3(self.conv3(y))) + + if self.downsample is not None: + x = self.downsample(x) + + return self.relu(x+y) + +class BasicEncoder(nn.Module): + def __init__(self, output_dim=128, norm_fn='batch', dropout=0.0): + super(BasicEncoder, self).__init__() + self.norm_fn = norm_fn + + if self.norm_fn == 'group': + self.norm1 = nn.GroupNorm(num_groups=8, num_channels=64) + + elif self.norm_fn == 'batch': + self.norm1 = nn.BatchNorm2d(64) + + elif self.norm_fn == 'instance': + self.norm1 = nn.InstanceNorm2d(64) + + elif self.norm_fn == 'none': + self.norm1 = nn.Sequential() + + self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3) + self.relu1 = nn.ReLU(inplace=True) + + self.in_planes = 64 + self.layer1 = self._make_layer(64, stride=1) + self.layer2 = self._make_layer(96, stride=2) + self.layer3 = self._make_layer(128, stride=2) + + # output convolution + self.conv2 = nn.Conv2d(128, output_dim, kernel_size=1) + + self.dropout = None + if dropout > 0: + self.dropout = nn.Dropout2d(p=dropout) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, (nn.BatchNorm2d, nn.InstanceNorm2d, nn.GroupNorm)): + if m.weight is not None: + nn.init.constant_(m.weight, 1) + if m.bias is not None: + nn.init.constant_(m.bias, 0) + + def _make_layer(self, dim, stride=1): + layer1 = ResidualBlock(self.in_planes, dim, self.norm_fn, stride=stride) + layer2 = ResidualBlock(dim, dim, self.norm_fn, stride=1) + layers = (layer1, layer2) + + self.in_planes = dim + return nn.Sequential(*layers) + + + def forward(self, x): + + # if input is list, combine batch dimension + is_list = isinstance(x, tuple) or isinstance(x, list) + if is_list: + batch_dim = x[0].shape[0] + x = torch.cat(x, dim=0) + + x = self.conv1(x) + x = self.norm1(x) + x = self.relu1(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + + x = self.conv2(x) + + if self.training and self.dropout is not None: + x = self.dropout(x) + + if is_list: + x = torch.split(x, [batch_dim, batch_dim], dim=0) + + return x + + +class SmallEncoder(nn.Module): + def __init__(self, output_dim=128, norm_fn='batch', dropout=0.0): + super(SmallEncoder, self).__init__() + self.norm_fn = norm_fn + + if self.norm_fn == 'group': + self.norm1 = nn.GroupNorm(num_groups=8, num_channels=32) + + elif self.norm_fn == 'batch': + self.norm1 = nn.BatchNorm2d(32) + + elif self.norm_fn == 'instance': + self.norm1 = nn.InstanceNorm2d(32) + + elif self.norm_fn == 'none': + self.norm1 = nn.Sequential() + + self.conv1 = nn.Conv2d(3, 32, kernel_size=7, stride=2, padding=3) + self.relu1 = nn.ReLU(inplace=True) + + self.in_planes = 32 + self.layer1 = self._make_layer(32, stride=1) + self.layer2 = self._make_layer(64, stride=2) + self.layer3 = self._make_layer(96, stride=2) + + self.dropout = None + if dropout > 0: + self.dropout = nn.Dropout2d(p=dropout) + + self.conv2 = nn.Conv2d(96, output_dim, kernel_size=1) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') + elif isinstance(m, (nn.BatchNorm2d, nn.InstanceNorm2d, nn.GroupNorm)): + if m.weight is not None: + nn.init.constant_(m.weight, 1) + if m.bias is not None: + nn.init.constant_(m.bias, 0) + + def _make_layer(self, dim, stride=1): + layer1 = BottleneckBlock(self.in_planes, dim, self.norm_fn, stride=stride) + layer2 = BottleneckBlock(dim, dim, self.norm_fn, stride=1) + layers = (layer1, layer2) + + self.in_planes = dim + return nn.Sequential(*layers) + + + def forward(self, x): + + # if input is list, combine batch dimension + is_list = isinstance(x, tuple) or isinstance(x, list) + if is_list: + batch_dim = x[0].shape[0] + x = torch.cat(x, dim=0) + + x = self.conv1(x) + x = self.norm1(x) + x = self.relu1(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.conv2(x) + + if self.training and self.dropout is not None: + x = self.dropout(x) + + if is_list: + x = torch.split(x, [batch_dim, batch_dim], dim=0) + + return x diff --git a/SD-CN-Animation/RAFT/raft.py b/SD-CN-Animation/RAFT/raft.py new file mode 100644 index 0000000000000000000000000000000000000000..1561cb423a17e6d36591b37d93511e4e41561a2f --- /dev/null +++ b/SD-CN-Animation/RAFT/raft.py @@ -0,0 +1,144 @@ +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F + +from RAFT.update import BasicUpdateBlock, SmallUpdateBlock +from RAFT.extractor import BasicEncoder, SmallEncoder +from RAFT.corr import CorrBlock, AlternateCorrBlock +from RAFT.utils.utils import bilinear_sampler, coords_grid, upflow8 + +try: + autocast = torch.cuda.amp.autocast +except: + # dummy autocast for PyTorch < 1.6 + class autocast: + def __init__(self, enabled): + pass + def __enter__(self): + pass + def __exit__(self, *args): + pass + + +class RAFT(nn.Module): + def __init__(self, args): + super(RAFT, self).__init__() + self.args = args + + if args.small: + self.hidden_dim = hdim = 96 + self.context_dim = cdim = 64 + args.corr_levels = 4 + args.corr_radius = 3 + + else: + self.hidden_dim = hdim = 128 + self.context_dim = cdim = 128 + args.corr_levels = 4 + args.corr_radius = 4 + + if 'dropout' not in self.args: + self.args.dropout = 0 + + if 'alternate_corr' not in self.args: + self.args.alternate_corr = False + + # feature network, context network, and update block + if args.small: + self.fnet = SmallEncoder(output_dim=128, norm_fn='instance', dropout=args.dropout) + self.cnet = SmallEncoder(output_dim=hdim+cdim, norm_fn='none', dropout=args.dropout) + self.update_block = SmallUpdateBlock(self.args, hidden_dim=hdim) + + else: + self.fnet = BasicEncoder(output_dim=256, norm_fn='instance', dropout=args.dropout) + self.cnet = BasicEncoder(output_dim=hdim+cdim, norm_fn='batch', dropout=args.dropout) + self.update_block = BasicUpdateBlock(self.args, hidden_dim=hdim) + + def freeze_bn(self): + for m in self.modules(): + if isinstance(m, nn.BatchNorm2d): + m.eval() + + def initialize_flow(self, img): + """ Flow is represented as difference between two coordinate grids flow = coords1 - coords0""" + N, C, H, W = img.shape + coords0 = coords_grid(N, H//8, W//8, device=img.device) + coords1 = coords_grid(N, H//8, W//8, device=img.device) + + # optical flow computed as difference: flow = coords1 - coords0 + return coords0, coords1 + + def upsample_flow(self, flow, mask): + """ Upsample flow field [H/8, W/8, 2] -> [H, W, 2] using convex combination """ + N, _, H, W = flow.shape + mask = mask.view(N, 1, 9, 8, 8, H, W) + mask = torch.softmax(mask, dim=2) + + up_flow = F.unfold(8 * flow, [3,3], padding=1) + up_flow = up_flow.view(N, 2, 9, 1, 1, H, W) + + up_flow = torch.sum(mask * up_flow, dim=2) + up_flow = up_flow.permute(0, 1, 4, 2, 5, 3) + return up_flow.reshape(N, 2, 8*H, 8*W) + + + def forward(self, image1, image2, iters=12, flow_init=None, upsample=True, test_mode=False): + """ Estimate optical flow between pair of frames """ + + image1 = 2 * (image1 / 255.0) - 1.0 + image2 = 2 * (image2 / 255.0) - 1.0 + + image1 = image1.contiguous() + image2 = image2.contiguous() + + hdim = self.hidden_dim + cdim = self.context_dim + + # run the feature network + with autocast(enabled=self.args.mixed_precision): + fmap1, fmap2 = self.fnet([image1, image2]) + + fmap1 = fmap1.float() + fmap2 = fmap2.float() + if self.args.alternate_corr: + corr_fn = AlternateCorrBlock(fmap1, fmap2, radius=self.args.corr_radius) + else: + corr_fn = CorrBlock(fmap1, fmap2, radius=self.args.corr_radius) + + # run the context network + with autocast(enabled=self.args.mixed_precision): + cnet = self.cnet(image1) + net, inp = torch.split(cnet, [hdim, cdim], dim=1) + net = torch.tanh(net) + inp = torch.relu(inp) + + coords0, coords1 = self.initialize_flow(image1) + + if flow_init is not None: + coords1 = coords1 + flow_init + + flow_predictions = [] + for itr in range(iters): + coords1 = coords1.detach() + corr = corr_fn(coords1) # index correlation volume + + flow = coords1 - coords0 + with autocast(enabled=self.args.mixed_precision): + net, up_mask, delta_flow = self.update_block(net, inp, corr, flow) + + # F(t+1) = F(t) + \Delta(t) + coords1 = coords1 + delta_flow + + # upsample predictions + if up_mask is None: + flow_up = upflow8(coords1 - coords0) + else: + flow_up = self.upsample_flow(coords1 - coords0, up_mask) + + flow_predictions.append(flow_up) + + if test_mode: + return coords1 - coords0, flow_up + + return flow_predictions diff --git a/SD-CN-Animation/RAFT/update.py b/SD-CN-Animation/RAFT/update.py new file mode 100644 index 0000000000000000000000000000000000000000..f940497f9b5eb1c12091574fe9a0223a1b196d50 --- /dev/null +++ b/SD-CN-Animation/RAFT/update.py @@ -0,0 +1,139 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + + +class FlowHead(nn.Module): + def __init__(self, input_dim=128, hidden_dim=256): + super(FlowHead, self).__init__() + self.conv1 = nn.Conv2d(input_dim, hidden_dim, 3, padding=1) + self.conv2 = nn.Conv2d(hidden_dim, 2, 3, padding=1) + self.relu = nn.ReLU(inplace=True) + + def forward(self, x): + return self.conv2(self.relu(self.conv1(x))) + +class ConvGRU(nn.Module): + def __init__(self, hidden_dim=128, input_dim=192+128): + super(ConvGRU, self).__init__() + self.convz = nn.Conv2d(hidden_dim+input_dim, hidden_dim, 3, padding=1) + self.convr = nn.Conv2d(hidden_dim+input_dim, hidden_dim, 3, padding=1) + self.convq = nn.Conv2d(hidden_dim+input_dim, hidden_dim, 3, padding=1) + + def forward(self, h, x): + hx = torch.cat([h, x], dim=1) + + z = torch.sigmoid(self.convz(hx)) + r = torch.sigmoid(self.convr(hx)) + q = torch.tanh(self.convq(torch.cat([r*h, x], dim=1))) + + h = (1-z) * h + z * q + return h + +class SepConvGRU(nn.Module): + def __init__(self, hidden_dim=128, input_dim=192+128): + super(SepConvGRU, self).__init__() + self.convz1 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (1,5), padding=(0,2)) + self.convr1 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (1,5), padding=(0,2)) + self.convq1 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (1,5), padding=(0,2)) + + self.convz2 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (5,1), padding=(2,0)) + self.convr2 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (5,1), padding=(2,0)) + self.convq2 = nn.Conv2d(hidden_dim+input_dim, hidden_dim, (5,1), padding=(2,0)) + + + def forward(self, h, x): + # horizontal + hx = torch.cat([h, x], dim=1) + z = torch.sigmoid(self.convz1(hx)) + r = torch.sigmoid(self.convr1(hx)) + q = torch.tanh(self.convq1(torch.cat([r*h, x], dim=1))) + h = (1-z) * h + z * q + + # vertical + hx = torch.cat([h, x], dim=1) + z = torch.sigmoid(self.convz2(hx)) + r = torch.sigmoid(self.convr2(hx)) + q = torch.tanh(self.convq2(torch.cat([r*h, x], dim=1))) + h = (1-z) * h + z * q + + return h + +class SmallMotionEncoder(nn.Module): + def __init__(self, args): + super(SmallMotionEncoder, self).__init__() + cor_planes = args.corr_levels * (2*args.corr_radius + 1)**2 + self.convc1 = nn.Conv2d(cor_planes, 96, 1, padding=0) + self.convf1 = nn.Conv2d(2, 64, 7, padding=3) + self.convf2 = nn.Conv2d(64, 32, 3, padding=1) + self.conv = nn.Conv2d(128, 80, 3, padding=1) + + def forward(self, flow, corr): + cor = F.relu(self.convc1(corr)) + flo = F.relu(self.convf1(flow)) + flo = F.relu(self.convf2(flo)) + cor_flo = torch.cat([cor, flo], dim=1) + out = F.relu(self.conv(cor_flo)) + return torch.cat([out, flow], dim=1) + +class BasicMotionEncoder(nn.Module): + def __init__(self, args): + super(BasicMotionEncoder, self).__init__() + cor_planes = args.corr_levels * (2*args.corr_radius + 1)**2 + self.convc1 = nn.Conv2d(cor_planes, 256, 1, padding=0) + self.convc2 = nn.Conv2d(256, 192, 3, padding=1) + self.convf1 = nn.Conv2d(2, 128, 7, padding=3) + self.convf2 = nn.Conv2d(128, 64, 3, padding=1) + self.conv = nn.Conv2d(64+192, 128-2, 3, padding=1) + + def forward(self, flow, corr): + cor = F.relu(self.convc1(corr)) + cor = F.relu(self.convc2(cor)) + flo = F.relu(self.convf1(flow)) + flo = F.relu(self.convf2(flo)) + + cor_flo = torch.cat([cor, flo], dim=1) + out = F.relu(self.conv(cor_flo)) + return torch.cat([out, flow], dim=1) + +class SmallUpdateBlock(nn.Module): + def __init__(self, args, hidden_dim=96): + super(SmallUpdateBlock, self).__init__() + self.encoder = SmallMotionEncoder(args) + self.gru = ConvGRU(hidden_dim=hidden_dim, input_dim=82+64) + self.flow_head = FlowHead(hidden_dim, hidden_dim=128) + + def forward(self, net, inp, corr, flow): + motion_features = self.encoder(flow, corr) + inp = torch.cat([inp, motion_features], dim=1) + net = self.gru(net, inp) + delta_flow = self.flow_head(net) + + return net, None, delta_flow + +class BasicUpdateBlock(nn.Module): + def __init__(self, args, hidden_dim=128, input_dim=128): + super(BasicUpdateBlock, self).__init__() + self.args = args + self.encoder = BasicMotionEncoder(args) + self.gru = SepConvGRU(hidden_dim=hidden_dim, input_dim=128+hidden_dim) + self.flow_head = FlowHead(hidden_dim, hidden_dim=256) + + self.mask = nn.Sequential( + nn.Conv2d(128, 256, 3, padding=1), + nn.ReLU(inplace=True), + nn.Conv2d(256, 64*9, 1, padding=0)) + + def forward(self, net, inp, corr, flow, upsample=True): + motion_features = self.encoder(flow, corr) + inp = torch.cat([inp, motion_features], dim=1) + + net = self.gru(net, inp) + delta_flow = self.flow_head(net) + + # scale mask to balence gradients + mask = .25 * self.mask(net) + return net, mask, delta_flow + + + diff --git a/SD-CN-Animation/RAFT/utils/__init__.py b/SD-CN-Animation/RAFT/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SD-CN-Animation/RAFT/utils/__pycache__/__init__.cpython-310.pyc b/SD-CN-Animation/RAFT/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e041a25ae6a3ca7d8a148fff00abd13decab9e3 Binary files /dev/null and b/SD-CN-Animation/RAFT/utils/__pycache__/__init__.cpython-310.pyc differ diff --git a/SD-CN-Animation/RAFT/utils/__pycache__/utils.cpython-310.pyc b/SD-CN-Animation/RAFT/utils/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd67075e211197b0d9f36b0c3aeadaabc6de4c16 Binary files /dev/null and b/SD-CN-Animation/RAFT/utils/__pycache__/utils.cpython-310.pyc differ diff --git a/SD-CN-Animation/RAFT/utils/augmentor.py b/SD-CN-Animation/RAFT/utils/augmentor.py new file mode 100644 index 0000000000000000000000000000000000000000..e81c4f2b5c16c31c0ae236d744f299d430228a04 --- /dev/null +++ b/SD-CN-Animation/RAFT/utils/augmentor.py @@ -0,0 +1,246 @@ +import numpy as np +import random +import math +from PIL import Image + +import cv2 +cv2.setNumThreads(0) +cv2.ocl.setUseOpenCL(False) + +import torch +from torchvision.transforms import ColorJitter +import torch.nn.functional as F + + +class FlowAugmentor: + def __init__(self, crop_size, min_scale=-0.2, max_scale=0.5, do_flip=True): + + # spatial augmentation params + self.crop_size = crop_size + self.min_scale = min_scale + self.max_scale = max_scale + self.spatial_aug_prob = 0.8 + self.stretch_prob = 0.8 + self.max_stretch = 0.2 + + # flip augmentation params + self.do_flip = do_flip + self.h_flip_prob = 0.5 + self.v_flip_prob = 0.1 + + # photometric augmentation params + self.photo_aug = ColorJitter(brightness=0.4, contrast=0.4, saturation=0.4, hue=0.5/3.14) + self.asymmetric_color_aug_prob = 0.2 + self.eraser_aug_prob = 0.5 + + def color_transform(self, img1, img2): + """ Photometric augmentation """ + + # asymmetric + if np.random.rand() < self.asymmetric_color_aug_prob: + img1 = np.array(self.photo_aug(Image.fromarray(img1)), dtype=np.uint8) + img2 = np.array(self.photo_aug(Image.fromarray(img2)), dtype=np.uint8) + + # symmetric + else: + image_stack = np.concatenate([img1, img2], axis=0) + image_stack = np.array(self.photo_aug(Image.fromarray(image_stack)), dtype=np.uint8) + img1, img2 = np.split(image_stack, 2, axis=0) + + return img1, img2 + + def eraser_transform(self, img1, img2, bounds=[50, 100]): + """ Occlusion augmentation """ + + ht, wd = img1.shape[:2] + if np.random.rand() < self.eraser_aug_prob: + mean_color = np.mean(img2.reshape(-1, 3), axis=0) + for _ in range(np.random.randint(1, 3)): + x0 = np.random.randint(0, wd) + y0 = np.random.randint(0, ht) + dx = np.random.randint(bounds[0], bounds[1]) + dy = np.random.randint(bounds[0], bounds[1]) + img2[y0:y0+dy, x0:x0+dx, :] = mean_color + + return img1, img2 + + def spatial_transform(self, img1, img2, flow): + # randomly sample scale + ht, wd = img1.shape[:2] + min_scale = np.maximum( + (self.crop_size[0] + 8) / float(ht), + (self.crop_size[1] + 8) / float(wd)) + + scale = 2 ** np.random.uniform(self.min_scale, self.max_scale) + scale_x = scale + scale_y = scale + if np.random.rand() < self.stretch_prob: + scale_x *= 2 ** np.random.uniform(-self.max_stretch, self.max_stretch) + scale_y *= 2 ** np.random.uniform(-self.max_stretch, self.max_stretch) + + scale_x = np.clip(scale_x, min_scale, None) + scale_y = np.clip(scale_y, min_scale, None) + + if np.random.rand() < self.spatial_aug_prob: + # rescale the images + img1 = cv2.resize(img1, None, fx=scale_x, fy=scale_y, interpolation=cv2.INTER_LINEAR) + img2 = cv2.resize(img2, None, fx=scale_x, fy=scale_y, interpolation=cv2.INTER_LINEAR) + flow = cv2.resize(flow, None, fx=scale_x, fy=scale_y, interpolation=cv2.INTER_LINEAR) + flow = flow * [scale_x, scale_y] + + if self.do_flip: + if np.random.rand() < self.h_flip_prob: # h-flip + img1 = img1[:, ::-1] + img2 = img2[:, ::-1] + flow = flow[:, ::-1] * [-1.0, 1.0] + + if np.random.rand() < self.v_flip_prob: # v-flip + img1 = img1[::-1, :] + img2 = img2[::-1, :] + flow = flow[::-1, :] * [1.0, -1.0] + + y0 = np.random.randint(0, img1.shape[0] - self.crop_size[0]) + x0 = np.random.randint(0, img1.shape[1] - self.crop_size[1]) + + img1 = img1[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]] + img2 = img2[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]] + flow = flow[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]] + + return img1, img2, flow + + def __call__(self, img1, img2, flow): + img1, img2 = self.color_transform(img1, img2) + img1, img2 = self.eraser_transform(img1, img2) + img1, img2, flow = self.spatial_transform(img1, img2, flow) + + img1 = np.ascontiguousarray(img1) + img2 = np.ascontiguousarray(img2) + flow = np.ascontiguousarray(flow) + + return img1, img2, flow + +class SparseFlowAugmentor: + def __init__(self, crop_size, min_scale=-0.2, max_scale=0.5, do_flip=False): + # spatial augmentation params + self.crop_size = crop_size + self.min_scale = min_scale + self.max_scale = max_scale + self.spatial_aug_prob = 0.8 + self.stretch_prob = 0.8 + self.max_stretch = 0.2 + + # flip augmentation params + self.do_flip = do_flip + self.h_flip_prob = 0.5 + self.v_flip_prob = 0.1 + + # photometric augmentation params + self.photo_aug = ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.3/3.14) + self.asymmetric_color_aug_prob = 0.2 + self.eraser_aug_prob = 0.5 + + def color_transform(self, img1, img2): + image_stack = np.concatenate([img1, img2], axis=0) + image_stack = np.array(self.photo_aug(Image.fromarray(image_stack)), dtype=np.uint8) + img1, img2 = np.split(image_stack, 2, axis=0) + return img1, img2 + + def eraser_transform(self, img1, img2): + ht, wd = img1.shape[:2] + if np.random.rand() < self.eraser_aug_prob: + mean_color = np.mean(img2.reshape(-1, 3), axis=0) + for _ in range(np.random.randint(1, 3)): + x0 = np.random.randint(0, wd) + y0 = np.random.randint(0, ht) + dx = np.random.randint(50, 100) + dy = np.random.randint(50, 100) + img2[y0:y0+dy, x0:x0+dx, :] = mean_color + + return img1, img2 + + def resize_sparse_flow_map(self, flow, valid, fx=1.0, fy=1.0): + ht, wd = flow.shape[:2] + coords = np.meshgrid(np.arange(wd), np.arange(ht)) + coords = np.stack(coords, axis=-1) + + coords = coords.reshape(-1, 2).astype(np.float32) + flow = flow.reshape(-1, 2).astype(np.float32) + valid = valid.reshape(-1).astype(np.float32) + + coords0 = coords[valid>=1] + flow0 = flow[valid>=1] + + ht1 = int(round(ht * fy)) + wd1 = int(round(wd * fx)) + + coords1 = coords0 * [fx, fy] + flow1 = flow0 * [fx, fy] + + xx = np.round(coords1[:,0]).astype(np.int32) + yy = np.round(coords1[:,1]).astype(np.int32) + + v = (xx > 0) & (xx < wd1) & (yy > 0) & (yy < ht1) + xx = xx[v] + yy = yy[v] + flow1 = flow1[v] + + flow_img = np.zeros([ht1, wd1, 2], dtype=np.float32) + valid_img = np.zeros([ht1, wd1], dtype=np.int32) + + flow_img[yy, xx] = flow1 + valid_img[yy, xx] = 1 + + return flow_img, valid_img + + def spatial_transform(self, img1, img2, flow, valid): + # randomly sample scale + + ht, wd = img1.shape[:2] + min_scale = np.maximum( + (self.crop_size[0] + 1) / float(ht), + (self.crop_size[1] + 1) / float(wd)) + + scale = 2 ** np.random.uniform(self.min_scale, self.max_scale) + scale_x = np.clip(scale, min_scale, None) + scale_y = np.clip(scale, min_scale, None) + + if np.random.rand() < self.spatial_aug_prob: + # rescale the images + img1 = cv2.resize(img1, None, fx=scale_x, fy=scale_y, interpolation=cv2.INTER_LINEAR) + img2 = cv2.resize(img2, None, fx=scale_x, fy=scale_y, interpolation=cv2.INTER_LINEAR) + flow, valid = self.resize_sparse_flow_map(flow, valid, fx=scale_x, fy=scale_y) + + if self.do_flip: + if np.random.rand() < 0.5: # h-flip + img1 = img1[:, ::-1] + img2 = img2[:, ::-1] + flow = flow[:, ::-1] * [-1.0, 1.0] + valid = valid[:, ::-1] + + margin_y = 20 + margin_x = 50 + + y0 = np.random.randint(0, img1.shape[0] - self.crop_size[0] + margin_y) + x0 = np.random.randint(-margin_x, img1.shape[1] - self.crop_size[1] + margin_x) + + y0 = np.clip(y0, 0, img1.shape[0] - self.crop_size[0]) + x0 = np.clip(x0, 0, img1.shape[1] - self.crop_size[1]) + + img1 = img1[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]] + img2 = img2[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]] + flow = flow[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]] + valid = valid[y0:y0+self.crop_size[0], x0:x0+self.crop_size[1]] + return img1, img2, flow, valid + + + def __call__(self, img1, img2, flow, valid): + img1, img2 = self.color_transform(img1, img2) + img1, img2 = self.eraser_transform(img1, img2) + img1, img2, flow, valid = self.spatial_transform(img1, img2, flow, valid) + + img1 = np.ascontiguousarray(img1) + img2 = np.ascontiguousarray(img2) + flow = np.ascontiguousarray(flow) + valid = np.ascontiguousarray(valid) + + return img1, img2, flow, valid diff --git a/SD-CN-Animation/RAFT/utils/flow_viz.py b/SD-CN-Animation/RAFT/utils/flow_viz.py new file mode 100644 index 0000000000000000000000000000000000000000..dcee65e89b91b07ee0496aeb4c7e7436abf99641 --- /dev/null +++ b/SD-CN-Animation/RAFT/utils/flow_viz.py @@ -0,0 +1,132 @@ +# Flow visualization code used from https://github.com/tomrunia/OpticalFlow_Visualization + + +# MIT License +# +# Copyright (c) 2018 Tom Runia +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to conditions. +# +# Author: Tom Runia +# Date Created: 2018-08-03 + +import numpy as np + +def make_colorwheel(): + """ + Generates a color wheel for optical flow visualization as presented in: + Baker et al. "A Database and Evaluation Methodology for Optical Flow" (ICCV, 2007) + URL: http://vision.middlebury.edu/flow/flowEval-iccv07.pdf + + Code follows the original C++ source code of Daniel Scharstein. + Code follows the the Matlab source code of Deqing Sun. + + Returns: + np.ndarray: Color wheel + """ + + RY = 15 + YG = 6 + GC = 4 + CB = 11 + BM = 13 + MR = 6 + + ncols = RY + YG + GC + CB + BM + MR + colorwheel = np.zeros((ncols, 3)) + col = 0 + + # RY + colorwheel[0:RY, 0] = 255 + colorwheel[0:RY, 1] = np.floor(255*np.arange(0,RY)/RY) + col = col+RY + # YG + colorwheel[col:col+YG, 0] = 255 - np.floor(255*np.arange(0,YG)/YG) + colorwheel[col:col+YG, 1] = 255 + col = col+YG + # GC + colorwheel[col:col+GC, 1] = 255 + colorwheel[col:col+GC, 2] = np.floor(255*np.arange(0,GC)/GC) + col = col+GC + # CB + colorwheel[col:col+CB, 1] = 255 - np.floor(255*np.arange(CB)/CB) + colorwheel[col:col+CB, 2] = 255 + col = col+CB + # BM + colorwheel[col:col+BM, 2] = 255 + colorwheel[col:col+BM, 0] = np.floor(255*np.arange(0,BM)/BM) + col = col+BM + # MR + colorwheel[col:col+MR, 2] = 255 - np.floor(255*np.arange(MR)/MR) + colorwheel[col:col+MR, 0] = 255 + return colorwheel + + +def flow_uv_to_colors(u, v, convert_to_bgr=False): + """ + Applies the flow color wheel to (possibly clipped) flow components u and v. + + According to the C++ source code of Daniel Scharstein + According to the Matlab source code of Deqing Sun + + Args: + u (np.ndarray): Input horizontal flow of shape [H,W] + v (np.ndarray): Input vertical flow of shape [H,W] + convert_to_bgr (bool, optional): Convert output image to BGR. Defaults to False. + + Returns: + np.ndarray: Flow visualization image of shape [H,W,3] + """ + flow_image = np.zeros((u.shape[0], u.shape[1], 3), np.uint8) + colorwheel = make_colorwheel() # shape [55x3] + ncols = colorwheel.shape[0] + rad = np.sqrt(np.square(u) + np.square(v)) + a = np.arctan2(-v, -u)/np.pi + fk = (a+1) / 2*(ncols-1) + k0 = np.floor(fk).astype(np.int32) + k1 = k0 + 1 + k1[k1 == ncols] = 0 + f = fk - k0 + for i in range(colorwheel.shape[1]): + tmp = colorwheel[:,i] + col0 = tmp[k0] / 255.0 + col1 = tmp[k1] / 255.0 + col = (1-f)*col0 + f*col1 + idx = (rad <= 1) + col[idx] = 1 - rad[idx] * (1-col[idx]) + col[~idx] = col[~idx] * 0.75 # out of range + # Note the 2-i => BGR instead of RGB + ch_idx = 2-i if convert_to_bgr else i + flow_image[:,:,ch_idx] = np.floor(255 * col) + return flow_image + + +def flow_to_image(flow_uv, clip_flow=None, convert_to_bgr=False): + """ + Expects a two dimensional flow image of shape. + + Args: + flow_uv (np.ndarray): Flow UV image of shape [H,W,2] + clip_flow (float, optional): Clip maximum of flow values. Defaults to None. + convert_to_bgr (bool, optional): Convert output image to BGR. Defaults to False. + + Returns: + np.ndarray: Flow visualization image of shape [H,W,3] + """ + assert flow_uv.ndim == 3, 'input flow must have three dimensions' + assert flow_uv.shape[2] == 2, 'input flow must have shape [H,W,2]' + if clip_flow is not None: + flow_uv = np.clip(flow_uv, 0, clip_flow) + u = flow_uv[:,:,0] + v = flow_uv[:,:,1] + rad = np.sqrt(np.square(u) + np.square(v)) + rad_max = np.max(rad) + epsilon = 1e-5 + u = u / (rad_max + epsilon) + v = v / (rad_max + epsilon) + return flow_uv_to_colors(u, v, convert_to_bgr) \ No newline at end of file diff --git a/SD-CN-Animation/RAFT/utils/frame_utils.py b/SD-CN-Animation/RAFT/utils/frame_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..6c491135efaffc25bd61ec3ecde99d236f5deb12 --- /dev/null +++ b/SD-CN-Animation/RAFT/utils/frame_utils.py @@ -0,0 +1,137 @@ +import numpy as np +from PIL import Image +from os.path import * +import re + +import cv2 +cv2.setNumThreads(0) +cv2.ocl.setUseOpenCL(False) + +TAG_CHAR = np.array([202021.25], np.float32) + +def readFlow(fn): + """ Read .flo file in Middlebury format""" + # Code adapted from: + # http://stackoverflow.com/questions/28013200/reading-middlebury-flow-files-with-python-bytes-array-numpy + + # WARNING: this will work on little-endian architectures (eg Intel x86) only! + # print 'fn = %s'%(fn) + with open(fn, 'rb') as f: + magic = np.fromfile(f, np.float32, count=1) + if 202021.25 != magic: + print('Magic number incorrect. Invalid .flo file') + return None + else: + w = np.fromfile(f, np.int32, count=1) + h = np.fromfile(f, np.int32, count=1) + # print 'Reading %d x %d flo file\n' % (w, h) + data = np.fromfile(f, np.float32, count=2*int(w)*int(h)) + # Reshape data into 3D array (columns, rows, bands) + # The reshape here is for visualization, the original code is (w,h,2) + return np.resize(data, (int(h), int(w), 2)) + +def readPFM(file): + file = open(file, 'rb') + + color = None + width = None + height = None + scale = None + endian = None + + header = file.readline().rstrip() + if header == b'PF': + color = True + elif header == b'Pf': + color = False + else: + raise Exception('Not a PFM file.') + + dim_match = re.match(rb'^(\d+)\s(\d+)\s$', file.readline()) + if dim_match: + width, height = map(int, dim_match.groups()) + else: + raise Exception('Malformed PFM header.') + + scale = float(file.readline().rstrip()) + if scale < 0: # little-endian + endian = '<' + scale = -scale + else: + endian = '>' # big-endian + + data = np.fromfile(file, endian + 'f') + shape = (height, width, 3) if color else (height, width) + + data = np.reshape(data, shape) + data = np.flipud(data) + return data + +def writeFlow(filename,uv,v=None): + """ Write optical flow to file. + + If v is None, uv is assumed to contain both u and v channels, + stacked in depth. + Original code by Deqing Sun, adapted from Daniel Scharstein. + """ + nBands = 2 + + if v is None: + assert(uv.ndim == 3) + assert(uv.shape[2] == 2) + u = uv[:,:,0] + v = uv[:,:,1] + else: + u = uv + + assert(u.shape == v.shape) + height,width = u.shape + f = open(filename,'wb') + # write the header + f.write(TAG_CHAR) + np.array(width).astype(np.int32).tofile(f) + np.array(height).astype(np.int32).tofile(f) + # arrange into matrix form + tmp = np.zeros((height, width*nBands)) + tmp[:,np.arange(width)*2] = u + tmp[:,np.arange(width)*2 + 1] = v + tmp.astype(np.float32).tofile(f) + f.close() + + +def readFlowKITTI(filename): + flow = cv2.imread(filename, cv2.IMREAD_ANYDEPTH|cv2.IMREAD_COLOR) + flow = flow[:,:,::-1].astype(np.float32) + flow, valid = flow[:, :, :2], flow[:, :, 2] + flow = (flow - 2**15) / 64.0 + return flow, valid + +def readDispKITTI(filename): + disp = cv2.imread(filename, cv2.IMREAD_ANYDEPTH) / 256.0 + valid = disp > 0.0 + flow = np.stack([-disp, np.zeros_like(disp)], -1) + return flow, valid + + +def writeFlowKITTI(filename, uv): + uv = 64.0 * uv + 2**15 + valid = np.ones([uv.shape[0], uv.shape[1], 1]) + uv = np.concatenate([uv, valid], axis=-1).astype(np.uint16) + cv2.imwrite(filename, uv[..., ::-1]) + + +def read_gen(file_name, pil=False): + ext = splitext(file_name)[-1] + if ext == '.png' or ext == '.jpeg' or ext == '.ppm' or ext == '.jpg': + return Image.open(file_name) + elif ext == '.bin' or ext == '.raw': + return np.load(file_name) + elif ext == '.flo': + return readFlow(file_name).astype(np.float32) + elif ext == '.pfm': + flow = readPFM(file_name).astype(np.float32) + if len(flow.shape) == 2: + return flow + else: + return flow[:, :, :-1] + return [] \ No newline at end of file diff --git a/SD-CN-Animation/RAFT/utils/utils.py b/SD-CN-Animation/RAFT/utils/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..741ccfe4d0d778c3199c586d368edc2882d4fff8 --- /dev/null +++ b/SD-CN-Animation/RAFT/utils/utils.py @@ -0,0 +1,82 @@ +import torch +import torch.nn.functional as F +import numpy as np +from scipy import interpolate + + +class InputPadder: + """ Pads images such that dimensions are divisible by 8 """ + def __init__(self, dims, mode='sintel'): + self.ht, self.wd = dims[-2:] + pad_ht = (((self.ht // 8) + 1) * 8 - self.ht) % 8 + pad_wd = (((self.wd // 8) + 1) * 8 - self.wd) % 8 + if mode == 'sintel': + self._pad = [pad_wd//2, pad_wd - pad_wd//2, pad_ht//2, pad_ht - pad_ht//2] + else: + self._pad = [pad_wd//2, pad_wd - pad_wd//2, 0, pad_ht] + + def pad(self, *inputs): + return [F.pad(x, self._pad, mode='replicate') for x in inputs] + + def unpad(self,x): + ht, wd = x.shape[-2:] + c = [self._pad[2], ht-self._pad[3], self._pad[0], wd-self._pad[1]] + return x[..., c[0]:c[1], c[2]:c[3]] + +def forward_interpolate(flow): + flow = flow.detach().cpu().numpy() + dx, dy = flow[0], flow[1] + + ht, wd = dx.shape + x0, y0 = np.meshgrid(np.arange(wd), np.arange(ht)) + + x1 = x0 + dx + y1 = y0 + dy + + x1 = x1.reshape(-1) + y1 = y1.reshape(-1) + dx = dx.reshape(-1) + dy = dy.reshape(-1) + + valid = (x1 > 0) & (x1 < wd) & (y1 > 0) & (y1 < ht) + x1 = x1[valid] + y1 = y1[valid] + dx = dx[valid] + dy = dy[valid] + + flow_x = interpolate.griddata( + (x1, y1), dx, (x0, y0), method='nearest', fill_value=0) + + flow_y = interpolate.griddata( + (x1, y1), dy, (x0, y0), method='nearest', fill_value=0) + + flow = np.stack([flow_x, flow_y], axis=0) + return torch.from_numpy(flow).float() + + +def bilinear_sampler(img, coords, mode='bilinear', mask=False): + """ Wrapper for grid_sample, uses pixel coordinates """ + H, W = img.shape[-2:] + xgrid, ygrid = coords.split([1,1], dim=-1) + xgrid = 2*xgrid/(W-1) - 1 + ygrid = 2*ygrid/(H-1) - 1 + + grid = torch.cat([xgrid, ygrid], dim=-1) + img = F.grid_sample(img, grid, align_corners=True) + + if mask: + mask = (xgrid > -1) & (ygrid > -1) & (xgrid < 1) & (ygrid < 1) + return img, mask.float() + + return img + + +def coords_grid(batch, ht, wd, device): + coords = torch.meshgrid(torch.arange(ht, device=device), torch.arange(wd, device=device)) + coords = torch.stack(coords[::-1], dim=0).float() + return coords[None].repeat(batch, 1, 1, 1) + + +def upflow8(flow, mode='bilinear'): + new_size = (8 * flow.shape[2], 8 * flow.shape[3]) + return 8 * F.interpolate(flow, size=new_size, mode=mode, align_corners=True) diff --git a/SD-CN-Animation/examples/bonefire_1.mp4 b/SD-CN-Animation/examples/bonefire_1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ec7724a2f240195373cbca4c391d93ea4a35dc2c Binary files /dev/null and b/SD-CN-Animation/examples/bonefire_1.mp4 differ diff --git a/SD-CN-Animation/examples/bonfire_1.gif b/SD-CN-Animation/examples/bonfire_1.gif new file mode 100644 index 0000000000000000000000000000000000000000..d7f6a5eb12c915a646c59d77fa28744c3d32beef Binary files /dev/null and b/SD-CN-Animation/examples/bonfire_1.gif differ diff --git a/SD-CN-Animation/examples/cn_settings.png b/SD-CN-Animation/examples/cn_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..84033431301ce691f089e4dd3af0181fb7648803 Binary files /dev/null and b/SD-CN-Animation/examples/cn_settings.png differ diff --git a/SD-CN-Animation/examples/diamond_4.gif b/SD-CN-Animation/examples/diamond_4.gif new file mode 100644 index 0000000000000000000000000000000000000000..f7a63c33a39081ae62c07ef69b895755fcfbff33 Binary files /dev/null and b/SD-CN-Animation/examples/diamond_4.gif differ diff --git a/SD-CN-Animation/examples/diamond_4.mp4 b/SD-CN-Animation/examples/diamond_4.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..39c8f436c6b736f15812f4a58a5813adaae764e5 Binary files /dev/null and b/SD-CN-Animation/examples/diamond_4.mp4 differ diff --git a/SD-CN-Animation/examples/flower_1.gif b/SD-CN-Animation/examples/flower_1.gif new file mode 100644 index 0000000000000000000000000000000000000000..87e022a592859d17d482c828c71d630b6214a298 --- /dev/null +++ b/SD-CN-Animation/examples/flower_1.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4fa97e65ea048e27472fa6b7d151ac66074c1ac3e5c5b4cfa333321d97b0bb9 +size 1678294 diff --git a/SD-CN-Animation/examples/flower_1.mp4 b/SD-CN-Animation/examples/flower_1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d8c85d1f7fae3991f073f672d4a92dc2fe054ab9 --- /dev/null +++ b/SD-CN-Animation/examples/flower_1.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8db0719d9f215b775ae1b5dae912a425bc010f0586b41894c14bb8ad042711e +size 1259280 diff --git a/SD-CN-Animation/examples/flower_11.mp4 b/SD-CN-Animation/examples/flower_11.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..19166e31701cb592c8297fd549846d2e9601327d --- /dev/null +++ b/SD-CN-Animation/examples/flower_11.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7499401998e41c65471963d6cbd70568908dd83a8c957a43940df99be7c52026 +size 1328049 diff --git a/SD-CN-Animation/examples/girl_org.gif b/SD-CN-Animation/examples/girl_org.gif new file mode 100644 index 0000000000000000000000000000000000000000..1dd6342b600e3b54c95135a9cfba12b4f36e46cf --- /dev/null +++ b/SD-CN-Animation/examples/girl_org.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:059307d932d818247672b8de1c8550a67d891ad9c2c32494e30b424abe643480 +size 3146415 diff --git a/SD-CN-Animation/examples/girl_to_jc.gif b/SD-CN-Animation/examples/girl_to_jc.gif new file mode 100644 index 0000000000000000000000000000000000000000..8acb72fb7e92a03802f79f5086a51eb3145531eb --- /dev/null +++ b/SD-CN-Animation/examples/girl_to_jc.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:604f24c3072ac1e17f87c0664894e93059e6741d9f17d0f03f33214549edc967 +size 3392451 diff --git a/SD-CN-Animation/examples/girl_to_jc.mp4 b/SD-CN-Animation/examples/girl_to_jc.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..a81e67eef2679dd4c9ba8b806145edf8f5868a5a --- /dev/null +++ b/SD-CN-Animation/examples/girl_to_jc.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d09ded8b44f7e30d55d5d6245d9ec7fa3b95e970a8c29d2c544b6c288341e39 +size 5274033 diff --git a/SD-CN-Animation/examples/girl_to_wc.gif b/SD-CN-Animation/examples/girl_to_wc.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc48a81b1950c1f4a1bf49a634bf4924913a8d5a --- /dev/null +++ b/SD-CN-Animation/examples/girl_to_wc.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d978c31d2d58f408fa40b186f080fbcdff89fb6e1d8a7cf2a0c81276735bd0c +size 3317758 diff --git a/SD-CN-Animation/examples/girl_to_wc.mp4 b/SD-CN-Animation/examples/girl_to_wc.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..01ae572a905dc16c59b05f848bd459d9c1df4eae --- /dev/null +++ b/SD-CN-Animation/examples/girl_to_wc.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd730de667b8e7ea5af2dddcf129095694349f126e06291c9b1c2bb7d49843a8 +size 5630710 diff --git a/SD-CN-Animation/examples/gold_1.gif b/SD-CN-Animation/examples/gold_1.gif new file mode 100644 index 0000000000000000000000000000000000000000..0588547723c367325513ff67168a92a560f90ee3 --- /dev/null +++ b/SD-CN-Animation/examples/gold_1.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20d4676372b63cef565f614676660b37226c5fbf7825ba2add15a6262eff1bed +size 1312951 diff --git a/SD-CN-Animation/examples/gold_1.mp4 b/SD-CN-Animation/examples/gold_1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c79a51130cd2850a338262662b5817d3118c2758 Binary files /dev/null and b/SD-CN-Animation/examples/gold_1.mp4 differ diff --git a/SD-CN-Animation/examples/macaroni_1.gif b/SD-CN-Animation/examples/macaroni_1.gif new file mode 100644 index 0000000000000000000000000000000000000000..d3eeb34463d1411ae02e6e4a489c4e10e1daac41 --- /dev/null +++ b/SD-CN-Animation/examples/macaroni_1.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7e43cd36aa70deac9ebc3fd26615183f9b13ffafd195bb0db2c0a4eb834dba3 +size 1164353 diff --git a/SD-CN-Animation/examples/macaroni_1.mp4 b/SD-CN-Animation/examples/macaroni_1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d722f41c8cef3603c4da6c8204cf5f362fc4cb7f Binary files /dev/null and b/SD-CN-Animation/examples/macaroni_1.mp4 differ diff --git a/SD-CN-Animation/examples/tree_2.gif b/SD-CN-Animation/examples/tree_2.gif new file mode 100644 index 0000000000000000000000000000000000000000..dd48a2254013cdb6ddb96ebec7f335137b7d6932 --- /dev/null +++ b/SD-CN-Animation/examples/tree_2.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6949d2fe4cd7902d7f339ec04b2d2ea520545128f28476b78f72df3b75f0924d +size 1595577 diff --git a/SD-CN-Animation/examples/tree_2.mp4 b/SD-CN-Animation/examples/tree_2.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..b775d113dc9c6909740fd759a4dcc86ac48185c0 --- /dev/null +++ b/SD-CN-Animation/examples/tree_2.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2bdab6694727e6dab21e49311efbc21296d445bf11473de6867848b305d3775 +size 1333426 diff --git a/SD-CN-Animation/examples/ui_preview.png b/SD-CN-Animation/examples/ui_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..efc548061ee5deff66eb423ef66ef4ad59ba9e03 Binary files /dev/null and b/SD-CN-Animation/examples/ui_preview.png differ diff --git a/SD-CN-Animation/install.py b/SD-CN-Animation/install.py new file mode 100644 index 0000000000000000000000000000000000000000..acb618203219142beac777148f187ddeeaaa8032 --- /dev/null +++ b/SD-CN-Animation/install.py @@ -0,0 +1,20 @@ +import launch +import os +import pkg_resources + +req_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "requirements.txt") + +with open(req_file) as file: + for package in file: + try: + package = package.strip() + if '==' in package: + package_name, package_version = package.split('==') + installed_version = pkg_resources.get_distribution(package_name).version + if installed_version != package_version: + launch.run_pip(f"install {package}", f"SD-CN-Animation requirement: changing {package_name} version from {installed_version} to {package_version}") + elif not launch.is_installed(package): + launch.run_pip(f"install {package}", f"SD-CN-Animation requirement: {package}") + except Exception as e: + print(e) + print(f'Warning: Failed to install {package}.') \ No newline at end of file diff --git a/SD-CN-Animation/old_scripts/compute_flow.py b/SD-CN-Animation/old_scripts/compute_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..862e17498839fce9acc0f140afaac99db819cb7c --- /dev/null +++ b/SD-CN-Animation/old_scripts/compute_flow.py @@ -0,0 +1,75 @@ +import cv2 +import base64 +import numpy as np +from tqdm import tqdm +import os + +from flow_utils import RAFT_estimate_flow +import h5py + +import argparse + +def main(args): + W, H = args.width, args.height + # Open the input video file + input_video = cv2.VideoCapture(args.input_video) + + # Get useful info from the source video + fps = int(input_video.get(cv2.CAP_PROP_FPS)) + total_frames = int(input_video.get(cv2.CAP_PROP_FRAME_COUNT)) + + prev_frame = None + + # create an empty HDF5 file + with h5py.File(args.output_file, 'w') as f: pass + + # open the file for writing a flow maps into it + with h5py.File(args.output_file, 'a') as f: + flow_maps = f.create_dataset('flow_maps', shape=(0, 2, H, W, 2), maxshape=(None, 2, H, W, 2), dtype=np.float16) + + for ind in tqdm(range(total_frames)): + # Read the next frame from the input video + if not input_video.isOpened(): break + ret, cur_frame = input_video.read() + if not ret: break + + cur_frame = cv2.resize(cur_frame, (W, H)) + + if prev_frame is not None: + next_flow, prev_flow, occlusion_mask, frame1_bg_removed, frame2_bg_removed = RAFT_estimate_flow(prev_frame, cur_frame, subtract_background=args.remove_background) + + # write data into a file + flow_maps.resize(ind, axis=0) + flow_maps[ind-1, 0] = next_flow + flow_maps[ind-1, 1] = prev_flow + + occlusion_mask = np.clip(occlusion_mask * 0.2 * 255, 0, 255).astype(np.uint8) + + if args.visualize: + # show the last written frame - useful to catch any issue with the process + if args.remove_background: + img_show = cv2.hconcat([cur_frame, frame2_bg_removed, occlusion_mask]) + else: + img_show = cv2.hconcat([cur_frame, occlusion_mask]) + cv2.imshow('Out img', img_show) + if cv2.waitKey(1) & 0xFF == ord('q'): exit() # press Q to close the script while processing + + prev_frame = cur_frame.copy() + + # Release the input and output video files + input_video.release() + + # Close all windows + if args.visualize: cv2.destroyAllWindows() + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-i', '--input_video', help="Path to input video file", required=True) + parser.add_argument('-o', '--output_file', help="Path to output flow file. Stored in *.h5 format", required=True) + parser.add_argument('-W', '--width', help='Width of the generated flow maps', default=1024, type=int) + parser.add_argument('-H', '--height', help='Height of the generated flow maps', default=576, type=int) + parser.add_argument('-v', '--visualize', action='store_true', help='Show proceed images and occlusion maps') + parser.add_argument('-rb', '--remove_background', action='store_true', help='Remove background of the image') + args = parser.parse_args() + + main(args) diff --git a/SD-CN-Animation/old_scripts/flow_utils.py b/SD-CN-Animation/old_scripts/flow_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..eef2457ce0997f79d46cd97757193535c2ca3785 --- /dev/null +++ b/SD-CN-Animation/old_scripts/flow_utils.py @@ -0,0 +1,139 @@ +import numpy as np +import cv2 + +# RAFT dependencies +import sys +sys.path.append('RAFT/core') + +from collections import namedtuple +import torch +import argparse +from raft import RAFT +from utils.utils import InputPadder + +RAFT_model = None +fgbg = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=True) + +def background_subtractor(frame, fgbg): + fgmask = fgbg.apply(frame) + return cv2.bitwise_and(frame, frame, mask=fgmask) + +def RAFT_estimate_flow(frame1, frame2, device='cuda', subtract_background=True): + global RAFT_model + if RAFT_model is None: + args = argparse.Namespace(**{ + 'model': 'RAFT/models/raft-things.pth', + 'mixed_precision': True, + 'small': False, + 'alternate_corr': False, + 'path': "" + }) + + RAFT_model = torch.nn.DataParallel(RAFT(args)) + RAFT_model.load_state_dict(torch.load(args.model)) + + RAFT_model = RAFT_model.module + RAFT_model.to(device) + RAFT_model.eval() + + if subtract_background: + frame1 = background_subtractor(frame1, fgbg) + frame2 = background_subtractor(frame2, fgbg) + + with torch.no_grad(): + frame1_torch = torch.from_numpy(frame1).permute(2, 0, 1).float()[None].to(device) + frame2_torch = torch.from_numpy(frame2).permute(2, 0, 1).float()[None].to(device) + + padder = InputPadder(frame1_torch.shape) + image1, image2 = padder.pad(frame1_torch, frame2_torch) + + # estimate optical flow + _, next_flow = RAFT_model(image1, image2, iters=20, test_mode=True) + _, prev_flow = RAFT_model(image2, image1, iters=20, test_mode=True) + + next_flow = next_flow[0].permute(1, 2, 0).cpu().numpy() + prev_flow = prev_flow[0].permute(1, 2, 0).cpu().numpy() + + fb_flow = next_flow + prev_flow + fb_norm = np.linalg.norm(fb_flow, axis=2) + + occlusion_mask = fb_norm[..., None].repeat(3, axis=-1) + + return next_flow, prev_flow, occlusion_mask, frame1, frame2 + +# ... rest of the file ... + + +def compute_diff_map(next_flow, prev_flow, prev_frame, cur_frame, prev_frame_styled): + h, w = cur_frame.shape[:2] + + #print(np.amin(next_flow), np.amax(next_flow)) + #exit() + + + fl_w, fl_h = next_flow.shape[:2] + + # normalize flow + next_flow = next_flow / np.array([fl_h,fl_w]) + prev_flow = prev_flow / np.array([fl_h,fl_w]) + + # remove low value noise (@alexfredo suggestion) + next_flow[np.abs(next_flow) < 0.05] = 0 + prev_flow[np.abs(prev_flow) < 0.05] = 0 + + # resize flow + next_flow = cv2.resize(next_flow, (w, h)) + next_flow = (next_flow * np.array([h,w])).astype(np.float32) + prev_flow = cv2.resize(prev_flow, (w, h)) + prev_flow = (prev_flow * np.array([h,w])).astype(np.float32) + + # Generate sampling grids + grid_y, grid_x = torch.meshgrid(torch.arange(0, h), torch.arange(0, w)) + flow_grid = torch.stack((grid_x, grid_y), dim=0).float() + flow_grid += torch.from_numpy(prev_flow).permute(2, 0, 1) + flow_grid = flow_grid.unsqueeze(0) + flow_grid[:, 0, :, :] = 2 * flow_grid[:, 0, :, :] / (w - 1) - 1 + flow_grid[:, 1, :, :] = 2 * flow_grid[:, 1, :, :] / (h - 1) - 1 + flow_grid = flow_grid.permute(0, 2, 3, 1) + + + prev_frame_torch = torch.from_numpy(prev_frame).float().unsqueeze(0).permute(0, 3, 1, 2) #N, C, H, W + prev_frame_styled_torch = torch.from_numpy(prev_frame_styled).float().unsqueeze(0).permute(0, 3, 1, 2) #N, C, H, W + + warped_frame = torch.nn.functional.grid_sample(prev_frame_torch, flow_grid, padding_mode="reflection").permute(0, 2, 3, 1)[0].numpy() + warped_frame_styled = torch.nn.functional.grid_sample(prev_frame_styled_torch, flow_grid, padding_mode="reflection").permute(0, 2, 3, 1)[0].numpy() + + #warped_frame = cv2.remap(prev_frame, flow_map, None, cv2.INTER_NEAREST, borderMode = cv2.BORDER_REFLECT) + #warped_frame_styled = cv2.remap(prev_frame_styled, flow_map, None, cv2.INTER_NEAREST, borderMode = cv2.BORDER_REFLECT) + + # compute occlusion mask + fb_flow = next_flow + prev_flow + fb_norm = np.linalg.norm(fb_flow, axis=2) + + occlusion_mask = fb_norm[..., None] + + diff_mask_org = np.abs(warped_frame.astype(np.float32) - cur_frame.astype(np.float32)) / 255 + diff_mask_org = diff_mask_org.max(axis = -1, keepdims=True) + + diff_mask_stl = np.abs(warped_frame_styled.astype(np.float32) - cur_frame.astype(np.float32)) / 255 + diff_mask_stl = diff_mask_stl.max(axis = -1, keepdims=True) + + alpha_mask = np.maximum(occlusion_mask * 0.3, diff_mask_org * 4, diff_mask_stl * 2) + alpha_mask = alpha_mask.repeat(3, axis = -1) + + #alpha_mask_blured = cv2.dilate(alpha_mask, np.ones((5, 5), np.float32)) + alpha_mask = cv2.GaussianBlur(alpha_mask, (51,51), 5, cv2.BORDER_REFLECT) + + alpha_mask = np.clip(alpha_mask, 0, 1) + + return alpha_mask, warped_frame_styled + +def frames_norm(occl): return occl / 127.5 - 1 + +def flow_norm(flow): return flow / 255 + +def occl_norm(occl): return occl / 127.5 - 1 + +def flow_renorm(flow): return flow * 255 + +def occl_renorm(occl): return (occl + 1) * 127.5 diff --git a/SD-CN-Animation/old_scripts/readme.md b/SD-CN-Animation/old_scripts/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..bbbff19b260eab4eb9377bded1cef0912d335ba1 --- /dev/null +++ b/SD-CN-Animation/old_scripts/readme.md @@ -0,0 +1,133 @@ +# SD-CN-Animation +This project allows you to automate video stylization task using StableDiffusion and ControlNet. It also allows you to generate completely new videos from text at any resolution and length in contrast to other current text2video methods using any Stable Diffusion model as a backbone, including custom ones. It uses '[RAFT](https://github.com/princeton-vl/RAFT)' optical flow estimation algorithm to keep the animation stable and create an inpainting mask that is used to generate the next frame. In text to video mode it relies on 'FloweR' method (work in progress) that predicts optical flow from the previous frames. + + +### Video to Video Examples: + + + + + + + + + + + + + +
Original video"Jessica Chastain""Watercolor painting"
+ +Examples presented are generated at 1024x576 resolution using the 'realisticVisionV13_v13' model as a base. They were cropt, downsized and compressed for better loading speed. You can see them in their original quality in the 'examples' folder. + +### Text to Video Examples: + + + + + + + + + + + + + + + + + + + + + + +
"close up of a flower""bonfire near the camp in the mountains at night""close up of a diamond laying on the table"
"close up of macaroni on the plate""close up of golden sphere""a tree standing in the winter forest"
+ +All examples you can see here are originally generated at 512x512 resolution using the 'sd-v1-5-inpainting' model as a base. They were downsized and compressed for better loading speed. You can see them in their original quality in the 'examples' folder. Actual prompts used were stated in the following format: "RAW photo, {subject}, 8k uhd, dslr, soft lighting, high quality, film grain, Fujifilm XT3", only the 'subject' part is described in the table above. + + + +## Dependencies +To install all the necessary dependencies, run this command: +``` +pip install opencv-python opencv-contrib-python numpy tqdm h5py scikit-image +``` +You have to set up the RAFT repository as it described here: https://github.com/princeton-vl/RAFT . Basically it just comes down to running "./download_models.sh" in RAFT folder to download the models. + + +## Running the scripts +This script works on top of [Automatic1111/web-ui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) interface via API. To run this script you have to set it up first. You should also have[sd-webui-controlnet](https://github.com/Mikubill/sd-webui-controlnet) extension installed. You need to have the control_hed-fp16 model installed. If you have web-ui with ControlNet working correctly, you have to also allow the API to work with controlNet. To do so, go to the web-ui settings -> ControlNet tab -> Set "Allow other script to control this extension" checkbox to active and set "Multi ControlNet: Max models amount (requires restart)" to more then 2 -> press "Apply settings". + + +### Video To Video +#### Step 1. +To process the video, first of all you would need to precompute optical flow data before running web-ui with this command: +``` +python3 compute_flow.py -i "path to your video" -o "path to output file with *.h5 format" -v -W width_of_the_flow_map -H height_of_the_flow_map +``` +The main reason to do this step separately is to save precious GPU memory that will be useful to generate better quality images. Choose W and H parameters as high as your GPU can handle with respect to the proportion of original video resolution. Do not worry if it is higher or less then the processing resolution, flow maps will be scaled accordingly at the processing stage. This will generate quite a large file that may take up to a several gigabytes on the drive even for minute long video. If you want to process a long video consider splitting it into several parts beforehand. + + +#### Step 2. +Run web-ui with '--api' flag. It is also better to use '--xformers' flag, as you would need to have the highest resolution possible and using xformers memory optimization will greatly help. +``` +bash webui.sh --xformers --api +``` + + +#### Step 3. +Go to the **vid2vid.py** file and change main parameters (INPUT_VIDEO, FLOW_MAPS, OUTPUT_VIDEO, PROMPT, N_PROMPT, W, H) to the ones you need for your project. FLOW_MAPS parameter should contain a path to the flow file that you generated at the first step. The script is pretty simple so you may change other parameters as well, although I would recommend to leave them as is for the first time. Finally run the script with the command: +``` +python3 vid2vid.py +``` + + +### Text To Video +This method is still in development and works on top of ‘Stable Diffusion’ and 'FloweR' - optical flow reconstruction method that is also in a yearly development stage. Do not expect much from it as it is more of a proof of a concept rather than a complete solution. + +#### Step 1. +Download 'FloweR_0.1.pth' model from here: [Google drive link](https://drive.google.com/file/d/1WhzoVIw6Kdg4EjfK9LaTLqFm5dF-IJ7F/view?usp=share_link) and place it in the 'FloweR' folder. + +#### Step 2. +Same as with vid2vid case, run web-ui with '--api' flag. It is also better to use '--xformers' flag, as you would need to have the highest resolution possible and using xformers memory optimization will greatly help. +``` +bash webui.sh --xformers --api +``` + +#### Step 3. +Go to the **txt2vid.py** file and change main parameters (OUTPUT_VIDEO, PROMPT, N_PROMPT, W, H) to the ones you need for your project. Again, the script is simple so you may change other parameters if you want to. Finally run the script with the command: +``` +python3 txt2vid.py +``` + +## Last version changes: v0.5 +* Fixed an issue with the wrong direction of an optical flow applied to an image. +* Added text to video mode within txt2vid.py script. Make sure to update new dependencies for this script to work! +* Added a threshold for an optical flow before processing the frame to remove white noise that might appear, as it was suggested by [@alexfredo](https://github.com/alexfredo). +* Background removal at flow computation stage implemented by [@CaptnSeraph](https://github.com/CaptnSeraph), it should reduce ghosting effect in most of the videos processed with vid2vid script. + + + + +## Licence +This repository can only be used for personal/research/non-commercial purposes. However, for commercial requests, please contact me directly at borsky.alexey@gmail.com + + + + diff --git a/SD-CN-Animation/old_scripts/txt2vid.py b/SD-CN-Animation/old_scripts/txt2vid.py new file mode 100644 index 0000000000000000000000000000000000000000..ee8f9bb43aa7afbc36f2edb4635890fdb280ac74 --- /dev/null +++ b/SD-CN-Animation/old_scripts/txt2vid.py @@ -0,0 +1,208 @@ +import requests +import cv2 +import base64 +import numpy as np +from tqdm import tqdm +import os + +import sys +sys.path.append('FloweR/') +sys.path.append('RAFT/core') + +import torch +from model import FloweR +from utils import flow_viz + +from flow_utils import * +import skimage +import datetime + + +OUTPUT_VIDEO = f'videos/result_{datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")}.mp4' + +PROMPT = "people looking at flying robots. Future. People looking to the sky. Stars in the background. Dramatic light, Cinematic light. Soft lighting, high quality, film grain." +N_PROMPT = "(deformed iris, deformed pupils, semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime:1.4), text, letters, logo, brand, close up, cropped, out of frame, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck" +w,h = 768, 512 # Width and height of the processed image. Note that actual image processed would be a W x H resolution. + +SAVE_FRAMES = True # saves individual frames into 'out' folder if set True. Again might be helpful with long animations + +PROCESSING_STRENGTH = 0.85 +FIX_STRENGTH = 0.35 + +CFG_SCALE = 5.5 + +APPLY_TEMPORALNET = False +APPLY_COLOR = False + +VISUALIZE = True +DEVICE = 'cuda' + +def to_b64(img): + img_cliped = np.clip(img, 0, 255).astype(np.uint8) + _, buffer = cv2.imencode('.png', img_cliped) + b64img = base64.b64encode(buffer).decode("utf-8") + return b64img + +class controlnetRequest(): + def __init__(self, b64_init_img = None, b64_prev_img = None, b64_color_img = None, ds = 0.35, w=w, h=h, mask = None, seed=-1, mode='img2img'): + self.url = f"http://localhost:7860/sdapi/v1/{mode}" + self.body = { + "init_images": [b64_init_img], + "mask": mask, + "mask_blur": 0, + "inpainting_fill": 1, + "inpainting_mask_invert": 0, + "prompt": PROMPT, + "negative_prompt": N_PROMPT, + "seed": seed, + "subseed": -1, + "subseed_strength": 0, + "batch_size": 1, + "n_iter": 1, + "steps": 15, + "cfg_scale": CFG_SCALE, + "denoising_strength": ds, + "width": w, + "height": h, + "restore_faces": False, + "eta": 0, + "sampler_index": "DPM++ 2S a", + "control_net_enabled": True, + "alwayson_scripts": { + "ControlNet":{"args": []} + }, + } + + if APPLY_TEMPORALNET: + self.body["alwayson_scripts"]["ControlNet"]["args"].append({ + "input_image": b64_prev_img, + "module": "none", + "model": "diff_control_sd15_temporalnet_fp16 [adc6bd97]", + "weight": 0.65, + "resize_mode": "Just Resize", + "lowvram": False, + "processor_res": 512, + "guidance_start": 0, + "guidance_end": 0.65, + "guessmode": False + }) + + if APPLY_COLOR: + self.body["alwayson_scripts"]["ControlNet"]["args"].append({ + "input_image": b64_prev_img, + "module": "color", + "model": "t2iadapter_color_sd14v1 [8522029d]", + "weight": 0.65, + "resize_mode": "Just Resize", + "lowvram": False, + "processor_res": 512, + "guidance_start": 0, + "guidance_end": 0.65, + "guessmode": False + }) + + + def sendRequest(self): + # Request to web-ui + data_js = requests.post(self.url, json=self.body).json() + + # Convert the byte array to a NumPy array + image_bytes = base64.b64decode(data_js["images"][0]) + np_array = np.frombuffer(image_bytes, dtype=np.uint8) + + # Convert the NumPy array to a cv2 image + out_image = cv2.imdecode(np_array, cv2.IMREAD_COLOR) + return out_image + + + +if VISUALIZE: cv2.namedWindow('Out img') + + +# Create an output video file with the same fps, width, and height as the input video +output_video = cv2.VideoWriter(OUTPUT_VIDEO, cv2.VideoWriter_fourcc(*'mp4v'), 15, (w, h)) + +prev_frame = None +prev_frame_styled = None + + +# Instantiate the model +model = FloweR(input_size = (h, w)) +model.load_state_dict(torch.load('FloweR/FloweR_0.1.1.pth')) +# Move the model to the device +model = model.to(DEVICE) + + +init_frame = controlnetRequest(mode='txt2img', ds=PROCESSING_STRENGTH, w=w, h=h).sendRequest() + +output_video.write(init_frame) +prev_frame = init_frame + +clip_frames = np.zeros((4, h, w, 3), dtype=np.uint8) + +color_shift = np.zeros((0, 3)) +color_scale = np.zeros((0, 3)) +for ind in tqdm(range(450)): + clip_frames = np.roll(clip_frames, -1, axis=0) + clip_frames[-1] = prev_frame + + clip_frames_torch = frames_norm(torch.from_numpy(clip_frames).to(DEVICE, dtype=torch.float32)) + + with torch.no_grad(): + pred_data = model(clip_frames_torch.unsqueeze(0))[0] + + pred_flow = flow_renorm(pred_data[...,:2]).cpu().numpy() + pred_occl = occl_renorm(pred_data[...,2:3]).cpu().numpy().repeat(3, axis = -1) + + pred_flow = pred_flow / (1 + np.linalg.norm(pred_flow, axis=-1, keepdims=True) * 0.05) + pred_flow = cv2.GaussianBlur(pred_flow, (31,31), 1, cv2.BORDER_REFLECT_101) + + + pred_occl = cv2.GaussianBlur(pred_occl, (21,21), 2, cv2.BORDER_REFLECT_101) + pred_occl = (np.abs(pred_occl / 255) ** 1.5) * 255 + pred_occl = np.clip(pred_occl * 25, 0, 255).astype(np.uint8) + + flow_map = pred_flow.copy() + flow_map[:,:,0] += np.arange(w) + flow_map[:,:,1] += np.arange(h)[:,np.newaxis] + + warped_frame = cv2.remap(prev_frame, flow_map, None, cv2.INTER_CUBIC, borderMode = cv2.BORDER_REFLECT_101) + + out_image = warped_frame.copy() + + out_image = controlnetRequest( + b64_init_img = to_b64(out_image), + b64_prev_img = to_b64(prev_frame), + b64_color_img = to_b64(warped_frame), + mask = to_b64(pred_occl), + ds=PROCESSING_STRENGTH, w=w, h=h).sendRequest() + + out_image = controlnetRequest( + b64_init_img = to_b64(out_image), + b64_prev_img = to_b64(prev_frame), + b64_color_img = to_b64(warped_frame), + mask = None, + ds=FIX_STRENGTH, w=w, h=h).sendRequest() + + # These step is necessary to reduce color drift of the image that some models may cause + out_image = skimage.exposure.match_histograms(out_image, init_frame, multichannel=True, channel_axis=-1) + + output_video.write(out_image) + if SAVE_FRAMES: + if not os.path.isdir('out'): os.makedirs('out') + cv2.imwrite(f'out/{ind+1:05d}.png', out_image) + + pred_flow_img = flow_viz.flow_to_image(pred_flow) + frames_img = cv2.hconcat(list(clip_frames)) + data_img = cv2.hconcat([pred_flow_img, pred_occl, warped_frame, out_image]) + + cv2.imshow('Out img', cv2.vconcat([frames_img, data_img])) + if cv2.waitKey(1) & 0xFF == ord('q'): exit() # press Q to close the script while processing + + prev_frame = out_image.copy() + +# Release the input and output video files +output_video.release() + +# Close all windows +if VISUALIZE: cv2.destroyAllWindows() \ No newline at end of file diff --git a/SD-CN-Animation/old_scripts/vid2vid.py b/SD-CN-Animation/old_scripts/vid2vid.py new file mode 100644 index 0000000000000000000000000000000000000000..8563889cc14703ddb0c9d8c42575a6a260a24cda --- /dev/null +++ b/SD-CN-Animation/old_scripts/vid2vid.py @@ -0,0 +1,237 @@ +import requests +import cv2 +import base64 +import numpy as np +from tqdm import tqdm +import os + +import h5py +from flow_utils import compute_diff_map + +import skimage +import datetime + +INPUT_VIDEO = "/media/alex/ded3efe6-5825-429d-ac89-7ded676a2b6d/media/Peter_Gabriel/pexels-monstera-5302599-4096x2160-30fps.mp4" +FLOW_MAPS = "/media/alex/ded3efe6-5825-429d-ac89-7ded676a2b6d/media/Peter_Gabriel/pexels-monstera-5302599-4096x2160-30fps.h5" +OUTPUT_VIDEO = f'videos/result_{datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")}.mp4' + +PROMPT = "Underwater shot Peter Gabriel with closed eyes in Peter Gabriel's music video. 80's music video. VHS style. Dramatic light, Cinematic light. RAW photo, 8k uhd, dslr, soft lighting, high quality, film grain." +N_PROMPT = "(deformed iris, deformed pupils, semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime:1.4), text, close up, cropped, out of frame, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck" +w,h = 1088, 576 # Width and height of the processed image. Note that actual image processed would be a W x H resolution. + +START_FROM_IND = 0 # index of a frame to start a processing from. Might be helpful with long animations where you need to restart the script multiple times +SAVE_FRAMES = True # saves individual frames into 'out' folder if set True. Again might be helpful with long animations + +PROCESSING_STRENGTH = 0.95 +BLUR_FIX_STRENGTH = 0.15 + +APPLY_HED = True +APPLY_CANNY = False +APPLY_DEPTH = False +GUESSMODE = False + +CFG_SCALE = 5.5 + +VISUALIZE = True + +def to_b64(img): + img_cliped = np.clip(img, 0, 255).astype(np.uint8) + _, buffer = cv2.imencode('.png', img_cliped) + b64img = base64.b64encode(buffer).decode("utf-8") + return b64img + +class controlnetRequest(): + def __init__(self, b64_cur_img, b64_hed_img, ds = 0.35, w=w, h=h, mask = None, seed=-1): + self.url = "http://localhost:7860/sdapi/v1/img2img" + self.body = { + "init_images": [b64_cur_img], + "mask": mask, + "mask_blur": 0, + "inpainting_fill": 1, + "inpainting_mask_invert": 0, + "prompt": PROMPT, + "negative_prompt": N_PROMPT, + "seed": seed, + "subseed": -1, + "subseed_strength": 0, + "batch_size": 1, + "n_iter": 1, + "steps": 15, + "cfg_scale": CFG_SCALE, + "denoising_strength": ds, + "width": w, + "height": h, + "restore_faces": False, + "eta": 0, + "sampler_index": "DPM++ 2S a", + "control_net_enabled": True, + "alwayson_scripts": { + "ControlNet":{"args": []} + }, + } + + if APPLY_HED: + self.body["alwayson_scripts"]["ControlNet"]["args"].append({ + "input_image": b64_hed_img, + "module": "hed", + "model": "control_hed-fp16 [13fee50b]", + "weight": 0.65, + "resize_mode": "Just Resize", + "lowvram": False, + "processor_res": 512, + "guidance_start": 0, + "guidance_end": 0.65, + "guessmode": GUESSMODE + }) + + if APPLY_CANNY: + self.body["alwayson_scripts"]["ControlNet"]["args"].append({ + "input_image": b64_hed_img, + "module": "canny", + "model": "control_canny-fp16 [e3fe7712]", + "weight": 0.85, + "resize_mode": "Just Resize", + "lowvram": False, + "threshold_a": 35, + "threshold_b": 35, + "processor_res": 512, + "guidance_start": 0, + "guidance_end": 0.85, + "guessmode": GUESSMODE + }) + + if APPLY_DEPTH: + self.body["alwayson_scripts"]["ControlNet"]["args"].append({ + "input_image": b64_hed_img, + "module": "depth", + "model": "control_depth-fp16 [400750f6]", + "weight": 0.85, + "resize_mode": "Just Resize", + "lowvram": False, + "processor_res": 512, + "guidance_start": 0, + "guidance_end": 0.85, + "guessmode": GUESSMODE + }) + + + def sendRequest(self): + # Request to web-ui + data_js = requests.post(self.url, json=self.body).json() + + # Convert the byte array to a NumPy array + image_bytes = base64.b64decode(data_js["images"][0]) + np_array = np.frombuffer(image_bytes, dtype=np.uint8) + + # Convert the NumPy array to a cv2 image + out_image = cv2.imdecode(np_array, cv2.IMREAD_COLOR) + return out_image + + + +if VISUALIZE: cv2.namedWindow('Out img') + +# Open the input video file +input_video = cv2.VideoCapture(INPUT_VIDEO) + +# Get useful info from the source video +fps = int(input_video.get(cv2.CAP_PROP_FPS)) +total_frames = int(input_video.get(cv2.CAP_PROP_FRAME_COUNT)) + +# Create an output video file with the same fps, width, and height as the input video +output_video = cv2.VideoWriter(OUTPUT_VIDEO, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h)) + +prev_frame = None +prev_frame_styled = None +#init_image = None + +# reading flow maps in a stream manner +with h5py.File(FLOW_MAPS, 'r') as f: + flow_maps = f['flow_maps'] + + for ind in tqdm(range(total_frames)): + # Read the next frame from the input video + if not input_video.isOpened(): break + ret, cur_frame = input_video.read() + if not ret: break + + if ind+1 < START_FROM_IND: continue + + is_keyframe = True + if prev_frame is not None: + # Compute absolute difference between current and previous frame + frames_diff = cv2.absdiff(cur_frame, prev_frame) + # Compute mean of absolute difference + mean_diff = cv2.mean(frames_diff)[0] + # Check if mean difference is above threshold + is_keyframe = mean_diff > 30 + + # Generate course version of a current frame with previous stylized frame as a reference image + if is_keyframe: + # Resize the frame to proper resolution + frame = cv2.resize(cur_frame, (w, h)) + + # Processing current frame with current frame as a mask without any inpainting + out_image = controlnetRequest(to_b64(frame), to_b64(frame), PROCESSING_STRENGTH, w, h, mask = None).sendRequest() + + alpha_img = out_image.copy() + out_image_ = out_image.copy() + warped_styled = out_image.copy() + #init_image = out_image.copy() + else: + # Resize the frame to proper resolution + frame = cv2.resize(cur_frame, (w, h)) + prev_frame = cv2.resize(prev_frame, (w, h)) + + # Processing current frame with current frame as a mask without any inpainting + out_image = controlnetRequest(to_b64(frame), to_b64(frame), PROCESSING_STRENGTH, w, h, mask = None).sendRequest() + + next_flow, prev_flow = flow_maps[ind-1].astype(np.float32) + alpha_mask, warped_styled = compute_diff_map(next_flow, prev_flow, prev_frame, frame, prev_frame_styled) + + # This clipping at lower side required to fix small trailing issues that for some reason left outside of the bright part of the mask, + # and at the higher part it making parts changed strongly to do it with less flickering. + alpha_mask = np.clip(alpha_mask + 0.05, 0.05, 0.95) + alpha_img = np.clip(alpha_mask * 255, 0, 255).astype(np.uint8) + + # normalizing the colors + out_image = skimage.exposure.match_histograms(out_image, frame, multichannel=False, channel_axis=-1) + + out_image = out_image.astype(float) * alpha_mask + warped_styled.astype(float) * (1 - alpha_mask) + + #out_image = skimage.exposure.match_histograms(out_image, prev_frame, multichannel=True, channel_axis=-1) + #out_image_ = (out_image * 0.65 + warped_styled * 0.35) + + + # Bluring issue fix via additional processing + out_image_fixed = controlnetRequest(to_b64(out_image), to_b64(frame), BLUR_FIX_STRENGTH, w, h, mask = None, seed=8888).sendRequest() + + + # Write the frame to the output video + frame_out = np.clip(out_image_fixed, 0, 255).astype(np.uint8) + output_video.write(frame_out) + + if VISUALIZE: + # show the last written frame - useful to catch any issue with the process + warped_styled = np.clip(warped_styled, 0, 255).astype(np.uint8) + + img_show_top = cv2.hconcat([frame, warped_styled]) + img_show_bot = cv2.hconcat([frame_out, alpha_img]) + cv2.imshow('Out img', cv2.vconcat([img_show_top, img_show_bot])) + cv2.setWindowTitle("Out img", str(ind+1)) + if cv2.waitKey(1) & 0xFF == ord('q'): exit() # press Q to close the script while processing + + if SAVE_FRAMES: + if not os.path.isdir('out'): os.makedirs('out') + cv2.imwrite(f'out/{ind+1:05d}.png', frame_out) + + prev_frame = cur_frame.copy() + prev_frame_styled = out_image.copy() + + +# Release the input and output video files +input_video.release() +output_video.release() + +# Close all windows +if VISUALIZE: cv2.destroyAllWindows() diff --git a/SD-CN-Animation/readme.md b/SD-CN-Animation/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..999412eb209b96e81b3985f26d6ce5e34008890b --- /dev/null +++ b/SD-CN-Animation/readme.md @@ -0,0 +1,89 @@ +# SD-CN-Animation +This project allows you to automate video stylization task using StableDiffusion and ControlNet. It also allows you to generate completely new videos from text at any resolution and length in contrast to other current text2video methods using any Stable Diffusion model as a backbone, including custom ones. It uses '[RAFT](https://github.com/princeton-vl/RAFT)' optical flow estimation algorithm to keep the animation stable and create an occlusion mask that is used to generate the next frame. In text to video mode it relies on 'FloweR' method (work in progress) that predicts optical flow from the previous frames. + +![sd-cn-animation ui preview](examples/ui_preview.png) +sd-cn-animation ui preview + +**In vid2vid mode do not forget to activate ControlNet model to achieve better results. Without it the resulting video might be quite choppy. Do not put any images in CN as the frames would pass automatically from the video.** +Here are CN parameters that seem to give the best results so far: +![sd-cn-animation cn params](examples/cn_settings.png) + + +### Video to Video Examples: + + + + + + + + + + + + +
Original video"Jessica Chastain""Watercolor painting"
+ +Examples presented are generated at 1024x576 resolution using the 'realisticVisionV13_v13' model as a base. They were cropt, downsized and compressed for better loading speed. You can see them in their original quality in the 'examples' folder. + +### Text to Video Examples: + + + + + + + + + + + + + + + + + + + + + + +
"close up of a flower""bonfire near the camp in the mountains at night""close up of a diamond laying on the table"
"close up of macaroni on the plate""close up of golden sphere""a tree standing in the winter forest"
+ +All examples you can see here are originally generated at 512x512 resolution using the 'sd-v1-5-inpainting' model as a base. They were downsized and compressed for better loading speed. You can see them in their original quality in the 'examples' folder. Actual prompts used were stated in the following format: "RAW photo, {subject}, 8k uhd, dslr, soft lighting, high quality, film grain, Fujifilm XT3", only the 'subject' part is described in the table above. + +## Installing the extension +To install the extension go to 'Extensions' tab in [Automatic1111 web-ui](https://github.com/AUTOMATIC1111/stable-diffusion-webui), then go to 'Install from URL' tab. In 'URL for extension's git repository' field inter the path to this repository, i.e. 'https://github.com/volotat/SD-CN-Animation.git'. Leave 'Local directory name' field empty. Then just press 'Install' button. Restart web-ui, new 'SD-CN-Animation' tab should appear. All generated video will be saved into 'stable-diffusion-webui/outputs/sd-cn-animation' folder. + +## Known issues +* If you see error like this ```IndexError: list index out of range``` try to restart webui, it should fix it. If the issue still prevelent try to uninstall and reinstall scikit-image==0.19.2 with no --no-cache-dir flag like this. +``` +pip uninstall scikit-image +pip install scikit-image==0.19.2 --no-cache-dir +``` +* The extension might work incorrectly if 'Apply color correction to img2img results to match original colors.' option is enabled. Make sure to disable it in 'Settings' tab -> 'Stable Diffusion' section. +* If you have an error like 'Need to enable queue to use generators.', please update webui to the latest version. Beware that only [Automatic1111 web-ui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) is fully supported. +* The extension is not compatible with Macs. If you have a case that extension is working for you or do you know how to make it compatible, please open a new discussion. + +## Last version changes: v0.9 +* Fixed issues #69, #76, #91, #92. +* Fixed an issue in vid2vid mode when an occlusion mask computed from the optical flow may include unnecessary parts (where flow is non-zero). +* Added 'Extra params' in vid2vid mode for more fine-grain controls of the processing pipeline. +* Better default parameters set for vid2vid pipeline. +* In txt2vid mode after the first frame is generated the seed is now automatically set to -1 to prevent blurring issues. +* Added an option to save resulting frames into a folder alongside the video. +* Added ability to export current parameters in a human readable form as a json. +* Interpolation mode in the flow-applying stage is set to ‘nearest’ to reduce overtime image blurring. +* Added ControlNet to txt2vid mode as well as fixing #86 issue, thanks to [@mariaWitch](https://github.com/mariaWitch) +* Fixed a major issue when ConrtolNet used wrong input images. Because of this vid2vid results were way worse than they should be. +* Text to video mode now supports video as a guidance for ControlNet. It allows to create much stronger video stylizations. + + diff --git a/SD-CN-Animation/requirements.txt b/SD-CN-Animation/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..ea923e0765cc4d1364426fad4a1ca1d439a76332 --- /dev/null +++ b/SD-CN-Animation/requirements.txt @@ -0,0 +1 @@ +scikit-image \ No newline at end of file diff --git a/SD-CN-Animation/scripts/__pycache__/base_ui.cpython-310.pyc b/SD-CN-Animation/scripts/__pycache__/base_ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ad3327b8ac307f78fa800f13f4bfa8bd2fba1ad Binary files /dev/null and b/SD-CN-Animation/scripts/__pycache__/base_ui.cpython-310.pyc differ diff --git a/SD-CN-Animation/scripts/base_ui.py b/SD-CN-Animation/scripts/base_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..0994fcf0b9ccda4c2a3e1a58fb3b742d85bfbc69 --- /dev/null +++ b/SD-CN-Animation/scripts/base_ui.py @@ -0,0 +1,252 @@ +import sys, os + +import gradio as gr +import modules +from types import SimpleNamespace + +from modules import script_callbacks, shared +from modules.shared import cmd_opts, opts +from webui import wrap_gradio_gpu_call + +from modules.ui_components import ToolButton, FormRow, FormGroup +from modules.ui import create_override_settings_dropdown +import modules.scripts as scripts + +from modules.sd_samplers import samplers_for_img2img +from modules.ui import setup_progressbar, create_sampler_and_steps_selection, ordered_ui_categories, create_output_panel + +from scripts.core import vid2vid, txt2vid, utils +import traceback + +def V2VArgs(): + seed = -1 + width = 1024 + height = 576 + cfg_scale = 5.5 + steps = 15 + prompt = "" + n_prompt = "text, letters, logo, brand, close up, cropped, out of frame, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck" + processing_strength = 0.85 + fix_frame_strength = 0.15 + return locals() + +def T2VArgs(): + seed = -1 + width = 768 + height = 512 + cfg_scale = 5.5 + steps = 15 + prompt = "" + n_prompt = "((blur, blurr, blurred, blurry, fuzzy, unclear, unfocus, bocca effect)), text, letters, logo, brand, close up, cropped, out of frame, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, extra fingers, mutated hands, poorly drawn hands, poorly drawn face, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, cloned face, disfigured, gross proportions, malformed limbs, missing arms, missing legs, extra arms, extra legs, fused fingers, too many fingers, long neck" + processing_strength = 0.75 + fix_frame_strength = 0.35 + return locals() + +def setup_common_values(mode, d): + with gr.Row(): + width = gr.Slider(label='Width', minimum=64, maximum=2048, step=64, value=d.width, interactive=True) + height = gr.Slider(label='Height', minimum=64, maximum=2048, step=64, value=d.height, interactive=True) + with gr.Row(elem_id=f'{mode}_prompt_toprow'): + prompt = gr.Textbox(label='Prompt', lines=3, interactive=True, elem_id=f"{mode}_prompt", placeholder="Enter your prompt here...") + with gr.Row(elem_id=f'{mode}_n_prompt_toprow'): + n_prompt = gr.Textbox(label='Negative prompt', lines=3, interactive=True, elem_id=f"{mode}_n_prompt", value=d.n_prompt) + with gr.Row(): + cfg_scale = gr.Slider(label='CFG scale', minimum=1, maximum=100, step=1, value=d.cfg_scale, interactive=True) + with gr.Row(): + seed = gr.Number(label='Seed (this parameter controls how the first frame looks like and the color distribution of the consecutive frames as they are dependent on the first one)', value = d.seed, Interactive = True, precision=0) + with gr.Row(): + processing_strength = gr.Slider(label="Processing strength (Step 1)", value=d.processing_strength, minimum=0, maximum=1, step=0.05, interactive=True) + fix_frame_strength = gr.Slider(label="Fix frame strength (Step 2)", value=d.fix_frame_strength, minimum=0, maximum=1, step=0.05, interactive=True) + with gr.Row(): + sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{mode}_sampling", choices=[x.name for x in samplers_for_img2img], value=samplers_for_img2img[0].name, type="index", interactive=True) + steps = gr.Slider(label="Sampling steps", minimum=1, maximum=150, step=1, elem_id=f"{mode}_steps", value=d.steps, interactive=True) + + return width, height, prompt, n_prompt, cfg_scale, seed, processing_strength, fix_frame_strength, sampler_index, steps + +def inputs_ui(): + v2v_args = SimpleNamespace(**V2VArgs()) + t2v_args = SimpleNamespace(**T2VArgs()) + with gr.Tabs(): + glo_sdcn_process_mode = gr.State(value='vid2vid') + + with gr.Tab('vid2vid') as tab_vid2vid: + with gr.Row(): + gr.HTML('Input video (each frame will be used as initial image for SD and as input image to CN): *REQUIRED') + with gr.Row(): + v2v_file = gr.File(label="Input video", interactive=True, file_count="single", file_types=["video"], elem_id="vid_to_vid_chosen_file") + + v2v_width, v2v_height, v2v_prompt, v2v_n_prompt, v2v_cfg_scale, v2v_seed, v2v_processing_strength, v2v_fix_frame_strength, v2v_sampler_index, v2v_steps = setup_common_values('vid2vid', v2v_args) + + with gr.Accordion("Extra settings",open=False): + gr.HTML('# Occlusion mask params:') + with gr.Row(): + with gr.Column(scale=1, variant='compact'): + v2v_occlusion_mask_blur = gr.Slider(label='Occlusion blur strength', minimum=0, maximum=10, step=0.1, value=3, interactive=True) + gr.HTML('') + v2v_occlusion_mask_trailing = gr.Checkbox(label="Occlusion trailing", info="Reduce ghosting but adds more flickering to the video", value=True, interactive=True) + with gr.Column(scale=1, variant='compact'): + v2v_occlusion_mask_flow_multiplier = gr.Slider(label='Occlusion flow multiplier', minimum=0, maximum=10, step=0.1, value=5, interactive=True) + v2v_occlusion_mask_difo_multiplier = gr.Slider(label='Occlusion diff origin multiplier', minimum=0, maximum=10, step=0.1, value=2, interactive=True) + v2v_occlusion_mask_difs_multiplier = gr.Slider(label='Occlusion diff styled multiplier', minimum=0, maximum=10, step=0.1, value=0, interactive=True) + + with gr.Row(): + with gr.Column(scale=1, variant='compact'): + gr.HTML('# Step 1 params:') + v2v_step_1_seed = gr.Number(label='Seed', value = -1, Interactive = True, precision=0) + gr.HTML('
') + v2v_step_1_blend_alpha = gr.Slider(label='Warped prev frame vs Current frame blend alpha', minimum=0, maximum=1, step=0.1, value=1, interactive=True) + v2v_step_1_processing_mode = gr.Radio(["Process full image then blend in occlusions", "Inpaint occlusions"], type="index", \ + label="Processing mode", value="Process full image then blend in occlusions", interactive=True) + + + with gr.Column(scale=1, variant='compact'): + gr.HTML('# Step 2 params:') + v2v_step_2_seed = gr.Number(label='Seed', value = 8888, Interactive = True, precision=0) + + with FormRow(elem_id="vid2vid_override_settings_row") as row: + v2v_override_settings = create_override_settings_dropdown("vid2vid", row) + + with FormGroup(elem_id=f"script_container"): + v2v_custom_inputs = scripts.scripts_img2img.setup_ui() + + with gr.Tab('txt2vid') as tab_txt2vid: + with gr.Row(): + gr.HTML('Control video (each frame will be used as input image to CN): *NOT REQUIRED') + with gr.Row(): + t2v_file = gr.File(label="Input video", interactive=True, file_count="single", file_types=["video"], elem_id="tex_to_vid_chosen_file") + t2v_init_image = gr.Image(label="Input image", interactive=True, file_count="single", file_types=["image"], elem_id="tex_to_vid_init_image") + + t2v_width, t2v_height, t2v_prompt, t2v_n_prompt, t2v_cfg_scale, t2v_seed, t2v_processing_strength, t2v_fix_frame_strength, t2v_sampler_index, t2v_steps = setup_common_values('txt2vid', t2v_args) + + with gr.Row(): + t2v_length = gr.Slider(label='Length (in frames)', minimum=10, maximum=2048, step=10, value=40, interactive=True) + t2v_fps = gr.Slider(label='Video FPS', minimum=4, maximum=64, step=4, value=12, interactive=True) + + gr.HTML('
') + t2v_cn_frame_send = gr.Radio(["None", "Current generated frame", "Previous generated frame", "Current reference video frame"], type="index", \ + label="What frame should be send to CN?", value="None", interactive=True) + + with FormRow(elem_id="txt2vid_override_settings_row") as row: + t2v_override_settings = create_override_settings_dropdown("txt2vid", row) + + with FormGroup(elem_id=f"script_container"): + t2v_custom_inputs = scripts.scripts_txt2img.setup_ui() + + tab_vid2vid.select(fn=lambda: 'vid2vid', inputs=[], outputs=[glo_sdcn_process_mode]) + tab_txt2vid.select(fn=lambda: 'txt2vid', inputs=[], outputs=[glo_sdcn_process_mode]) + + return locals() + +def process(*args): + msg = 'Done' + try: + if args[0] == 'vid2vid': + yield from vid2vid.start_process(*args) + elif args[0] == 'txt2vid': + yield from txt2vid.start_process(*args) + else: + msg = f"Unsupported processing mode: '{args[0]}'" + raise Exception(msg) + except Exception as error: + # handle the exception + msg = f"An exception occurred while trying to process the frame: {error}" + print(msg) + traceback.print_exc() + + yield msg, gr.Image.update(), gr.Image.update(), gr.Image.update(), gr.Image.update(), gr.Video.update(), gr.Button.update(interactive=True), gr.Button.update(interactive=False) + +def stop_process(*args): + utils.shared.is_interrupted = True + return gr.Button.update(interactive=False) + + + +def on_ui_tabs(): + modules.scripts.scripts_current = modules.scripts.scripts_img2img + modules.scripts.scripts_img2img.initialize_scripts(is_img2img=True) + + with gr.Blocks(analytics_enabled=False) as sdcnanim_interface: + components = {} + + #dv = SimpleNamespace(**T2VOutputArgs()) + with gr.Row(elem_id='sdcn-core').style(equal_height=False, variant='compact'): + with gr.Column(scale=1, variant='panel'): + #with gr.Tabs(): + components = inputs_ui() + + with gr.Accordion("Export settings", open=False): + export_settings_button = gr.Button('Export', elem_id=f"sdcn_export_settings_button") + export_setting_json = gr.Code(value='') + + + with gr.Column(scale=1, variant='compact'): + with gr.Row(variant='compact'): + run_button = gr.Button('Generate', elem_id=f"sdcn_anim_generate", variant='primary') + stop_button = gr.Button('Interrupt', elem_id=f"sdcn_anim_interrupt", variant='primary', interactive=False) + + save_frames_check = gr.Checkbox(label="Save frames into a folder nearby a video (check it before running the generation if you also want to save frames separately)", value=True, interactive=True) + gr.HTML('
') + + with gr.Column(variant="panel"): + sp_progress = gr.HTML(elem_id="sp_progress", value="") + + with gr.Row(variant='compact'): + img_preview_curr_frame = gr.Image(label='Current frame', elem_id=f"img_preview_curr_frame", type='pil').style(height=240) + img_preview_curr_occl = gr.Image(label='Current occlusion', elem_id=f"img_preview_curr_occl", type='pil').style(height=240) + with gr.Row(variant='compact'): + img_preview_prev_warp = gr.Image(label='Previous frame warped', elem_id=f"img_preview_curr_frame", type='pil').style(height=240) + img_preview_processed = gr.Image(label='Processed', elem_id=f"img_preview_processed", type='pil').style(height=240) + + video_preview = gr.Video(interactive=False) + + with gr.Row(variant='compact'): + dummy_component = gr.Label(visible=False) + + components['glo_save_frames_check'] = save_frames_check + + # Define parameters for the action methods. + utils.shared.v2v_custom_inputs_size = len(components['v2v_custom_inputs']) + utils.shared.t2v_custom_inputs_size = len(components['t2v_custom_inputs']) + #print('v2v_custom_inputs', len(components['v2v_custom_inputs']), components['v2v_custom_inputs']) + #print('t2v_custom_inputs', len(components['t2v_custom_inputs']), components['t2v_custom_inputs']) + method_inputs = [components[name] for name in utils.get_component_names()] + components['v2v_custom_inputs'] + components['t2v_custom_inputs'] + + method_outputs = [ + sp_progress, + img_preview_curr_frame, + img_preview_curr_occl, + img_preview_prev_warp, + img_preview_processed, + video_preview, + run_button, + stop_button, + ] + + run_button.click( + fn=process, #wrap_gradio_gpu_call(start_process, extra_outputs=[None, '', '']), + inputs=method_inputs, + outputs=method_outputs, + show_progress=True, + ) + + stop_button.click( + fn=stop_process, + outputs=[stop_button], + show_progress=False + ) + + export_settings_button.click( + fn=utils.export_settings, + inputs=method_inputs, + outputs=[export_setting_json], + show_progress=False + ) + + modules.scripts.scripts_current = None + + # define queue - required for generators + sdcnanim_interface.queue(concurrency_count=1) + return [(sdcnanim_interface, "SD-CN-Animation", "sd_cn_animation_interface")] + + +script_callbacks.on_ui_tabs(on_ui_tabs) diff --git a/SD-CN-Animation/scripts/core/__pycache__/flow_utils.cpython-310.pyc b/SD-CN-Animation/scripts/core/__pycache__/flow_utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6366601ea0662901491dd85d2ffae5b130d265b5 Binary files /dev/null and b/SD-CN-Animation/scripts/core/__pycache__/flow_utils.cpython-310.pyc differ diff --git a/SD-CN-Animation/scripts/core/__pycache__/txt2vid.cpython-310.pyc b/SD-CN-Animation/scripts/core/__pycache__/txt2vid.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e170d95c657d927f42e76e5d15583ba5b701b9d Binary files /dev/null and b/SD-CN-Animation/scripts/core/__pycache__/txt2vid.cpython-310.pyc differ diff --git a/SD-CN-Animation/scripts/core/__pycache__/utils.cpython-310.pyc b/SD-CN-Animation/scripts/core/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f5590bd68baf60d36ef3060bc361aab2ee34600 Binary files /dev/null and b/SD-CN-Animation/scripts/core/__pycache__/utils.cpython-310.pyc differ diff --git a/SD-CN-Animation/scripts/core/__pycache__/vid2vid.cpython-310.pyc b/SD-CN-Animation/scripts/core/__pycache__/vid2vid.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb3030945fa8b85ea580730d1e6ef8136e23942a Binary files /dev/null and b/SD-CN-Animation/scripts/core/__pycache__/vid2vid.cpython-310.pyc differ diff --git a/SD-CN-Animation/scripts/core/flow_utils.py b/SD-CN-Animation/scripts/core/flow_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..15eae7732c1200d392a15fcdc1015991fc63cb5e --- /dev/null +++ b/SD-CN-Animation/scripts/core/flow_utils.py @@ -0,0 +1,156 @@ +import sys, os + +import numpy as np +import cv2 + +from collections import namedtuple +import torch +import argparse +from RAFT.raft import RAFT +from RAFT.utils.utils import InputPadder + +import modules.paths as ph +import gc + +RAFT_model = None +fgbg = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=True) + +def background_subtractor(frame, fgbg): + fgmask = fgbg.apply(frame) + return cv2.bitwise_and(frame, frame, mask=fgmask) + +def RAFT_clear_memory(): + global RAFT_model + del RAFT_model + gc.collect() + torch.cuda.empty_cache() + RAFT_model = None + +def RAFT_estimate_flow(frame1, frame2, device='cuda'): + global RAFT_model + + org_size = frame1.shape[1], frame1.shape[0] + size = frame1.shape[1] // 16 * 16, frame1.shape[0] // 16 * 16 + frame1 = cv2.resize(frame1, size) + frame2 = cv2.resize(frame2, size) + + model_path = ph.models_path + '/RAFT/raft-things.pth' + remote_model_path = 'https://drive.google.com/uc?id=1MqDajR89k-xLV0HIrmJ0k-n8ZpG6_suM' + + if not os.path.isfile(model_path): + from basicsr.utils.download_util import load_file_from_url + os.makedirs(os.path.dirname(model_path), exist_ok=True) + load_file_from_url(remote_model_path, file_name=model_path) + + if RAFT_model is None: + args = argparse.Namespace(**{ + 'model': ph.models_path + '/RAFT/raft-things.pth', + 'mixed_precision': True, + 'small': False, + 'alternate_corr': False, + 'path': "" + }) + + RAFT_model = torch.nn.DataParallel(RAFT(args)) + RAFT_model.load_state_dict(torch.load(args.model)) + + RAFT_model = RAFT_model.module + RAFT_model.to(device) + RAFT_model.eval() + + with torch.no_grad(): + frame1_torch = torch.from_numpy(frame1).permute(2, 0, 1).float()[None].to(device) + frame2_torch = torch.from_numpy(frame2).permute(2, 0, 1).float()[None].to(device) + + padder = InputPadder(frame1_torch.shape) + image1, image2 = padder.pad(frame1_torch, frame2_torch) + + # estimate optical flow + _, next_flow = RAFT_model(image1, image2, iters=20, test_mode=True) + _, prev_flow = RAFT_model(image2, image1, iters=20, test_mode=True) + + next_flow = next_flow[0].permute(1, 2, 0).cpu().numpy() + prev_flow = prev_flow[0].permute(1, 2, 0).cpu().numpy() + + fb_flow = next_flow + prev_flow + fb_norm = np.linalg.norm(fb_flow, axis=2) + + occlusion_mask = fb_norm[..., None].repeat(3, axis=-1) + + next_flow = cv2.resize(next_flow, org_size) + prev_flow = cv2.resize(prev_flow, org_size) + + return next_flow, prev_flow, occlusion_mask + +def compute_diff_map(next_flow, prev_flow, prev_frame, cur_frame, prev_frame_styled, args_dict): + h, w = cur_frame.shape[:2] + fl_w, fl_h = next_flow.shape[:2] + + # normalize flow + next_flow = next_flow / np.array([fl_h,fl_w]) + prev_flow = prev_flow / np.array([fl_h,fl_w]) + + # compute occlusion mask + fb_flow = next_flow + prev_flow + fb_norm = np.linalg.norm(fb_flow , axis=2) + + zero_flow_mask = np.clip(1 - np.linalg.norm(prev_flow, axis=-1)[...,None] * 20, 0, 1) + diff_mask_flow = fb_norm[..., None] * zero_flow_mask + + # resize flow + next_flow = cv2.resize(next_flow, (w, h)) + next_flow = (next_flow * np.array([h,w])).astype(np.float32) + prev_flow = cv2.resize(prev_flow, (w, h)) + prev_flow = (prev_flow * np.array([h,w])).astype(np.float32) + + # Generate sampling grids + grid_y, grid_x = torch.meshgrid(torch.arange(0, h), torch.arange(0, w)) + flow_grid = torch.stack((grid_x, grid_y), dim=0).float() + flow_grid += torch.from_numpy(prev_flow).permute(2, 0, 1) + flow_grid = flow_grid.unsqueeze(0) + flow_grid[:, 0, :, :] = 2 * flow_grid[:, 0, :, :] / (w - 1) - 1 + flow_grid[:, 1, :, :] = 2 * flow_grid[:, 1, :, :] / (h - 1) - 1 + flow_grid = flow_grid.permute(0, 2, 3, 1) + + + prev_frame_torch = torch.from_numpy(prev_frame).float().unsqueeze(0).permute(0, 3, 1, 2) #N, C, H, W + prev_frame_styled_torch = torch.from_numpy(prev_frame_styled).float().unsqueeze(0).permute(0, 3, 1, 2) #N, C, H, W + + warped_frame = torch.nn.functional.grid_sample(prev_frame_torch, flow_grid, mode="nearest", padding_mode="reflection", align_corners=True).permute(0, 2, 3, 1)[0].numpy() + warped_frame_styled = torch.nn.functional.grid_sample(prev_frame_styled_torch, flow_grid, mode="nearest", padding_mode="reflection", align_corners=True).permute(0, 2, 3, 1)[0].numpy() + + #warped_frame = cv2.remap(prev_frame, flow_map, None, cv2.INTER_NEAREST, borderMode = cv2.BORDER_REFLECT) + #warped_frame_styled = cv2.remap(prev_frame_styled, flow_map, None, cv2.INTER_NEAREST, borderMode = cv2.BORDER_REFLECT) + + + diff_mask_org = np.abs(warped_frame.astype(np.float32) - cur_frame.astype(np.float32)) / 255 + diff_mask_org = diff_mask_org.max(axis = -1, keepdims=True) + + diff_mask_stl = np.abs(warped_frame_styled.astype(np.float32) - cur_frame.astype(np.float32)) / 255 + diff_mask_stl = diff_mask_stl.max(axis = -1, keepdims=True) + + alpha_mask = np.maximum.reduce([diff_mask_flow * args_dict['occlusion_mask_flow_multiplier'] * 10, \ + diff_mask_org * args_dict['occlusion_mask_difo_multiplier'], \ + diff_mask_stl * args_dict['occlusion_mask_difs_multiplier']]) # + alpha_mask = alpha_mask.repeat(3, axis = -1) + + #alpha_mask_blured = cv2.dilate(alpha_mask, np.ones((5, 5), np.float32)) + if args_dict['occlusion_mask_blur'] > 0: + blur_filter_size = min(w,h) // 15 | 1 + alpha_mask = cv2.GaussianBlur(alpha_mask, (blur_filter_size, blur_filter_size) , args_dict['occlusion_mask_blur'], cv2.BORDER_REFLECT) + + alpha_mask = np.clip(alpha_mask, 0, 1) + + return alpha_mask, warped_frame_styled + +def frames_norm(frame): return frame / 127.5 - 1 + +def flow_norm(flow): return flow / 255 + +def occl_norm(occl): return occl / 127.5 - 1 + +def frames_renorm(frame): return (frame + 1) * 127.5 + +def flow_renorm(flow): return flow * 255 + +def occl_renorm(occl): return (occl + 1) * 127.5 diff --git a/SD-CN-Animation/scripts/core/txt2vid.py b/SD-CN-Animation/scripts/core/txt2vid.py new file mode 100644 index 0000000000000000000000000000000000000000..a03784a531e47d9e0176aa084ad1c21710888ef5 --- /dev/null +++ b/SD-CN-Animation/scripts/core/txt2vid.py @@ -0,0 +1,240 @@ +import sys, os + +import torch +import gc +import numpy as np +from PIL import Image + +import modules.paths as ph +from modules.shared import devices + +from scripts.core import utils, flow_utils +from FloweR.model import FloweR + +import skimage +import datetime +import cv2 +import gradio as gr +import time + +FloweR_model = None +DEVICE = 'cpu' +def FloweR_clear_memory(): + global FloweR_model + del FloweR_model + gc.collect() + torch.cuda.empty_cache() + FloweR_model = None + +def FloweR_load_model(w, h): + global DEVICE, FloweR_model + DEVICE = devices.get_optimal_device() + + model_path = ph.models_path + '/FloweR/FloweR_0.1.2.pth' + #remote_model_path = 'https://drive.google.com/uc?id=1K7gXUosgxU729_l-osl1HBU5xqyLsALv' #FloweR_0.1.1.pth + remote_model_path = 'https://drive.google.com/uc?id=1-UYsTXkdUkHLgtPK1Y5_7kKzCgzL_Z6o' #FloweR_0.1.2.pth + + if not os.path.isfile(model_path): + from basicsr.utils.download_util import load_file_from_url + os.makedirs(os.path.dirname(model_path), exist_ok=True) + load_file_from_url(remote_model_path, file_name=model_path) + + + FloweR_model = FloweR(input_size = (h, w)) + FloweR_model.load_state_dict(torch.load(model_path, map_location=DEVICE)) + # Move the model to the device + FloweR_model = FloweR_model.to(DEVICE) + FloweR_model.eval() + +def read_frame_from_video(input_video): + if input_video is None: return None + + # Reading video file + if input_video.isOpened(): + ret, cur_frame = input_video.read() + if cur_frame is not None: + cur_frame = cv2.cvtColor(cur_frame, cv2.COLOR_BGR2RGB) + else: + cur_frame = None + input_video.release() + input_video = None + + return cur_frame + +def start_process(*args): + processing_start_time = time.time() + args_dict = utils.args_to_dict(*args) + args_dict = utils.get_mode_args('t2v', args_dict) + + # Open the input video file + input_video = None + if args_dict['file'] is not None: + input_video = cv2.VideoCapture(args_dict['file'].name) + + # Create an output video file with the same fps, width, and height as the input video + output_video_name = f'outputs/sd-cn-animation/txt2vid/{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}.mp4' + output_video_folder = os.path.splitext(output_video_name)[0] + os.makedirs(os.path.dirname(output_video_name), exist_ok=True) + + #if args_dict['save_frames_check']: + os.makedirs(output_video_folder, exist_ok=True) + + # Writing to current params to params.json + setts_json = utils.export_settings(*args) + with open(os.path.join(output_video_folder, "params.json"), "w") as outfile: + outfile.write(setts_json) + + curr_frame = None + prev_frame = None + + def save_result_to_image(image, ind): + if args_dict['save_frames_check']: + cv2.imwrite(os.path.join(output_video_folder, f'{ind:05d}.png'), cv2.cvtColor(image, cv2.COLOR_RGB2BGR)) + + def set_cn_frame_input(): + if args_dict['cn_frame_send'] == 0: # Current generated frame" + pass + elif args_dict['cn_frame_send'] == 1: # Current generated frame" + if curr_frame is not None: + utils.set_CNs_input_image(args_dict, Image.fromarray(curr_frame), set_references=True) + elif args_dict['cn_frame_send'] == 2: # Previous generated frame + if prev_frame is not None: + utils.set_CNs_input_image(args_dict, Image.fromarray(prev_frame), set_references=True) + elif args_dict['cn_frame_send'] == 3: # Current reference video frame + if input_video is not None: + curr_video_frame = read_frame_from_video(input_video) + curr_video_frame = cv2.resize(curr_video_frame, (args_dict['width'], args_dict['height'])) + utils.set_CNs_input_image(args_dict, Image.fromarray(curr_video_frame), set_references=True) + else: + raise Exception('There is no input video! Set it up first.') + else: + raise Exception('Incorrect cn_frame_send mode!') + + set_cn_frame_input() + + if args_dict['init_image'] is not None: + #resize array to args_dict['width'], args_dict['height'] + image_array=args_dict['init_image']#this is a numpy array + init_frame = np.array(Image.fromarray(image_array).resize((args_dict['width'], args_dict['height'])).convert('RGB')) + processed_frame = init_frame.copy() + else: + processed_frames, _, _, _ = utils.txt2img(args_dict) + processed_frame = np.array(processed_frames[0])[...,:3] + #if input_video is not None: + # processed_frame = skimage.exposure.match_histograms(processed_frame, curr_video_frame, channel_axis=-1) + processed_frame = np.clip(processed_frame, 0, 255).astype(np.uint8) + init_frame = processed_frame.copy() + + output_video = cv2.VideoWriter(output_video_name, cv2.VideoWriter_fourcc(*'mp4v'), args_dict['fps'], (args_dict['width'], args_dict['height'])) + output_video.write(cv2.cvtColor(processed_frame, cv2.COLOR_RGB2BGR)) + + stat = f"Frame: 1 / {args_dict['length']}; " + utils.get_time_left(1, args_dict['length'], processing_start_time) + utils.shared.is_interrupted = False + + save_result_to_image(processed_frame, 1) + yield stat, init_frame, None, None, processed_frame, None, gr.Button.update(interactive=False), gr.Button.update(interactive=True) + + org_size = args_dict['width'], args_dict['height'] + size = args_dict['width'] // 128 * 128, args_dict['height'] // 128 * 128 + FloweR_load_model(size[0], size[1]) + + clip_frames = np.zeros((4, size[1], size[0], 3), dtype=np.uint8) + + prev_frame = init_frame + + for ind in range(args_dict['length'] - 1): + if utils.shared.is_interrupted: break + + args_dict = utils.args_to_dict(*args) + args_dict = utils.get_mode_args('t2v', args_dict) + + clip_frames = np.roll(clip_frames, -1, axis=0) + clip_frames[-1] = cv2.resize(prev_frame[...,:3], size) + clip_frames_torch = flow_utils.frames_norm(torch.from_numpy(clip_frames).to(DEVICE, dtype=torch.float32)) + + with torch.no_grad(): + pred_data = FloweR_model(clip_frames_torch.unsqueeze(0))[0] + + pred_flow = flow_utils.flow_renorm(pred_data[...,:2]).cpu().numpy() + pred_occl = flow_utils.occl_renorm(pred_data[...,2:3]).cpu().numpy().repeat(3, axis = -1) + pred_next = flow_utils.frames_renorm(pred_data[...,3:6]).cpu().numpy() + + pred_occl = np.clip(pred_occl * 10, 0, 255).astype(np.uint8) + pred_next = np.clip(pred_next, 0, 255).astype(np.uint8) + + pred_flow = cv2.resize(pred_flow, org_size) + pred_occl = cv2.resize(pred_occl, org_size) + pred_next = cv2.resize(pred_next, org_size) + + curr_frame = pred_next.copy() + + ''' + pred_flow = pred_flow / (1 + np.linalg.norm(pred_flow, axis=-1, keepdims=True) * 0.05) + pred_flow = cv2.GaussianBlur(pred_flow, (31,31), 1, cv2.BORDER_REFLECT_101) + + pred_occl = cv2.GaussianBlur(pred_occl, (21,21), 2, cv2.BORDER_REFLECT_101) + pred_occl = (np.abs(pred_occl / 255) ** 1.5) * 255 + pred_occl = np.clip(pred_occl * 25, 0, 255).astype(np.uint8) + + flow_map = pred_flow.copy() + flow_map[:,:,0] += np.arange(args_dict['width']) + flow_map[:,:,1] += np.arange(args_dict['height'])[:,np.newaxis] + + warped_frame = cv2.remap(prev_frame, flow_map, None, cv2.INTER_NEAREST, borderMode = cv2.BORDER_REFLECT_101) + alpha_mask = pred_occl / 255. + #alpha_mask = np.clip(alpha_mask + np.random.normal(0, 0.4, size = alpha_mask.shape), 0, 1) + curr_frame = pred_next.astype(float) * alpha_mask + warped_frame.astype(float) * (1 - alpha_mask) + curr_frame = np.clip(curr_frame, 0, 255).astype(np.uint8) + #curr_frame = warped_frame.copy() + ''' + + set_cn_frame_input() + + args_dict['mode'] = 4 + args_dict['init_img'] = Image.fromarray(pred_next) + args_dict['mask_img'] = Image.fromarray(pred_occl) + args_dict['seed'] = -1 + args_dict['denoising_strength'] = args_dict['processing_strength'] + + processed_frames, _, _, _ = utils.img2img(args_dict) + processed_frame = np.array(processed_frames[0])[...,:3] + #if input_video is not None: + # processed_frame = skimage.exposure.match_histograms(processed_frame, curr_video_frame, channel_axis=-1) + #else: + processed_frame = skimage.exposure.match_histograms(processed_frame, init_frame, channel_axis=-1) + processed_frame = np.clip(processed_frame, 0, 255).astype(np.uint8) + + args_dict['mode'] = 0 + args_dict['init_img'] = Image.fromarray(processed_frame) + args_dict['mask_img'] = None + args_dict['seed'] = -1 + args_dict['denoising_strength'] = args_dict['fix_frame_strength'] + + #utils.set_CNs_input_image(args_dict, Image.fromarray(curr_frame)) + processed_frames, _, _, _ = utils.img2img(args_dict) + processed_frame = np.array(processed_frames[0])[...,:3] + #if input_video is not None: + # processed_frame = skimage.exposure.match_histograms(processed_frame, curr_video_frame, channel_axis=-1) + #else: + processed_frame = skimage.exposure.match_histograms(processed_frame, init_frame, channel_axis=-1) + processed_frame = np.clip(processed_frame, 0, 255).astype(np.uint8) + + output_video.write(cv2.cvtColor(processed_frame, cv2.COLOR_RGB2BGR)) + prev_frame = processed_frame.copy() + + save_result_to_image(processed_frame, ind + 2) + stat = f"Frame: {ind + 2} / {args_dict['length']}; " + utils.get_time_left(ind+2, args_dict['length'], processing_start_time) + yield stat, curr_frame, pred_occl, pred_next, processed_frame, None, gr.Button.update(interactive=False), gr.Button.update(interactive=True) + + if input_video is not None: input_video.release() + output_video.release() + FloweR_clear_memory() + + curr_frame = gr.Image.update() + occlusion_mask = gr.Image.update() + warped_styled_frame_ = gr.Image.update() + processed_frame = gr.Image.update() + + # print('TOTAL TIME:', int(time.time() - processing_start_time)) + + yield 'done', curr_frame, occlusion_mask, warped_styled_frame_, processed_frame, output_video_name, gr.Button.update(interactive=True), gr.Button.update(interactive=False) \ No newline at end of file diff --git a/SD-CN-Animation/scripts/core/utils.py b/SD-CN-Animation/scripts/core/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ef3bcb90cc98550fb1c232f664972146d71846f9 --- /dev/null +++ b/SD-CN-Animation/scripts/core/utils.py @@ -0,0 +1,432 @@ +class shared: + is_interrupted = False + v2v_custom_inputs_size = 0 + t2v_custom_inputs_size = 0 + +def get_component_names(): + components_list = [ + 'glo_sdcn_process_mode', + 'v2v_file', 'v2v_width', 'v2v_height', 'v2v_prompt', 'v2v_n_prompt', 'v2v_cfg_scale', 'v2v_seed', 'v2v_processing_strength', 'v2v_fix_frame_strength', + 'v2v_sampler_index', 'v2v_steps', 'v2v_override_settings', + 'v2v_occlusion_mask_blur', 'v2v_occlusion_mask_trailing', 'v2v_occlusion_mask_flow_multiplier', 'v2v_occlusion_mask_difo_multiplier', 'v2v_occlusion_mask_difs_multiplier', + 'v2v_step_1_processing_mode', 'v2v_step_1_blend_alpha', 'v2v_step_1_seed', 'v2v_step_2_seed', + 't2v_file','t2v_init_image', 't2v_width', 't2v_height', 't2v_prompt', 't2v_n_prompt', 't2v_cfg_scale', 't2v_seed', 't2v_processing_strength', 't2v_fix_frame_strength', + 't2v_sampler_index', 't2v_steps', 't2v_length', 't2v_fps', 't2v_cn_frame_send', + 'glo_save_frames_check' + ] + + return components_list + +def args_to_dict(*args): # converts list of argumets into dictionary for better handling of it + args_list = get_component_names() + + # set default values for params that were not specified + args_dict = { + # video to video params + 'v2v_mode': 0, + 'v2v_prompt': '', + 'v2v_n_prompt': '', + 'v2v_prompt_styles': [], + 'v2v_init_video': None, # Always required + + 'v2v_steps': 15, + 'v2v_sampler_index': 0, # 'Euler a' + 'v2v_mask_blur': 0, + + 'v2v_inpainting_fill': 1, # original + 'v2v_restore_faces': False, + 'v2v_tiling': False, + 'v2v_n_iter': 1, + 'v2v_batch_size': 1, + 'v2v_cfg_scale': 5.5, + 'v2v_image_cfg_scale': 1.5, + 'v2v_denoising_strength': 0.75, + 'v2v_processing_strength': 0.85, + 'v2v_fix_frame_strength': 0.15, + 'v2v_seed': -1, + 'v2v_subseed': -1, + 'v2v_subseed_strength': 0, + 'v2v_seed_resize_from_h': 512, + 'v2v_seed_resize_from_w': 512, + 'v2v_seed_enable_extras': False, + 'v2v_height': 512, + 'v2v_width': 512, + 'v2v_resize_mode': 1, + 'v2v_inpaint_full_res': True, + 'v2v_inpaint_full_res_padding': 0, + 'v2v_inpainting_mask_invert': False, + + # text to video params + 't2v_mode': 4, + 't2v_prompt': '', + 't2v_n_prompt': '', + 't2v_prompt_styles': [], + 't2v_init_img': None, + 't2v_mask_img': None, + + 't2v_steps': 15, + 't2v_sampler_index': 0, # 'Euler a' + 't2v_mask_blur': 0, + + 't2v_inpainting_fill': 1, # original + 't2v_restore_faces': False, + 't2v_tiling': False, + 't2v_n_iter': 1, + 't2v_batch_size': 1, + 't2v_cfg_scale': 5.5, + 't2v_image_cfg_scale': 1.5, + 't2v_denoising_strength': 0.75, + 't2v_processing_strength': 0.85, + 't2v_fix_frame_strength': 0.15, + 't2v_seed': -1, + 't2v_subseed': -1, + 't2v_subseed_strength': 0, + 't2v_seed_resize_from_h': 512, + 't2v_seed_resize_from_w': 512, + 't2v_seed_enable_extras': False, + 't2v_height': 512, + 't2v_width': 512, + 't2v_resize_mode': 1, + 't2v_inpaint_full_res': True, + 't2v_inpaint_full_res_padding': 0, + 't2v_inpainting_mask_invert': False, + + 't2v_override_settings': [], + #'t2v_script_inputs': [0], + + 't2v_fps': 12, + } + + args = list(args) + + for i in range(len(args_list)): + if (args[i] is None) and (args_list[i] in args_dict): + #args[i] = args_dict[args_list[i]] + pass + else: + args_dict[args_list[i]] = args[i] + + args_dict['v2v_script_inputs'] = args[len(args_list):len(args_list)+shared.v2v_custom_inputs_size] + #print('v2v_script_inputs', args_dict['v2v_script_inputs']) + args_dict['t2v_script_inputs'] = args[len(args_list)+shared.v2v_custom_inputs_size:] + #print('t2v_script_inputs', args_dict['t2v_script_inputs']) + return args_dict + +def get_mode_args(mode, args_dict): + mode_args_dict = {} + for key, value in args_dict.items(): + if key[:3] in [mode, 'glo'] : + mode_args_dict[key[4:]] = value + + return mode_args_dict + +def set_CNs_input_image(args_dict, image, set_references = False): + for script_input in args_dict['script_inputs']: + if type(script_input).__name__ == 'UiControlNetUnit': + if script_input.module not in ["reference_only", "reference_adain", "reference_adain+attn"] or set_references: + script_input.image = np.array(image) + script_input.batch_images = [np.array(image)] + +import time +import datetime + +def get_time_left(ind, length, processing_start_time): + s_passed = int(time.time() - processing_start_time) + time_passed = datetime.timedelta(seconds=s_passed) + s_left = int(s_passed / ind * (length - ind)) + time_left = datetime.timedelta(seconds=s_left) + return f"Time elapsed: {time_passed}; Time left: {time_left};" + +import numpy as np +from PIL import Image, ImageOps, ImageFilter, ImageEnhance, ImageChops +from types import SimpleNamespace + +from modules.generation_parameters_copypaste import create_override_settings_dict +from modules.processing import Processed, StableDiffusionProcessingImg2Img, StableDiffusionProcessingTxt2Img, process_images +import modules.processing as processing +from modules.ui import plaintext_to_html +import modules.images as images +import modules.scripts +from modules.shared import opts, devices, state +from modules import devices, sd_samplers, img2img +from modules import shared, sd_hijack, lowvram + +# TODO: Refactor all the code below + +def process_img(p, input_img, output_dir, inpaint_mask_dir, args): + processing.fix_seed(p) + + #images = shared.listfiles(input_dir) + images = [input_img] + + is_inpaint_batch = False + #if inpaint_mask_dir: + # inpaint_masks = shared.listfiles(inpaint_mask_dir) + # is_inpaint_batch = len(inpaint_masks) > 0 + #if is_inpaint_batch: + # print(f"\nInpaint batch is enabled. {len(inpaint_masks)} masks found.") + + #print(f"Will process {len(images)} images, creating {p.n_iter * p.batch_size} new images for each.") + + save_normally = output_dir == '' + + p.do_not_save_grid = True + p.do_not_save_samples = not save_normally + + state.job_count = len(images) * p.n_iter + + generated_images = [] + for i, image in enumerate(images): + state.job = f"{i+1} out of {len(images)}" + if state.skipped: + state.skipped = False + + if state.interrupted: + break + + img = image #Image.open(image) + # Use the EXIF orientation of photos taken by smartphones. + img = ImageOps.exif_transpose(img) + p.init_images = [img] * p.batch_size + + #if is_inpaint_batch: + # # try to find corresponding mask for an image using simple filename matching + # mask_image_path = os.path.join(inpaint_mask_dir, os.path.basename(image)) + # # if not found use first one ("same mask for all images" use-case) + # if not mask_image_path in inpaint_masks: + # mask_image_path = inpaint_masks[0] + # mask_image = Image.open(mask_image_path) + # p.image_mask = mask_image + + proc = modules.scripts.scripts_img2img.run(p, *args) + if proc is None: + proc = process_images(p) + generated_images.append(proc.images[0]) + + #for n, processed_image in enumerate(proc.images): + # filename = os.path.basename(image) + + # if n > 0: + # left, right = os.path.splitext(filename) + # filename = f"{left}-{n}{right}" + + # if not save_normally: + # os.makedirs(output_dir, exist_ok=True) + # if processed_image.mode == 'RGBA': + # processed_image = processed_image.convert("RGB") + # processed_image.save(os.path.join(output_dir, filename)) + + return generated_images + +def img2img(args_dict): + args = SimpleNamespace(**args_dict) + override_settings = create_override_settings_dict(args.override_settings) + + is_batch = args.mode == 5 + + if args.mode == 0: # img2img + image = args.init_img.convert("RGB") + mask = None + elif args.mode == 1: # img2img sketch + image = args.sketch.convert("RGB") + mask = None + elif args.mode == 2: # inpaint + image, mask = args.init_img_with_mask["image"], args.init_img_with_mask["mask"] + alpha_mask = ImageOps.invert(image.split()[-1]).convert('L').point(lambda x: 255 if x > 0 else 0, mode='1') + mask = ImageChops.lighter(alpha_mask, mask.convert('L')).convert('L') + image = image.convert("RGB") + elif args.mode == 3: # inpaint sketch + image = args.inpaint_color_sketch + orig = args.inpaint_color_sketch_orig or args.inpaint_color_sketch + pred = np.any(np.array(image) != np.array(orig), axis=-1) + mask = Image.fromarray(pred.astype(np.uint8) * 255, "L") + mask = ImageEnhance.Brightness(mask).enhance(1 - args.mask_alpha / 100) + blur = ImageFilter.GaussianBlur(args.mask_blur) + image = Image.composite(image.filter(blur), orig, mask.filter(blur)) + image = image.convert("RGB") + elif args.mode == 4: # inpaint upload mask + #image = args.init_img_inpaint + #mask = args.init_mask_inpaint + + image = args.init_img.convert("RGB") + mask = args.mask_img.convert("L") + else: + image = None + mask = None + + # Use the EXIF orientation of photos taken by smartphones. + if image is not None: + image = ImageOps.exif_transpose(image) + + assert 0. <= args.denoising_strength <= 1., 'can only work with strength in [0.0, 1.0]' + + p = StableDiffusionProcessingImg2Img( + sd_model=shared.sd_model, + outpath_samples=opts.outdir_samples or opts.outdir_img2img_samples, + outpath_grids=opts.outdir_grids or opts.outdir_img2img_grids, + prompt=args.prompt, + negative_prompt=args.n_prompt, + styles=args.prompt_styles, + seed=args.seed, + subseed=args.subseed, + subseed_strength=args.subseed_strength, + seed_resize_from_h=args.seed_resize_from_h, + seed_resize_from_w=args.seed_resize_from_w, + seed_enable_extras=args.seed_enable_extras, + sampler_name=sd_samplers.samplers_for_img2img[args.sampler_index].name, + batch_size=args.batch_size, + n_iter=args.n_iter, + steps=args.steps, + cfg_scale=args.cfg_scale, + width=args.width, + height=args.height, + restore_faces=args.restore_faces, + tiling=args.tiling, + init_images=[image], + mask=mask, + mask_blur=args.mask_blur, + inpainting_fill=args.inpainting_fill, + resize_mode=args.resize_mode, + denoising_strength=args.denoising_strength, + image_cfg_scale=args.image_cfg_scale, + inpaint_full_res=args.inpaint_full_res, + inpaint_full_res_padding=args.inpaint_full_res_padding, + inpainting_mask_invert=args.inpainting_mask_invert, + override_settings=override_settings, + ) + + p.scripts = modules.scripts.scripts_img2img + p.script_args = args.script_inputs + + #if shared.cmd_opts.enable_console_prompts: + # print(f"\nimg2img: {args.prompt}", file=shared.progress_print_out) + + if mask: + p.extra_generation_params["Mask blur"] = args.mask_blur + + ''' + if is_batch: + ... + # assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" + # process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args.script_inputs) + # processed = Processed(p, [], p.seed, "") + else: + processed = modules.scripts.scripts_img2img.run(p, *args.script_inputs) + if processed is None: + processed = process_images(p) + ''' + + generated_images = process_img(p, image, None, '', args.script_inputs) + processed = Processed(p, [], p.seed, "") + p.close() + + shared.total_tqdm.clear() + + generation_info_js = processed.js() + #if opts.samples_log_stdout: + # print(generation_info_js) + + #if opts.do_not_show_images: + # processed.images = [] + + #print(generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments)) + return generated_images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments) + +def txt2img(args_dict): + args = SimpleNamespace(**args_dict) + override_settings = create_override_settings_dict(args.override_settings) + + p = StableDiffusionProcessingTxt2Img( + sd_model=shared.sd_model, + outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples, + outpath_grids=opts.outdir_grids or opts.outdir_txt2img_grids, + prompt=args.prompt, + styles=args.prompt_styles, + negative_prompt=args.n_prompt, + seed=args.seed, + subseed=args.subseed, + subseed_strength=args.subseed_strength, + seed_resize_from_h=args.seed_resize_from_h, + seed_resize_from_w=args.seed_resize_from_w, + seed_enable_extras=args.seed_enable_extras, + sampler_name=sd_samplers.samplers[args.sampler_index].name, + batch_size=args.batch_size, + n_iter=args.n_iter, + steps=args.steps, + cfg_scale=args.cfg_scale, + width=args.width, + height=args.height, + restore_faces=args.restore_faces, + tiling=args.tiling, + #enable_hr=args.enable_hr, + #denoising_strength=args.denoising_strength if enable_hr else None, + #hr_scale=hr_scale, + #hr_upscaler=hr_upscaler, + #hr_second_pass_steps=hr_second_pass_steps, + #hr_resize_x=hr_resize_x, + #hr_resize_y=hr_resize_y, + override_settings=override_settings, + ) + + p.scripts = modules.scripts.scripts_txt2img + p.script_args = args.script_inputs + + #if cmd_opts.enable_console_prompts: + # print(f"\ntxt2img: {prompt}", file=shared.progress_print_out) + + processed = modules.scripts.scripts_txt2img.run(p, *args.script_inputs) + + if processed is None: + processed = process_images(p) + + p.close() + + shared.total_tqdm.clear() + + generation_info_js = processed.js() + #if opts.samples_log_stdout: + # print(generation_info_js) + + #if opts.do_not_show_images: + # processed.images = [] + + return processed.images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments) + + +import json +def get_json(obj): + return json.loads( + json.dumps(obj, default=lambda o: getattr(o, '__dict__', str(o))) + ) + +def export_settings(*args): + args_dict = args_to_dict(*args) + if args[0] == 'vid2vid': + args_dict = get_mode_args('v2v', args_dict) + elif args[0] == 'txt2vid': + args_dict = get_mode_args('t2v', args_dict) + else: + msg = f"Unsupported processing mode: '{args[0]}'" + raise Exception(msg) + + # convert CN params into a readable dict + cn_remove_list = ['low_vram', 'is_ui', 'input_mode', 'batch_images', 'output_dir', 'loopback', 'image'] + + args_dict['ControlNets'] = [] + for script_input in args_dict['script_inputs']: + if type(script_input).__name__ == 'UiControlNetUnit': + cn_values_dict = get_json(script_input) + if cn_values_dict['enabled']: + for key in cn_remove_list: + if key in cn_values_dict: del cn_values_dict[key] + args_dict['ControlNets'].append(cn_values_dict) + + # remove unimportant values + remove_list = ['save_frames_check', 'restore_faces', 'prompt_styles', 'mask_blur', 'inpainting_fill', 'tiling', 'n_iter', 'batch_size', 'subseed', 'subseed_strength', 'seed_resize_from_h', \ + 'seed_resize_from_w', 'seed_enable_extras', 'resize_mode', 'inpaint_full_res', 'inpaint_full_res_padding', 'inpainting_mask_invert', 'file', 'denoising_strength', \ + 'override_settings', 'script_inputs', 'init_img', 'mask_img', 'mode', 'init_video'] + + for key in remove_list: + if key in args_dict: del args_dict[key] + + return json.dumps(args_dict, indent=2, default=lambda o: getattr(o, '__dict__', str(o))) \ No newline at end of file diff --git a/SD-CN-Animation/scripts/core/vid2vid.py b/SD-CN-Animation/scripts/core/vid2vid.py new file mode 100644 index 0000000000000000000000000000000000000000..207af3060c55fe077bf1814a31c1c715fb3e23eb --- /dev/null +++ b/SD-CN-Animation/scripts/core/vid2vid.py @@ -0,0 +1,270 @@ +import sys, os + +import math +import os +import sys +import traceback + +import numpy as np +from PIL import Image + +from modules import devices, sd_samplers +from modules import shared, sd_hijack, lowvram + +from modules.shared import devices +import modules.shared as shared + +import gc +import cv2 +import gradio as gr + +import time +import skimage +import datetime + +from scripts.core.flow_utils import RAFT_estimate_flow, RAFT_clear_memory, compute_diff_map +from scripts.core import utils + +class sdcn_anim_tmp: + prepear_counter = 0 + process_counter = 0 + input_video = None + output_video = None + curr_frame = None + prev_frame = None + prev_frame_styled = None + prev_frame_alpha_mask = None + fps = None + total_frames = None + prepared_frames = None + prepared_next_flows = None + prepared_prev_flows = None + frames_prepared = False + +def read_frame_from_video(): + # Reading video file + if sdcn_anim_tmp.input_video.isOpened(): + ret, cur_frame = sdcn_anim_tmp.input_video.read() + if cur_frame is not None: + cur_frame = cv2.cvtColor(cur_frame, cv2.COLOR_BGR2RGB) + else: + cur_frame = None + sdcn_anim_tmp.input_video.release() + + return cur_frame + +def get_cur_stat(): + stat = f'Frames prepared: {sdcn_anim_tmp.prepear_counter + 1} / {sdcn_anim_tmp.total_frames}; ' + stat += f'Frames processed: {sdcn_anim_tmp.process_counter + 1} / {sdcn_anim_tmp.total_frames}; ' + return stat + +def clear_memory_from_sd(): + if shared.sd_model is not None: + sd_hijack.model_hijack.undo_hijack(shared.sd_model) + try: + lowvram.send_everything_to_cpu() + except Exception as e: + ... + del shared.sd_model + shared.sd_model = None + gc.collect() + devices.torch_gc() + +def start_process(*args): + processing_start_time = time.time() + args_dict = utils.args_to_dict(*args) + args_dict = utils.get_mode_args('v2v', args_dict) + + sdcn_anim_tmp.process_counter = 0 + sdcn_anim_tmp.prepear_counter = 0 + + # Open the input video file + sdcn_anim_tmp.input_video = cv2.VideoCapture(args_dict['file'].name) + + # Get useful info from the source video + sdcn_anim_tmp.fps = int(sdcn_anim_tmp.input_video.get(cv2.CAP_PROP_FPS)) + sdcn_anim_tmp.total_frames = int(sdcn_anim_tmp.input_video.get(cv2.CAP_PROP_FRAME_COUNT)) + loop_iterations = (sdcn_anim_tmp.total_frames-1) * 2 + + # Create an output video file with the same fps, width, and height as the input video + output_video_name = f'outputs/sd-cn-animation/vid2vid/{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}.mp4' + output_video_folder = os.path.splitext(output_video_name)[0] + os.makedirs(os.path.dirname(output_video_name), exist_ok=True) + + if args_dict['save_frames_check']: + os.makedirs(output_video_folder, exist_ok=True) + + def save_result_to_image(image, ind): + if args_dict['save_frames_check']: + cv2.imwrite(os.path.join(output_video_folder, f'{ind:05d}.png'), cv2.cvtColor(image, cv2.COLOR_RGB2BGR)) + + sdcn_anim_tmp.output_video = cv2.VideoWriter(output_video_name, cv2.VideoWriter_fourcc(*'mp4v'), sdcn_anim_tmp.fps, (args_dict['width'], args_dict['height'])) + + curr_frame = read_frame_from_video() + curr_frame = cv2.resize(curr_frame, (args_dict['width'], args_dict['height'])) + sdcn_anim_tmp.prepared_frames = np.zeros((11, args_dict['height'], args_dict['width'], 3), dtype=np.uint8) + sdcn_anim_tmp.prepared_next_flows = np.zeros((10, args_dict['height'], args_dict['width'], 2)) + sdcn_anim_tmp.prepared_prev_flows = np.zeros((10, args_dict['height'], args_dict['width'], 2)) + sdcn_anim_tmp.prepared_frames[0] = curr_frame + + args_dict['init_img'] = Image.fromarray(curr_frame) + utils.set_CNs_input_image(args_dict, Image.fromarray(curr_frame)) + processed_frames, _, _, _ = utils.img2img(args_dict) + processed_frame = np.array(processed_frames[0])[...,:3] + processed_frame = skimage.exposure.match_histograms(processed_frame, curr_frame, channel_axis=None) + processed_frame = np.clip(processed_frame, 0, 255).astype(np.uint8) + #print('Processed frame ', 0) + + sdcn_anim_tmp.curr_frame = curr_frame + sdcn_anim_tmp.prev_frame = curr_frame.copy() + sdcn_anim_tmp.prev_frame_styled = processed_frame.copy() + utils.shared.is_interrupted = False + + save_result_to_image(processed_frame, 1) + stat = get_cur_stat() + utils.get_time_left(1, loop_iterations, processing_start_time) + yield stat, sdcn_anim_tmp.curr_frame, None, None, processed_frame, None, gr.Button.update(interactive=False), gr.Button.update(interactive=True) + + for step in range(loop_iterations): + if utils.shared.is_interrupted: break + + args_dict = utils.args_to_dict(*args) + args_dict = utils.get_mode_args('v2v', args_dict) + + occlusion_mask = None + prev_frame = None + curr_frame = sdcn_anim_tmp.curr_frame + warped_styled_frame_ = gr.Image.update() + processed_frame = gr.Image.update() + + prepare_steps = 10 + if sdcn_anim_tmp.process_counter % prepare_steps == 0 and not sdcn_anim_tmp.frames_prepared: # prepare next 10 frames for processing + #clear_memory_from_sd() + device = devices.get_optimal_device() + + curr_frame = read_frame_from_video() + if curr_frame is not None: + curr_frame = cv2.resize(curr_frame, (args_dict['width'], args_dict['height'])) + prev_frame = sdcn_anim_tmp.prev_frame.copy() + + next_flow, prev_flow, occlusion_mask = RAFT_estimate_flow(prev_frame, curr_frame, device=device) + occlusion_mask = np.clip(occlusion_mask * 0.1 * 255, 0, 255).astype(np.uint8) + + cn = sdcn_anim_tmp.prepear_counter % 10 + if sdcn_anim_tmp.prepear_counter % 10 == 0: + sdcn_anim_tmp.prepared_frames[cn] = sdcn_anim_tmp.prev_frame + sdcn_anim_tmp.prepared_frames[cn + 1] = curr_frame.copy() + sdcn_anim_tmp.prepared_next_flows[cn] = next_flow.copy() + sdcn_anim_tmp.prepared_prev_flows[cn] = prev_flow.copy() + #print('Prepared frame ', cn+1) + + sdcn_anim_tmp.prev_frame = curr_frame.copy() + + sdcn_anim_tmp.prepear_counter += 1 + if sdcn_anim_tmp.prepear_counter % prepare_steps == 0 or \ + sdcn_anim_tmp.prepear_counter >= sdcn_anim_tmp.total_frames - 1 or \ + curr_frame is None: + # Remove RAFT from memory + RAFT_clear_memory() + sdcn_anim_tmp.frames_prepared = True + else: + # process frame + sdcn_anim_tmp.frames_prepared = False + + cn = sdcn_anim_tmp.process_counter % 10 + curr_frame = sdcn_anim_tmp.prepared_frames[cn+1][...,:3] + prev_frame = sdcn_anim_tmp.prepared_frames[cn][...,:3] + next_flow = sdcn_anim_tmp.prepared_next_flows[cn] + prev_flow = sdcn_anim_tmp.prepared_prev_flows[cn] + + ### STEP 1 + alpha_mask, warped_styled_frame = compute_diff_map(next_flow, prev_flow, prev_frame, curr_frame, sdcn_anim_tmp.prev_frame_styled, args_dict) + warped_styled_frame_ = warped_styled_frame.copy() + + #fl_w, fl_h = prev_flow.shape[:2] + #prev_flow_n = prev_flow / np.array([fl_h,fl_w]) + #flow_mask = np.clip(1 - np.linalg.norm(prev_flow_n, axis=-1)[...,None] * 20, 0, 1) + #alpha_mask = alpha_mask * flow_mask + + if sdcn_anim_tmp.process_counter > 0 and args_dict['occlusion_mask_trailing']: + alpha_mask = alpha_mask + sdcn_anim_tmp.prev_frame_alpha_mask * 0.5 + sdcn_anim_tmp.prev_frame_alpha_mask = alpha_mask + + # alpha_mask = np.round(alpha_mask * 8) / 8 #> 0.3 + alpha_mask = np.clip(alpha_mask, 0, 1) + occlusion_mask = np.clip(alpha_mask * 255, 0, 255).astype(np.uint8) + + # fix warped styled frame from duplicated that occures on the places where flow is zero, but only because there is no place to get the color from + warped_styled_frame = curr_frame.astype(float) * alpha_mask + warped_styled_frame.astype(float) * (1 - alpha_mask) + + # process current frame + # TODO: convert args_dict into separate dict that stores only params necessery for img2img processing + img2img_args_dict = args_dict #copy.deepcopy(args_dict) + img2img_args_dict['denoising_strength'] = args_dict['processing_strength'] + if args_dict['step_1_processing_mode'] == 0: # Process full image then blend in occlusions + img2img_args_dict['mode'] = 0 + img2img_args_dict['mask_img'] = None #Image.fromarray(occlusion_mask) + elif args_dict['step_1_processing_mode'] == 1: # Inpaint occlusions + img2img_args_dict['mode'] = 4 + img2img_args_dict['mask_img'] = Image.fromarray(occlusion_mask) + else: + raise Exception('Incorrect step 1 processing mode!') + + blend_alpha = args_dict['step_1_blend_alpha'] + init_img = warped_styled_frame * (1 - blend_alpha) + curr_frame * blend_alpha + img2img_args_dict['init_img'] = Image.fromarray(np.clip(init_img, 0, 255).astype(np.uint8)) + img2img_args_dict['seed'] = args_dict['step_1_seed'] + utils.set_CNs_input_image(img2img_args_dict, Image.fromarray(curr_frame)) + processed_frames, _, _, _ = utils.img2img(img2img_args_dict) + processed_frame = np.array(processed_frames[0])[...,:3] + + # normalizing the colors + processed_frame = skimage.exposure.match_histograms(processed_frame, curr_frame, channel_axis=None) + processed_frame = processed_frame.astype(float) * alpha_mask + warped_styled_frame.astype(float) * (1 - alpha_mask) + + #processed_frame = processed_frame * 0.94 + curr_frame * 0.06 + processed_frame = np.clip(processed_frame, 0, 255).astype(np.uint8) + sdcn_anim_tmp.prev_frame_styled = processed_frame.copy() + + ### STEP 2 + if args_dict['fix_frame_strength'] > 0: + img2img_args_dict = args_dict #copy.deepcopy(args_dict) + img2img_args_dict['mode'] = 0 + img2img_args_dict['init_img'] = Image.fromarray(processed_frame) + img2img_args_dict['mask_img'] = None + img2img_args_dict['denoising_strength'] = args_dict['fix_frame_strength'] + img2img_args_dict['seed'] = args_dict['step_2_seed'] + utils.set_CNs_input_image(img2img_args_dict, Image.fromarray(curr_frame)) + processed_frames, _, _, _ = utils.img2img(img2img_args_dict) + processed_frame = np.array(processed_frames[0]) + processed_frame = skimage.exposure.match_histograms(processed_frame, curr_frame, channel_axis=None) + + processed_frame = np.clip(processed_frame, 0, 255).astype(np.uint8) + warped_styled_frame_ = np.clip(warped_styled_frame_, 0, 255).astype(np.uint8) + + # Write the frame to the output video + frame_out = np.clip(processed_frame, 0, 255).astype(np.uint8) + frame_out = cv2.cvtColor(frame_out, cv2.COLOR_RGB2BGR) + sdcn_anim_tmp.output_video.write(frame_out) + + sdcn_anim_tmp.process_counter += 1 + #if sdcn_anim_tmp.process_counter >= sdcn_anim_tmp.total_frames - 1: + # sdcn_anim_tmp.input_video.release() + # sdcn_anim_tmp.output_video.release() + # sdcn_anim_tmp.prev_frame = None + + save_result_to_image(processed_frame, sdcn_anim_tmp.process_counter + 1) + + stat = get_cur_stat() + utils.get_time_left(step+2, loop_iterations+1, processing_start_time) + yield stat, curr_frame, occlusion_mask, warped_styled_frame_, processed_frame, None, gr.Button.update(interactive=False), gr.Button.update(interactive=True) + + RAFT_clear_memory() + + sdcn_anim_tmp.input_video.release() + sdcn_anim_tmp.output_video.release() + + curr_frame = gr.Image.update() + occlusion_mask = gr.Image.update() + warped_styled_frame_ = gr.Image.update() + processed_frame = gr.Image.update() + + yield get_cur_stat(), curr_frame, occlusion_mask, warped_styled_frame_, processed_frame, output_video_name, gr.Button.update(interactive=True), gr.Button.update(interactive=False) \ No newline at end of file diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc index 8316470304baee4ca3aa0d394a7b7aaecd8c5ce9..c03a5c92e2729d072a7ae0fe128e02c0dbb01459 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/__pycache__/civitai_helper.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc index cfb6b12c7083a8d9d01adc8682fe32b0365f9ee8..07ec1abe650a43258f58c2ae3707e3a46f9e9240 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/__init__.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc index 13995b18a1267d7658f2810ab786239d7a9a0adc..2974b8625bc653b45653f9b992f3743019725583 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/civitai.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc index 15e341adcbe9817d29487951bbac3b6b50c05b4e..39b5640c2db7d56212d8987c33a1e8aa5bf1fa1a 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/downloader.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc index d3b92da0f0bf94fa7501346089c0b608bbf20fd7..10d1d6220272dd54fc412947ab9f16e5a6aa953b 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/js_action_civitai.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc index 64a8bc356d1b637591db984eedb23a56f283be1a..2da86538961480f3f82db218731fe75e31b4dbeb 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc index 585e89c9766512532f6e3b97f3769699ae53e96c..874f44b246a3e07a7c86779f7723e6ccc164a04a 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/model_action_civitai.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc index f60d0880a4a335b82a468046462da3b5d337ab9d..d66e8ec9a7a578fcb2c7ea5be5067c545816b1a6 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/msg_handler.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc index aeffd25b4ff90c26e1a8c03d83a9fcc8a5bf0bb0..0ff628599d4395ce388fab1fa2d3317b971ad69d 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/setting.cpython-310.pyc differ diff --git a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc index b72479dcd78a5f9ea5f0d7095e5ada5bb71c4f46..5d293ef350dca51fdeb9c1bc64b935f54b6d85c6 100644 Binary files a/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc and b/Stable-Diffusion-Webui-Civitai-Helper/scripts/ch_lib/__pycache__/util.cpython-310.pyc differ diff --git a/a1111-microsoftexcel-locon/__pycache__/locon.cpython-310.pyc b/a1111-microsoftexcel-locon/__pycache__/locon.cpython-310.pyc index a525aa3417009759447129e49b24c25865156fa2..ea30789fd61784290a62a26e3d46f82966d1d23f 100644 Binary files a/a1111-microsoftexcel-locon/__pycache__/locon.cpython-310.pyc and b/a1111-microsoftexcel-locon/__pycache__/locon.cpython-310.pyc differ diff --git a/a1111-microsoftexcel-locon/__pycache__/locon_compvis.cpython-310.pyc b/a1111-microsoftexcel-locon/__pycache__/locon_compvis.cpython-310.pyc index d53f1e4e8ccdad42cb73b6f2f383f0f3597bfaa0..e4f2f656fd6cf619ea9be07c424cc23a3b531d6c 100644 Binary files a/a1111-microsoftexcel-locon/__pycache__/locon_compvis.cpython-310.pyc and b/a1111-microsoftexcel-locon/__pycache__/locon_compvis.cpython-310.pyc differ diff --git a/a1111-microsoftexcel-locon/scripts/__pycache__/main.cpython-310.pyc b/a1111-microsoftexcel-locon/scripts/__pycache__/main.cpython-310.pyc index 9e5059aaaafcd822e18eda5213ecb27ffcacf0a7..a35ac65e207266dcc44de55cbdeb1102c8e5bf7f 100644 Binary files a/a1111-microsoftexcel-locon/scripts/__pycache__/main.cpython-310.pyc and b/a1111-microsoftexcel-locon/scripts/__pycache__/main.cpython-310.pyc differ diff --git a/a1111-microsoftexcel-tagcomplete/scripts/__pycache__/tag_autocomplete_helper.cpython-310.pyc b/a1111-microsoftexcel-tagcomplete/scripts/__pycache__/tag_autocomplete_helper.cpython-310.pyc index b24ef70aad6ff07ad5f0bb7cf0dc4f272b6d66bc..76d7b78a6b2191b7cc035ce74df54ca174bc46ca 100644 Binary files a/a1111-microsoftexcel-tagcomplete/scripts/__pycache__/tag_autocomplete_helper.cpython-310.pyc and b/a1111-microsoftexcel-tagcomplete/scripts/__pycache__/tag_autocomplete_helper.cpython-310.pyc differ diff --git a/a1111-sd-webui-lycoris/__pycache__/extra_networks_lyco.cpython-310.pyc b/a1111-sd-webui-lycoris/__pycache__/extra_networks_lyco.cpython-310.pyc index 335cfe421ff99914b105fb5687c935d0dfe25022..478097c25a01340e7338f2a0b01949a63dfd6b40 100644 Binary files a/a1111-sd-webui-lycoris/__pycache__/extra_networks_lyco.cpython-310.pyc and b/a1111-sd-webui-lycoris/__pycache__/extra_networks_lyco.cpython-310.pyc differ diff --git a/a1111-sd-webui-lycoris/__pycache__/lycoris.cpython-310.pyc b/a1111-sd-webui-lycoris/__pycache__/lycoris.cpython-310.pyc index 382e92a335e1a78e7e8983384283381232672c80..076a3cde6c2c9bacd56454bbc5af118ead15b4a8 100644 Binary files a/a1111-sd-webui-lycoris/__pycache__/lycoris.cpython-310.pyc and b/a1111-sd-webui-lycoris/__pycache__/lycoris.cpython-310.pyc differ diff --git a/a1111-sd-webui-lycoris/__pycache__/preload.cpython-310.pyc b/a1111-sd-webui-lycoris/__pycache__/preload.cpython-310.pyc index 1e87f39a71c464810722cdfd8ca9287e06b92408..881ca86afb24804be4e01d42e41698b03a61c1cb 100644 Binary files a/a1111-sd-webui-lycoris/__pycache__/preload.cpython-310.pyc and b/a1111-sd-webui-lycoris/__pycache__/preload.cpython-310.pyc differ diff --git a/a1111-sd-webui-lycoris/__pycache__/ui_extra_networks_lyco.cpython-310.pyc b/a1111-sd-webui-lycoris/__pycache__/ui_extra_networks_lyco.cpython-310.pyc index 41cf3b3d9a281e5518d9d79d9506d1808ba029f7..856f48497d2952e6d145690bbb314ccc964466c4 100644 Binary files a/a1111-sd-webui-lycoris/__pycache__/ui_extra_networks_lyco.cpython-310.pyc and b/a1111-sd-webui-lycoris/__pycache__/ui_extra_networks_lyco.cpython-310.pyc differ diff --git a/a1111-sd-webui-lycoris/scripts/__pycache__/lycoris_script.cpython-310.pyc b/a1111-sd-webui-lycoris/scripts/__pycache__/lycoris_script.cpython-310.pyc index 663b7e41e345b50fcc720e984936dcef3e8b8c66..16f292b9b1db5cce2dc7af50ab213c84582a0b34 100644 Binary files a/a1111-sd-webui-lycoris/scripts/__pycache__/lycoris_script.cpython-310.pyc and b/a1111-sd-webui-lycoris/scripts/__pycache__/lycoris_script.cpython-310.pyc differ diff --git a/addtional/__pycache__/preload.cpython-310.pyc b/addtional/__pycache__/preload.cpython-310.pyc index 78c6e363e56144aaf1da1fa522533282ac4146b5..675977c28efe739c08ae2ed58e67973230fa211c 100644 Binary files a/addtional/__pycache__/preload.cpython-310.pyc and b/addtional/__pycache__/preload.cpython-310.pyc differ diff --git a/addtional/hashes.json b/addtional/hashes.json index f82170446ed4eb3357922cde1f9419f5803543d9..94e7e79bbe0d19e60b9eb029458405bcc00c87d2 100644 --- a/addtional/hashes.json +++ b/addtional/hashes.json @@ -3,32 +3,37 @@ "/content/microsoftexcel/models/Lora/GoodHands-beta2.safetensors": { "model": "c05ed279295ed06ad8c459254bd9d910d46d1dab4de5dbdb59e9d5a231b8b43f", "legacy": "5275bd79", - "mtime": 1685856032.911875 + "mtime": 1687338891.6372578 }, "/content/microsoftexcel/models/Lora/Caitlyn.safetensors": { "model": "fe2d779164cb23b97a10b926d809fc94d074e1e7deb595be615b8ad11ed5fd11", "legacy": "40fa34e7", - "mtime": 1685856018.1636622 + "mtime": 1687338884.7806156 }, "/content/microsoftexcel/models/Lora/ireliav2-000034.safetensors": { "model": "7fd4ca5cdc40f1d61548e39149b9c0eff6bac17601469abf7de72a42beea245d", "legacy": "d7774c83", - "mtime": 1685856017.3855984 + "mtime": 1687338883.9015331 }, "/content/microsoftexcel/models/Lora/ratatatat74-000030.safetensors": { "model": "43f33c0dd794e5ae4cb4cd1d12d3ad70466c61084c43bf105750790504dc8bdd", "legacy": "e5787969", - "mtime": 1685856021.2959197 + "mtime": 1687338886.6787932 }, "/content/microsoftexcel/models/Lora/nixeu_offset64dim.safetensors": { "model": "3bfe61233b402e3663c136396c3bdea4cfd982215429e9ee83bff505692e6bbe", "legacy": "86bd75dd", - "mtime": 1685856013.7583003 + "mtime": 1687338882.9234416 }, "/content/microsoftexcel/models/Lora/sukuna.safetensors": { "model": "89a59997da996808d64cecae57b23cb788118cfc3c02815faf7a1857c4babcee", "legacy": "f2ac6ed8", - "mtime": 1685856541.630725 + "mtime": 1687338879.7851477 + }, + "/content/microsoftexcel/models/Lora/MakimaCSM.safetensors": { + "model": "0b7b2682d8a6c06208d62aab8534af8e1251dbc2d8a66962df80455a8083fad9", + "legacy": "11d289ef", + "mtime": 1687344071.8123095 } } } \ No newline at end of file diff --git a/addtional/scripts/__pycache__/additional_networks.cpython-310.pyc b/addtional/scripts/__pycache__/additional_networks.cpython-310.pyc index a3c0f7f3f420b8fcd9ea54d4453f27ccf626d054..5e1f4cea4e6914dd31aba6c5464b0dc8bcbf5d0e 100644 Binary files a/addtional/scripts/__pycache__/additional_networks.cpython-310.pyc and b/addtional/scripts/__pycache__/additional_networks.cpython-310.pyc differ diff --git a/addtional/scripts/__pycache__/addnet_xyz_grid_support.cpython-310.pyc b/addtional/scripts/__pycache__/addnet_xyz_grid_support.cpython-310.pyc index d81f4b307fadc82ef5e89515b7c3ecad0322da73..a4a78298c01a4c51a2754e0949ca75dc6375238a 100644 Binary files a/addtional/scripts/__pycache__/addnet_xyz_grid_support.cpython-310.pyc and b/addtional/scripts/__pycache__/addnet_xyz_grid_support.cpython-310.pyc differ diff --git a/addtional/scripts/__pycache__/lora_compvis.cpython-310.pyc b/addtional/scripts/__pycache__/lora_compvis.cpython-310.pyc index 0dbbc5b19332d9a21baba934aa9ec805c643246b..c1513f3ecc75489ffbc310e586b2977f38e2c161 100644 Binary files a/addtional/scripts/__pycache__/lora_compvis.cpython-310.pyc and b/addtional/scripts/__pycache__/lora_compvis.cpython-310.pyc differ diff --git a/addtional/scripts/__pycache__/metadata_editor.cpython-310.pyc b/addtional/scripts/__pycache__/metadata_editor.cpython-310.pyc index 21579db8d4257223ec43d1adb31f6438cad3eb8d..3409a98a05e5de080816fe71be266dea0c3b4eb9 100644 Binary files a/addtional/scripts/__pycache__/metadata_editor.cpython-310.pyc and b/addtional/scripts/__pycache__/metadata_editor.cpython-310.pyc differ diff --git a/addtional/scripts/__pycache__/model_util.cpython-310.pyc b/addtional/scripts/__pycache__/model_util.cpython-310.pyc index 00d97a439c62b045a56ab4f78aaad066f80fb918..d342f679e9074fa7ae30bf0e3a72a2cbba367190 100644 Binary files a/addtional/scripts/__pycache__/model_util.cpython-310.pyc and b/addtional/scripts/__pycache__/model_util.cpython-310.pyc differ diff --git a/addtional/scripts/__pycache__/safetensors_hack.cpython-310.pyc b/addtional/scripts/__pycache__/safetensors_hack.cpython-310.pyc index cfcaab8342ca911eb6cf9f1a0e7351f0ef184d37..08ff26b7b5e1fe3cfcef03db80f46115bee6fb89 100644 Binary files a/addtional/scripts/__pycache__/safetensors_hack.cpython-310.pyc and b/addtional/scripts/__pycache__/safetensors_hack.cpython-310.pyc differ diff --git a/addtional/scripts/__pycache__/util.cpython-310.pyc b/addtional/scripts/__pycache__/util.cpython-310.pyc index 05b6642240f5992e1d876abbffc973b9f4553bad..40e5072d1c40d0c4892c7a323c4609ebefe56a5f 100644 Binary files a/addtional/scripts/__pycache__/util.cpython-310.pyc and b/addtional/scripts/__pycache__/util.cpython-310.pyc differ diff --git a/adetailer/__pycache__/preload.cpython-310.pyc b/adetailer/__pycache__/preload.cpython-310.pyc index 201f39ea9460190cb6ce6bdc69b4f601986412ea..ee25d5a0c988c8e2699fa2aef46383d52e889722 100644 Binary files a/adetailer/__pycache__/preload.cpython-310.pyc and b/adetailer/__pycache__/preload.cpython-310.pyc differ diff --git a/adetailer/adetailer/__pycache__/__init__.cpython-310.pyc b/adetailer/adetailer/__pycache__/__init__.cpython-310.pyc index 586a5a8bdb533733458914f4632509a490cba63c..8afa6c64bfbbbf7df54981a65db1c1aa44bfe12d 100644 Binary files a/adetailer/adetailer/__pycache__/__init__.cpython-310.pyc and b/adetailer/adetailer/__pycache__/__init__.cpython-310.pyc differ diff --git a/adetailer/adetailer/__pycache__/__version__.cpython-310.pyc b/adetailer/adetailer/__pycache__/__version__.cpython-310.pyc index 8e1785be7a6805751af401e2984aa0d91825650c..73a4628f80b2e26bd519907d0ea48204b93ebd8e 100644 Binary files a/adetailer/adetailer/__pycache__/__version__.cpython-310.pyc and b/adetailer/adetailer/__pycache__/__version__.cpython-310.pyc differ diff --git a/adetailer/adetailer/__pycache__/args.cpython-310.pyc b/adetailer/adetailer/__pycache__/args.cpython-310.pyc index 035dbc3d6274d3164d1d83a482adfe476eaa0e2f..caad26b4ccf7a0a0e0ed8c3f7b55e4259a6aaab5 100644 Binary files a/adetailer/adetailer/__pycache__/args.cpython-310.pyc and b/adetailer/adetailer/__pycache__/args.cpython-310.pyc differ diff --git a/adetailer/adetailer/__pycache__/common.cpython-310.pyc b/adetailer/adetailer/__pycache__/common.cpython-310.pyc index da551e4891edfc020ba6565efad0baa4f54eb7f2..ad5190aab4a8e1aac3bd1a7a538d19b329f47b25 100644 Binary files a/adetailer/adetailer/__pycache__/common.cpython-310.pyc and b/adetailer/adetailer/__pycache__/common.cpython-310.pyc differ diff --git a/adetailer/adetailer/__pycache__/mask.cpython-310.pyc b/adetailer/adetailer/__pycache__/mask.cpython-310.pyc index f231ab7f8a750b7720a702dc1181b8eb643daeba..14ba8fabfc945971674902395035465bb213309d 100644 Binary files a/adetailer/adetailer/__pycache__/mask.cpython-310.pyc and b/adetailer/adetailer/__pycache__/mask.cpython-310.pyc differ diff --git a/adetailer/adetailer/__pycache__/mediapipe.cpython-310.pyc b/adetailer/adetailer/__pycache__/mediapipe.cpython-310.pyc index 1c1b30f428eafc048d01b47082971de00c00e5db..df5ad8304b49f8dd47892f77b0be85b582e35387 100644 Binary files a/adetailer/adetailer/__pycache__/mediapipe.cpython-310.pyc and b/adetailer/adetailer/__pycache__/mediapipe.cpython-310.pyc differ diff --git a/adetailer/adetailer/__pycache__/ui.cpython-310.pyc b/adetailer/adetailer/__pycache__/ui.cpython-310.pyc index c9965d9985b216885285561cb5079bd60140cf17..09fc685b8858610c6cbd4d7d64a5b6f0c79f6bc3 100644 Binary files a/adetailer/adetailer/__pycache__/ui.cpython-310.pyc and b/adetailer/adetailer/__pycache__/ui.cpython-310.pyc differ diff --git a/adetailer/adetailer/__pycache__/ultralytics.cpython-310.pyc b/adetailer/adetailer/__pycache__/ultralytics.cpython-310.pyc index 749de14d2181eeb90d38e1e0bae57ada37a99ab3..fffc32db43af9feb153cfbb4dc2448471e287704 100644 Binary files a/adetailer/adetailer/__pycache__/ultralytics.cpython-310.pyc and b/adetailer/adetailer/__pycache__/ultralytics.cpython-310.pyc differ diff --git a/adetailer/controlnet_ext/__pycache__/__init__.cpython-310.pyc b/adetailer/controlnet_ext/__pycache__/__init__.cpython-310.pyc index 5acb26b5f0103152d114e0bdf64444ad224fedbd..0df262d94a20fc4fccae1437b0015b0cbb85de65 100644 Binary files a/adetailer/controlnet_ext/__pycache__/__init__.cpython-310.pyc and b/adetailer/controlnet_ext/__pycache__/__init__.cpython-310.pyc differ diff --git a/adetailer/controlnet_ext/__pycache__/controlnet_ext.cpython-310.pyc b/adetailer/controlnet_ext/__pycache__/controlnet_ext.cpython-310.pyc index 71b4d0037cf694aec32262790ca62bec70b1a454..c4204b2ab53ab011113601f9a17c9842d44a08d1 100644 Binary files a/adetailer/controlnet_ext/__pycache__/controlnet_ext.cpython-310.pyc and b/adetailer/controlnet_ext/__pycache__/controlnet_ext.cpython-310.pyc differ diff --git a/adetailer/controlnet_ext/__pycache__/restore.cpython-310.pyc b/adetailer/controlnet_ext/__pycache__/restore.cpython-310.pyc index 36b9ad7df387cb174ccd746cec7aa9e82bc8f04d..5a9ba7be1670c9223581c667123decc5ec0987ec 100644 Binary files a/adetailer/controlnet_ext/__pycache__/restore.cpython-310.pyc and b/adetailer/controlnet_ext/__pycache__/restore.cpython-310.pyc differ diff --git a/adetailer/scripts/__pycache__/!adetailer.cpython-310.pyc b/adetailer/scripts/__pycache__/!adetailer.cpython-310.pyc index 48235e68602de7d421e6e6377f635fa620235e8d..effc23b6fef81cdb0ac920e6d4eb5cc99435b728 100644 Binary files a/adetailer/scripts/__pycache__/!adetailer.cpython-310.pyc and b/adetailer/scripts/__pycache__/!adetailer.cpython-310.pyc differ diff --git a/adetailer/sd_webui/__pycache__/__init__.cpython-310.pyc b/adetailer/sd_webui/__pycache__/__init__.cpython-310.pyc index 8fe564e90c48b4bc687b6bb555ae15744302aed0..549c360e20c61dd371f70b7cd5eafde70edca30a 100644 Binary files a/adetailer/sd_webui/__pycache__/__init__.cpython-310.pyc and b/adetailer/sd_webui/__pycache__/__init__.cpython-310.pyc differ diff --git a/adetailer/sd_webui/__pycache__/images.cpython-310.pyc b/adetailer/sd_webui/__pycache__/images.cpython-310.pyc index 38962c75e1944af09707b376436263427d270029..da639cd7ce3917fdaff44ec950474f8002231497 100644 Binary files a/adetailer/sd_webui/__pycache__/images.cpython-310.pyc and b/adetailer/sd_webui/__pycache__/images.cpython-310.pyc differ diff --git a/adetailer/sd_webui/__pycache__/paths.cpython-310.pyc b/adetailer/sd_webui/__pycache__/paths.cpython-310.pyc index 60bb573d89acfabef4a611df142389a40d8c0dea..3548ab37c35478a2166214ed5f06ae87901be3ab 100644 Binary files a/adetailer/sd_webui/__pycache__/paths.cpython-310.pyc and b/adetailer/sd_webui/__pycache__/paths.cpython-310.pyc differ diff --git a/adetailer/sd_webui/__pycache__/processing.cpython-310.pyc b/adetailer/sd_webui/__pycache__/processing.cpython-310.pyc index ab5e9f8d8ba9553f507785d9c2dd94e6f4f94385..6b6d06a6777ba8711fb8c77fcb0ccdf465477b74 100644 Binary files a/adetailer/sd_webui/__pycache__/processing.cpython-310.pyc and b/adetailer/sd_webui/__pycache__/processing.cpython-310.pyc differ diff --git a/adetailer/sd_webui/__pycache__/safe.cpython-310.pyc b/adetailer/sd_webui/__pycache__/safe.cpython-310.pyc index a0aca9eababe5bf28b3aa70ff7851932a7ab7063..cf2ef8726c3edaabb49f6324da26ea796bf6358a 100644 Binary files a/adetailer/sd_webui/__pycache__/safe.cpython-310.pyc and b/adetailer/sd_webui/__pycache__/safe.cpython-310.pyc differ diff --git a/adetailer/sd_webui/__pycache__/script_callbacks.cpython-310.pyc b/adetailer/sd_webui/__pycache__/script_callbacks.cpython-310.pyc index 7b6b2a0d208dae67c7766d82742b5d0a0304b847..100b6cf9a43e5289c89c04da72aac29f8eadd18a 100644 Binary files a/adetailer/sd_webui/__pycache__/script_callbacks.cpython-310.pyc and b/adetailer/sd_webui/__pycache__/script_callbacks.cpython-310.pyc differ diff --git a/adetailer/sd_webui/__pycache__/scripts.cpython-310.pyc b/adetailer/sd_webui/__pycache__/scripts.cpython-310.pyc index 50cb4acfcde342b3e477d91c927fd05e3062fbf9..9c335331d1e172d1006afe506e869b7d2db9e459 100644 Binary files a/adetailer/sd_webui/__pycache__/scripts.cpython-310.pyc and b/adetailer/sd_webui/__pycache__/scripts.cpython-310.pyc differ diff --git a/adetailer/sd_webui/__pycache__/shared.cpython-310.pyc b/adetailer/sd_webui/__pycache__/shared.cpython-310.pyc index 51e845c10f899c37257b9c8b2be0d9a35b5d4a62..41d8a95682acdffec0121eb56fdb30762bf03136 100644 Binary files a/adetailer/sd_webui/__pycache__/shared.cpython-310.pyc and b/adetailer/sd_webui/__pycache__/shared.cpython-310.pyc differ diff --git a/ebsynth_utility/README.md b/ebsynth_utility/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ac30c03427d56ba94edc51337247904487162d87 --- /dev/null +++ b/ebsynth_utility/README.md @@ -0,0 +1,186 @@ +# ebsynth_utility + +## Overview +#### AUTOMATIC1111 UI extension for creating videos using img2img and ebsynth. +#### This extension allows you to output edited videos using ebsynth.(AE is not required) + + +##### With [Controlnet](https://github.com/Mikubill/sd-webui-controlnet) installed, I have confirmed that all features of this extension are working properly! +##### [Controlnet](https://github.com/Mikubill/sd-webui-controlnet) is a must for video editing, so I recommend installing it. +##### Multi ControlNet("canny" + "normal map") would be suitable for video editing. + +
+ +###### I made a new extension to make videos in a different way than this extension. You can use it if you like. +###### [sd_loopback_music_sync_wave](https://github.com/s9roll7/sd_loopback_music_sync_wave) +
+ +
+ + +## Example +- The following sample is raw output of this extension. +#### sample 1 mask with [clipseg](https://github.com/timojl/clipseg) +- first from left : original +- second from left : masking "cat" exclude "finger" +- third from left : masking "cat head" +- right : color corrected with [color-matcher](https://github.com/hahnec/color-matcher) (see stage 3.5) +- Multiple targets can also be specified.(e.g. cat,dog,boy,girl) +
+ +#### sample 2 blend background +- person : masterpiece, best quality, masterpiece, 1girl, masterpiece, best quality,anime screencap, anime style +- background : cyberpunk, factory, room ,anime screencap, anime style +- It is also possible to blend with your favorite videos. +
+ +#### sample 3 auto tagging +- left : original +- center : apply the same prompts in all keyframes +- right : apply auto tagging by deepdanbooru in all keyframes +- This function improves the detailed changes in facial expressions, hand expressions, etc. + In the sample video, the "closed_eyes" and "hands_on_own_face" tags have been added to better represent eye blinks and hands brought in front of the face. +
+ +#### sample 4 auto tagging (apply lora dynamically) +- left : apply auto tagging by deepdanbooru in all keyframes +- right : apply auto tagging by deepdanbooru in all keyframes + apply "anyahehface" lora dynamically +- Added the function to dynamically apply TI, hypernet, Lora, and additional prompts according to automatically attached tags. + In the sample video, if the "smile" tag is given, the lora and lora trigger keywords are set to be added according to the strength of the "smile" tag. + Also, since automatically added tags are sometimes incorrect, unnecessary tags are listed in the blacklist. + [Here](sample/) is the actual configuration file used. placed in "Project directory" for use. +
+ +
+ +## Installation +- Install [ffmpeg](https://ffmpeg.org/) for your operating system + (https://www.geeksforgeeks.org/how-to-install-ffmpeg-on-windows/) +- Install [Ebsynth](https://ebsynth.com/) +- Use the Extensions tab of the webui to [Install from URL] + +
+
+ +## Usage +- Go to [Ebsynth Utility] tab. +- Create an empty directory somewhere, and fill in the "Project directory" field. +- Place the video you want to edit from somewhere, and fill in the "Original Movie Path" field. + Use short videos of a few seconds at first. +- Select stage 1 and Generate. +- Execute in order from stage 1 to 7. + Progress during the process is not reflected in webui, so please check the console screen. + If you see "completed." in webui, it is completed. +(In the current latest webui, it seems to cause an error if you do not drop the image on the main screen of img2img. +Please drop the image as it does not affect the result.) + +
+
+ +## Note 1 +For reference, here's what I did when I edited a 1280x720 30fps 15sec video based on +#### Stage 1 +There is nothing to configure. +All frames of the video and mask images for all frames are generated. + +#### Stage 2 +In the implementation of this extension, the keyframe interval is chosen to be shorter where there is a lot of motion and longer where there is little motion. +If the animation breaks up, increase the keyframe, if it flickers, decrease the keyframe. +First, generate one time with the default settings and go straight ahead without worrying about the result. + + +#### Stage 3 +Select one of the keyframes, throw it to img2img, and run [Interrogate DeepBooru]. +Delete unwanted words such as blur from the displayed prompt. +Fill in the rest of the settings as you would normally do for image generation. + +Here is the settings I used. +- Sampling method : Euler a +- Sampling Steps : 50 +- Width : 960 +- Height : 512 +- CFG Scale : 20 +- Denoising strength : 0.2 + +Here is the settings for extension. +- Mask Mode(Override img2img Mask mode) : Normal +- Img2Img Repeat Count (Loop Back) : 5 +- Add N to seed when repeating : 1 +- use Face Crop img2img : True +- Face Detection Method : YuNet +- Max Crop Size : 1024 +- Face Denoising Strength : 0.25 +- Face Area Magnification : 1.5 (The larger the number, the closer to the model's painting style, but the more likely it is to shift when merged with the body.) +- Enable Face Prompt : False + +Trial and error in this process is the most time-consuming part. +Monitor the destination folder and if you do not like results, interrupt and change the settings. +[Prompt][Denoising strength] and [Face Denoising Strength] settings when using Face Crop img2img will greatly affect the result. +For more information on Face Crop img2img, check [here](https://github.com/s9roll7/face_crop_img2img) + +If you have lots of memory to spare, increasing the width and height values while maintaining the aspect ratio may greatly improve results. + +This extension may help with the adjustment. +https://github.com/s9roll7/img2img_for_all_method + +
+ +**The information above is from a time when there was no controlnet. +When controlnet are used together (especially multi-controlnets), +Even setting "Denoising strength" to a high value works well, and even setting it to 1.0 produces meaningful results. +If "Denoising strength" is set to a high value, "Loop Back" can be set to 1.** + +
+ +#### Stage 4 +Scale it up or down and process it to exactly the same size as the original video. +This process should only need to be done once. + +- Width : 1280 +- Height : 720 +- Upscaler 1 : R-ESRGAN 4x+ +- Upscaler 2 : R-ESRGAN 4x+ Anime6B +- Upscaler 2 visibility : 0.5 +- GFPGAN visibility : 1 +- CodeFormer visibility : 0 +- CodeFormer weight : 0 + +#### Stage 5 +There is nothing to configure. +.ebs file will be generated. + +#### Stage 6 +Run the .ebs file. +I wouldn't change the settings, but you could adjust the .ebs settings. + +#### Stage 7 +Finally, output the video. +In my case, the entire process from 1 to 7 took about 30 minutes. + +- Crossfade blend rate : 1.0 +- Export type : mp4 + +
+
+ +## Note 2 : How to use multi-controlnet together +#### in webui setting +![controlnet_setting](imgs/controlnet_setting.png "controlnet_setting") +
+#### In controlnet settings in img2img tab(for controlnet 0) +![controlnet_0](imgs/controlnet_0.png "controlnet_0") +
+#### In controlnet settings in img2img tab(for controlnet 1) +![controlnet_1](imgs/controlnet_1.png "controlnet_1") +
+#### In ebsynth_utility settings in img2img tab +**Warning : "Weight" in the controlnet settings is overridden by the following values** +![controlnet_option_in_ebsynthutil](imgs/controlnet_option_in_ebsynthutil.png "controlnet_option_in_ebsynthutil") + +
+
+ +## Note 3 : How to use clipseg +![clipseg](imgs/clipseg.png "How to use clipseg") + + diff --git a/ebsynth_utility/__pycache__/calculator.cpython-310.pyc b/ebsynth_utility/__pycache__/calculator.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8788e77987e2f9a1299209c776354e1a78a87946 Binary files /dev/null and b/ebsynth_utility/__pycache__/calculator.cpython-310.pyc differ diff --git a/ebsynth_utility/__pycache__/ebsynth_utility.cpython-310.pyc b/ebsynth_utility/__pycache__/ebsynth_utility.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50bf5e597ab311617bbfc65b60399dc2d98237aa Binary files /dev/null and b/ebsynth_utility/__pycache__/ebsynth_utility.cpython-310.pyc differ diff --git a/ebsynth_utility/__pycache__/stage1.cpython-310.pyc b/ebsynth_utility/__pycache__/stage1.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1e9adf226c3a7911d248e6d0c9c1a15be6b33b1 Binary files /dev/null and b/ebsynth_utility/__pycache__/stage1.cpython-310.pyc differ diff --git a/ebsynth_utility/__pycache__/stage2.cpython-310.pyc b/ebsynth_utility/__pycache__/stage2.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e88f3a62e6b48ed7f296ac950c5bcb64e3af66d Binary files /dev/null and b/ebsynth_utility/__pycache__/stage2.cpython-310.pyc differ diff --git a/ebsynth_utility/__pycache__/stage3_5.cpython-310.pyc b/ebsynth_utility/__pycache__/stage3_5.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1aaaef3a875b073ed072f9b9169767120d19c6f2 Binary files /dev/null and b/ebsynth_utility/__pycache__/stage3_5.cpython-310.pyc differ diff --git a/ebsynth_utility/__pycache__/stage5.cpython-310.pyc b/ebsynth_utility/__pycache__/stage5.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e3e09360fb6dd6c8c773cb6bfd304d051b5a9c5 Binary files /dev/null and b/ebsynth_utility/__pycache__/stage5.cpython-310.pyc differ diff --git a/ebsynth_utility/__pycache__/stage7.cpython-310.pyc b/ebsynth_utility/__pycache__/stage7.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26bde6ff258cff9f5d62d2de8c94a0455efb6d84 Binary files /dev/null and b/ebsynth_utility/__pycache__/stage7.cpython-310.pyc differ diff --git a/ebsynth_utility/__pycache__/stage8.cpython-310.pyc b/ebsynth_utility/__pycache__/stage8.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68c10f76529cd81c68d422ddaa1961f30023c70f Binary files /dev/null and b/ebsynth_utility/__pycache__/stage8.cpython-310.pyc differ diff --git a/ebsynth_utility/calculator.py b/ebsynth_utility/calculator.py new file mode 100644 index 0000000000000000000000000000000000000000..35d66bfe960d86b0a56ce207cca3725f303a2da9 --- /dev/null +++ b/ebsynth_utility/calculator.py @@ -0,0 +1,237 @@ +# https://www.mycompiler.io/view/3TFZagC + +class ParseError(Exception): + def __init__(self, pos, msg, *args): + self.pos = pos + self.msg = msg + self.args = args + + def __str__(self): + return '%s at position %s' % (self.msg % self.args, self.pos) + +class Parser: + def __init__(self): + self.cache = {} + + def parse(self, text): + self.text = text + self.pos = -1 + self.len = len(text) - 1 + rv = self.start() + self.assert_end() + return rv + + def assert_end(self): + if self.pos < self.len: + raise ParseError( + self.pos + 1, + 'Expected end of string but got %s', + self.text[self.pos + 1] + ) + + def eat_whitespace(self): + while self.pos < self.len and self.text[self.pos + 1] in " \f\v\r\t\n": + self.pos += 1 + + def split_char_ranges(self, chars): + try: + return self.cache[chars] + except KeyError: + pass + + rv = [] + index = 0 + length = len(chars) + + while index < length: + if index + 2 < length and chars[index + 1] == '-': + if chars[index] >= chars[index + 2]: + raise ValueError('Bad character range') + + rv.append(chars[index:index + 3]) + index += 3 + else: + rv.append(chars[index]) + index += 1 + + self.cache[chars] = rv + return rv + + def char(self, chars=None): + if self.pos >= self.len: + raise ParseError( + self.pos + 1, + 'Expected %s but got end of string', + 'character' if chars is None else '[%s]' % chars + ) + + next_char = self.text[self.pos + 1] + if chars == None: + self.pos += 1 + return next_char + + for char_range in self.split_char_ranges(chars): + if len(char_range) == 1: + if next_char == char_range: + self.pos += 1 + return next_char + elif char_range[0] <= next_char <= char_range[2]: + self.pos += 1 + return next_char + + raise ParseError( + self.pos + 1, + 'Expected %s but got %s', + 'character' if chars is None else '[%s]' % chars, + next_char + ) + + def keyword(self, *keywords): + self.eat_whitespace() + if self.pos >= self.len: + raise ParseError( + self.pos + 1, + 'Expected %s but got end of string', + ','.join(keywords) + ) + + for keyword in keywords: + low = self.pos + 1 + high = low + len(keyword) + + if self.text[low:high] == keyword: + self.pos += len(keyword) + self.eat_whitespace() + return keyword + + raise ParseError( + self.pos + 1, + 'Expected %s but got %s', + ','.join(keywords), + self.text[self.pos + 1], + ) + + def match(self, *rules): + self.eat_whitespace() + last_error_pos = -1 + last_exception = None + last_error_rules = [] + + for rule in rules: + initial_pos = self.pos + try: + rv = getattr(self, rule)() + self.eat_whitespace() + return rv + except ParseError as e: + self.pos = initial_pos + + if e.pos > last_error_pos: + last_exception = e + last_error_pos = e.pos + last_error_rules.clear() + last_error_rules.append(rule) + elif e.pos == last_error_pos: + last_error_rules.append(rule) + + if len(last_error_rules) == 1: + raise last_exception + else: + raise ParseError( + last_error_pos, + 'Expected %s but got %s', + ','.join(last_error_rules), + self.text[last_error_pos] + ) + + def maybe_char(self, chars=None): + try: + return self.char(chars) + except ParseError: + return None + + def maybe_match(self, *rules): + try: + return self.match(*rules) + except ParseError: + return None + + def maybe_keyword(self, *keywords): + try: + return self.keyword(*keywords) + except ParseError: + return None + +class CalcParser(Parser): + def start(self): + return self.expression() + + def expression(self): + rv = self.match('term') + while True: + op = self.maybe_keyword('+', '-') + if op is None: + break + + term = self.match('term') + if op == '+': + rv += term + else: + rv -= term + + return rv + + def term(self): + rv = self.match('factor') + while True: + op = self.maybe_keyword('*', '/') + if op is None: + break + + term = self.match('factor') + if op == '*': + rv *= term + else: + rv /= term + + return rv + + def factor(self): + if self.maybe_keyword('('): + rv = self.match('expression') + self.keyword(')') + + return rv + + return self.match('number') + + def number(self): + chars = [] + + sign = self.maybe_keyword('+', '-') + if sign is not None: + chars.append(sign) + + chars.append(self.char('0-9')) + + while True: + char = self.maybe_char('0-9') + if char is None: + break + + chars.append(char) + + if self.maybe_char('.'): + chars.append('.') + chars.append(self.char('0-9')) + + while True: + char = self.maybe_char('0-9') + if char is None: + break + + chars.append(char) + + rv = float(''.join(chars)) + return rv + diff --git a/ebsynth_utility/ebsynth_utility.py b/ebsynth_utility/ebsynth_utility.py new file mode 100644 index 0000000000000000000000000000000000000000..68c7466a81389c88f968086b58f14a6eb05f4870 --- /dev/null +++ b/ebsynth_utility/ebsynth_utility.py @@ -0,0 +1,185 @@ +import os + +from modules.ui import plaintext_to_html + +import cv2 +import glob +from PIL import Image + +from extensions.ebsynth_utility.stage1 import ebsynth_utility_stage1,ebsynth_utility_stage1_invert +from extensions.ebsynth_utility.stage2 import ebsynth_utility_stage2 +from extensions.ebsynth_utility.stage5 import ebsynth_utility_stage5 +from extensions.ebsynth_utility.stage7 import ebsynth_utility_stage7 +from extensions.ebsynth_utility.stage8 import ebsynth_utility_stage8 +from extensions.ebsynth_utility.stage3_5 import ebsynth_utility_stage3_5 + + +def x_ceiling(value, step): + return -(-value // step) * step + +def dump_dict(string, d:dict): + for key in d.keys(): + string += ( key + " : " + str(d[key]) + "\n") + return string + +class debug_string: + txt = "" + def print(self, comment): + print(comment) + self.txt += comment + '\n' + def to_string(self): + return self.txt + +def ebsynth_utility_process(stage_index: int, project_dir:str, original_movie_path:str, frame_width:int, frame_height:int, st1_masking_method_index:int, st1_mask_threshold:float, tb_use_fast_mode:bool, tb_use_jit:bool, clipseg_mask_prompt:str, clipseg_exclude_prompt:str, clipseg_mask_threshold:int, clipseg_mask_blur_size:int, clipseg_mask_blur_size2:int, key_min_gap:int, key_max_gap:int, key_th:float, key_add_last_frame:bool, color_matcher_method:str, st3_5_use_mask:bool, st3_5_use_mask_ref:bool, st3_5_use_mask_org:bool, color_matcher_ref_type:int, color_matcher_ref_image:Image, blend_rate:float, export_type:str, bg_src:str, bg_type:str, mask_blur_size:int, mask_threshold:float, fg_transparency:float, mask_mode:str): + args = locals() + info = "" + info = dump_dict(info, args) + dbg = debug_string() + + + def process_end(dbg, info): + return plaintext_to_html(dbg.to_string()), plaintext_to_html(info) + + + if not os.path.isdir(project_dir): + dbg.print("{0} project_dir not found".format(project_dir)) + return process_end( dbg, info ) + + if not os.path.isfile(original_movie_path): + dbg.print("{0} original_movie_path not found".format(original_movie_path)) + return process_end( dbg, info ) + + is_invert_mask = False + if mask_mode == "Invert": + is_invert_mask = True + + frame_path = os.path.join(project_dir , "video_frame") + frame_mask_path = os.path.join(project_dir, "video_mask") + + if is_invert_mask: + inv_path = os.path.join(project_dir, "inv") + os.makedirs(inv_path, exist_ok=True) + + org_key_path = os.path.join(inv_path, "video_key") + img2img_key_path = os.path.join(inv_path, "img2img_key") + img2img_upscale_key_path = os.path.join(inv_path, "img2img_upscale_key") + else: + org_key_path = os.path.join(project_dir, "video_key") + img2img_key_path = os.path.join(project_dir, "img2img_key") + img2img_upscale_key_path = os.path.join(project_dir, "img2img_upscale_key") + + if mask_mode == "None": + frame_mask_path = "" + + + project_args = [project_dir, original_movie_path, frame_path, frame_mask_path, org_key_path, img2img_key_path, img2img_upscale_key_path] + + + if stage_index == 0: + ebsynth_utility_stage1(dbg, project_args, frame_width, frame_height, st1_masking_method_index, st1_mask_threshold, tb_use_fast_mode, tb_use_jit, clipseg_mask_prompt, clipseg_exclude_prompt, clipseg_mask_threshold, clipseg_mask_blur_size, clipseg_mask_blur_size2, is_invert_mask) + if is_invert_mask: + inv_mask_path = os.path.join(inv_path, "inv_video_mask") + ebsynth_utility_stage1_invert(dbg, frame_mask_path, inv_mask_path) + + elif stage_index == 1: + ebsynth_utility_stage2(dbg, project_args, key_min_gap, key_max_gap, key_th, key_add_last_frame, is_invert_mask) + elif stage_index == 2: + + sample_image = glob.glob( os.path.join(frame_path , "*.png" ) )[0] + img_height, img_width, _ = cv2.imread(sample_image).shape + if img_width < img_height: + re_w = 512 + re_h = int(x_ceiling( (512 / img_width) * img_height , 64)) + else: + re_w = int(x_ceiling( (512 / img_height) * img_width , 64)) + re_h = 512 + img_width = re_w + img_height = re_h + + dbg.print("stage 3") + dbg.print("") + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + dbg.print("1. Go to img2img tab") + dbg.print("2. Select [ebsynth utility] in the script combo box") + dbg.print("3. Fill in the \"Project directory\" field with [" + project_dir + "]" ) + dbg.print("4. Select in the \"Mask Mode(Override img2img Mask mode)\" field with [" + ("Invert" if is_invert_mask else "Normal") + "]" ) + dbg.print("5. I recommend to fill in the \"Width\" field with [" + str(img_width) + "]" ) + dbg.print("6. I recommend to fill in the \"Height\" field with [" + str(img_height) + "]" ) + dbg.print("7. I recommend to fill in the \"Denoising strength\" field with lower than 0.35" ) + dbg.print(" (When using controlnet together, you can put in large values (even 1.0 is possible).)") + dbg.print("8. Fill in the remaining configuration fields of img2img. No image and mask settings are required.") + dbg.print("9. Drop any image onto the img2img main screen. This is necessary to avoid errors, but does not affect the results of img2img.") + dbg.print("10. Generate") + dbg.print("(Images are output to [" + img2img_key_path + "])") + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + return process_end( dbg, "" ) + + elif stage_index == 3: + ebsynth_utility_stage3_5(dbg, project_args, color_matcher_method, st3_5_use_mask, st3_5_use_mask_ref, st3_5_use_mask_org, color_matcher_ref_type, color_matcher_ref_image) + + elif stage_index == 4: + sample_image = glob.glob( os.path.join(frame_path , "*.png" ) )[0] + img_height, img_width, _ = cv2.imread(sample_image).shape + + sample_img2img_key = glob.glob( os.path.join(img2img_key_path , "*.png" ) )[0] + img_height_key, img_width_key, _ = cv2.imread(sample_img2img_key).shape + + if is_invert_mask: + project_dir = inv_path + + dbg.print("stage 4") + dbg.print("") + + if img_height == img_height_key and img_width == img_width_key: + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + dbg.print("!! The size of frame and img2img_key matched.") + dbg.print("!! You can skip this stage.") + + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + dbg.print("0. Enable the following item") + dbg.print("Settings ->") + dbg.print(" Saving images/grids ->") + dbg.print(" Use original name for output filename during batch process in extras tab") + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + dbg.print("1. If \"img2img_upscale_key\" directory already exists in the %s, delete it manually before executing."%(project_dir)) + dbg.print("2. Go to Extras tab") + dbg.print("3. Go to Batch from Directory tab") + dbg.print("4. Fill in the \"Input directory\" field with [" + img2img_key_path + "]" ) + dbg.print("5. Fill in the \"Output directory\" field with [" + img2img_upscale_key_path + "]" ) + dbg.print("6. Go to Scale to tab") + dbg.print("7. Fill in the \"Width\" field with [" + str(img_width) + "]" ) + dbg.print("8. Fill in the \"Height\" field with [" + str(img_height) + "]" ) + dbg.print("9. Fill in the remaining configuration fields of Upscaler.") + dbg.print("10. Generate") + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + return process_end( dbg, "" ) + elif stage_index == 5: + ebsynth_utility_stage5(dbg, project_args, is_invert_mask) + elif stage_index == 6: + + if is_invert_mask: + project_dir = inv_path + + dbg.print("stage 6") + dbg.print("") + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + dbg.print("Running ebsynth.(on your self)") + dbg.print("Open the generated .ebs under %s and press [Run All] button."%(project_dir)) + dbg.print("If ""out-*"" directory already exists in the %s, delete it manually before executing."%(project_dir)) + dbg.print("If multiple .ebs files are generated, run them all.") + dbg.print("(I recommend associating the .ebs file with EbSynth.exe.)") + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + return process_end( dbg, "" ) + elif stage_index == 7: + ebsynth_utility_stage7(dbg, project_args, blend_rate, export_type, is_invert_mask) + elif stage_index == 8: + if mask_mode != "Normal": + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + dbg.print("Please reset [configuration]->[etc]->[Mask Mode] to Normal.") + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + return process_end( dbg, "" ) + ebsynth_utility_stage8(dbg, project_args, bg_src, bg_type, mask_blur_size, mask_threshold, fg_transparency, export_type) + else: + pass + + return process_end( dbg, info ) diff --git a/ebsynth_utility/imgs/clipseg.png b/ebsynth_utility/imgs/clipseg.png new file mode 100644 index 0000000000000000000000000000000000000000..4e68833759a200695ef09d6e8d08ec724f2b586b Binary files /dev/null and b/ebsynth_utility/imgs/clipseg.png differ diff --git a/ebsynth_utility/imgs/controlnet_0.png b/ebsynth_utility/imgs/controlnet_0.png new file mode 100644 index 0000000000000000000000000000000000000000..7dc7b73e6f9576cee696c1878901048d4d6ff929 Binary files /dev/null and b/ebsynth_utility/imgs/controlnet_0.png differ diff --git a/ebsynth_utility/imgs/controlnet_1.png b/ebsynth_utility/imgs/controlnet_1.png new file mode 100644 index 0000000000000000000000000000000000000000..7560b4f4310424376b5c732608002ee1161bf3fc Binary files /dev/null and b/ebsynth_utility/imgs/controlnet_1.png differ diff --git a/ebsynth_utility/imgs/controlnet_option_in_ebsynthutil.png b/ebsynth_utility/imgs/controlnet_option_in_ebsynthutil.png new file mode 100644 index 0000000000000000000000000000000000000000..0094b752beb0e568321555eafd9db0f948b641fc Binary files /dev/null and b/ebsynth_utility/imgs/controlnet_option_in_ebsynthutil.png differ diff --git a/ebsynth_utility/imgs/controlnet_setting.png b/ebsynth_utility/imgs/controlnet_setting.png new file mode 100644 index 0000000000000000000000000000000000000000..cc53d786afa0d7a09cdd2d87fd0e036cde69dd2e Binary files /dev/null and b/ebsynth_utility/imgs/controlnet_setting.png differ diff --git a/ebsynth_utility/imgs/sample1.mp4 b/ebsynth_utility/imgs/sample1.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..00cad979c7a11aed65e5959aec18ec60f167156c --- /dev/null +++ b/ebsynth_utility/imgs/sample1.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c5458eea82a691a584af26f51d1db728092069a3409c6f9eb2dd14fd2b71173 +size 4824162 diff --git a/ebsynth_utility/imgs/sample2.mp4 b/ebsynth_utility/imgs/sample2.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e429339273350c132c563a1da76d212f9212b4ed --- /dev/null +++ b/ebsynth_utility/imgs/sample2.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537b8331b74d8ea49ee580aed138d460735ef897ab31ca694031d7a56d99ff72 +size 2920523 diff --git a/ebsynth_utility/imgs/sample3.mp4 b/ebsynth_utility/imgs/sample3.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e64e52d5ff97864046e7d3bfe975d74738aafd99 --- /dev/null +++ b/ebsynth_utility/imgs/sample3.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c49739c2ef1f2ecaf14453f463e46b1a05de1688ce22b50200563a03b1758ddf +size 5161880 diff --git a/ebsynth_utility/imgs/sample4.mp4 b/ebsynth_utility/imgs/sample4.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..bb5965b9573d430d27cbc9bcaf1c73c838a1d45c --- /dev/null +++ b/ebsynth_utility/imgs/sample4.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62ed25263b6e5328a49460714c0b6e6ac46759921c87c91850f749f5bf068cfa +size 5617838 diff --git a/ebsynth_utility/imgs/sample5.mp4 b/ebsynth_utility/imgs/sample5.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..670b7c583cdbb35d340282ed3b7cb59d87c91649 --- /dev/null +++ b/ebsynth_utility/imgs/sample5.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e429cfef8b3ed7829ce3219895fcfdbbe94e1494a2e4dcd87988e03509c8d50 +size 4190467 diff --git a/ebsynth_utility/imgs/sample6.mp4 b/ebsynth_utility/imgs/sample6.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..2f485284f2d21bd38c31f6d23b991f856239f08b --- /dev/null +++ b/ebsynth_utility/imgs/sample6.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb9a9ea8662ef1b7fc7151b987006eef8dd3598e320242bd87a2838ac8733df6 +size 6890883 diff --git a/ebsynth_utility/imgs/sample_anyaheh.mp4 b/ebsynth_utility/imgs/sample_anyaheh.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..f8efdac7ecf4fd7888ee3f676dcc4a9afc6d8624 --- /dev/null +++ b/ebsynth_utility/imgs/sample_anyaheh.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4de4e9b9758cefe28c430f909da9dfc086e5b3510e9d0aa7becab7b4be355447 +size 12159686 diff --git a/ebsynth_utility/imgs/sample_autotag.mp4 b/ebsynth_utility/imgs/sample_autotag.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..b461523d5228a8dd1e2ce8c1957578730a3c6045 --- /dev/null +++ b/ebsynth_utility/imgs/sample_autotag.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e904d68fc0fd9ac09ce153a9d54e9f1ce9f8db7cf5e96109c496f7e64924c92 +size 7058129 diff --git a/ebsynth_utility/imgs/sample_clipseg.mp4 b/ebsynth_utility/imgs/sample_clipseg.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..2c009c2127273ed15ca701f6bb119ba053d88dc0 --- /dev/null +++ b/ebsynth_utility/imgs/sample_clipseg.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17701c5c3a376d3c4cf8ce0acfb991033830d56670ca3178eedd6e671e096af3 +size 10249706 diff --git a/ebsynth_utility/install.py b/ebsynth_utility/install.py new file mode 100644 index 0000000000000000000000000000000000000000..018d5000add4858cee9d4a9a9bf95112a60238f9 --- /dev/null +++ b/ebsynth_utility/install.py @@ -0,0 +1,24 @@ +import launch + +def update_transparent_background(): + from importlib.metadata import version as meta_version + from packaging import version + v = meta_version("transparent-background") + print("current transparent-background " + v) + if version.parse(v) < version.parse('1.2.3'): + launch.run_pip("install -U transparent-background", "update transparent-background version for Ebsynth Utility") + +if not launch.is_installed("transparent_background"): + launch.run_pip("install transparent-background", "requirements for Ebsynth Utility") + +update_transparent_background() + +if not launch.is_installed("IPython"): + launch.run_pip("install ipython", "requirements for Ebsynth Utility") + +if not launch.is_installed("seaborn"): + launch.run_pip("install ""seaborn>=0.11.0""", "requirements for Ebsynth Utility") + +if not launch.is_installed("color_matcher"): + launch.run_pip("install color-matcher", "requirements for Ebsynth Utility") + diff --git a/ebsynth_utility/sample/add_token.txt b/ebsynth_utility/sample/add_token.txt new file mode 100644 index 0000000000000000000000000000000000000000..0f413582f6f4888c4baf01324e1bc71ad734c6bf --- /dev/null +++ b/ebsynth_utility/sample/add_token.txt @@ -0,0 +1,54 @@ +[ + { + "target":"smile", + "min_score":0.5, + "token": ["lottalewds_v0", "1.2"], + "type":"lora" + }, + { + "target":"smile", + "min_score":0.5, + "token": ["anyahehface", "score*1.2"], + "type":"normal" + }, + { + "target":"smile", + "min_score":0.5, + "token": ["wicked smug", "score*1.2"], + "type":"normal" + }, + { + "target":"smile", + "min_score":0.5, + "token": ["half closed eyes", "0.2 + score*0.3"], + "type":"normal" + }, + + + + { + "target":"test_token", + "min_score":0.8, + "token": ["lora_name_A", "0.5"], + "type":"lora" + }, + { + "target":"test_token", + "min_score":0.5, + "token": ["bbbb", "score - 0.1"], + "type":"normal" + }, + { + "target":"test_token2", + "min_score":0.8, + "token": ["hypernet_name_A", "score"], + "type":"hypernet" + }, + { + "target":"test_token3", + "min_score":0.0, + "token": ["dddd", "score"], + "type":"normal" + } +] + diff --git a/ebsynth_utility/sample/blacklist.txt b/ebsynth_utility/sample/blacklist.txt new file mode 100644 index 0000000000000000000000000000000000000000..6938ecd2e4eb99ad6403d077a4678e633c710d1e --- /dev/null +++ b/ebsynth_utility/sample/blacklist.txt @@ -0,0 +1,10 @@ +motion_blur +blurry +realistic +depth_of_field +mountain +tree +water +underwater +tongue +tongue_out diff --git a/ebsynth_utility/scripts/__pycache__/custom_script.cpython-310.pyc b/ebsynth_utility/scripts/__pycache__/custom_script.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..650293490ae83b91af39906bfd9bc9b7024acfaf Binary files /dev/null and b/ebsynth_utility/scripts/__pycache__/custom_script.cpython-310.pyc differ diff --git a/ebsynth_utility/scripts/__pycache__/ui.cpython-310.pyc b/ebsynth_utility/scripts/__pycache__/ui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc2deee3fda2d158a1511131a1992d00f06ad623 Binary files /dev/null and b/ebsynth_utility/scripts/__pycache__/ui.cpython-310.pyc differ diff --git a/ebsynth_utility/scripts/custom_script.py b/ebsynth_utility/scripts/custom_script.py new file mode 100644 index 0000000000000000000000000000000000000000..661d7e5d68f762d28bfc58d55ca3cb306ae392f7 --- /dev/null +++ b/ebsynth_utility/scripts/custom_script.py @@ -0,0 +1,1012 @@ +import modules.scripts as scripts +import gradio as gr +import os +import torch +import random +import time +import pprint +import shutil + +from modules.processing import process_images,Processed +from modules.paths import models_path +from modules.textual_inversion import autocrop +import modules.images +from modules import shared,deepbooru,masking +import cv2 +import copy +import numpy as np +from PIL import Image,ImageOps +import glob +import requests +import json +import re +from extensions.ebsynth_utility.calculator import CalcParser,ParseError + +def get_my_dir(): + if os.path.isdir("extensions/ebsynth_utility"): + return "extensions/ebsynth_utility" + return scripts.basedir() + +def x_ceiling(value, step): + return -(-value // step) * step + +def remove_pngs_in_dir(path): + if not os.path.isdir(path): + return + pngs = glob.glob( os.path.join(path, "*.png") ) + for png in pngs: + os.remove(png) + +def resize_img(img, w, h): + if img.shape[0] + img.shape[1] < h + w: + interpolation = interpolation=cv2.INTER_CUBIC + else: + interpolation = interpolation=cv2.INTER_AREA + + return cv2.resize(img, (w, h), interpolation=interpolation) + +def download_and_cache_models(dirname): + download_url = 'https://github.com/zymk9/yolov5_anime/blob/8b50add22dbd8224904221be3173390f56046794/weights/yolov5s_anime.pt?raw=true' + model_file_name = 'yolov5s_anime.pt' + + if not os.path.exists(dirname): + os.makedirs(dirname) + + cache_file = os.path.join(dirname, model_file_name) + if not os.path.exists(cache_file): + print(f"downloading face detection model from '{download_url}' to '{cache_file}'") + response = requests.get(download_url) + with open(cache_file, "wb") as f: + f.write(response.content) + + if os.path.exists(cache_file): + return cache_file + return None + +class Script(scripts.Script): + anime_face_detector = None + face_detector = None + face_merge_mask_filename = "face_crop_img2img_mask.png" + face_merge_mask_image = None + prompts_dir = "" + calc_parser = None + is_invert_mask = False + controlnet_weight = 0.5 + controlnet_weight_for_face = 0.5 + add_tag_replace_underscore = False + + +# The title of the script. This is what will be displayed in the dropdown menu. + def title(self): + return "ebsynth utility" + +# Determines when the script should be shown in the dropdown menu via the +# returned value. As an example: +# is_img2img is True if the current tab is img2img, and False if it is txt2img. +# Thus, return is_img2img to only show the script on the img2img tab. + + def show(self, is_img2img): + return is_img2img + +# How the script's is displayed in the UI. See https://gradio.app/docs/#components +# for the different UI components you can use and how to create them. +# Most UI components can return a value, such as a boolean for a checkbox. +# The returned values are passed to the run method as parameters. + + def ui(self, is_img2img): + with gr.Column(variant='panel'): + with gr.Column(): + project_dir = gr.Textbox(label='Project directory', lines=1) + generation_test = gr.Checkbox(False, label="Generation TEST!!(Ignore Project directory and use the image and mask specified in the main UI)") + + with gr.Accordion("Mask option"): + mask_mode = gr.Dropdown(choices=["Normal","Invert","None","Don't Override"], value="Normal" ,label="Mask Mode(Override img2img Mask mode)") + inpaint_area = gr.Dropdown(choices=["Whole picture","Only masked","Don't Override"], type = "index", value="Only masked" ,label="Inpaint Area(Override img2img Inpaint area)") + use_depth = gr.Checkbox(True, label="Use Depth Map If exists in /video_key_depth") + gr.HTML(value="

\ + See \ + [here] for depth map.\ +

") + + with gr.Accordion("ControlNet option"): + controlnet_weight = gr.Slider(minimum=0.0, maximum=2.0, step=0.01, value=0.5, label="Control Net Weight") + controlnet_weight_for_face = gr.Slider(minimum=0.0, maximum=2.0, step=0.01, value=0.5, label="Control Net Weight For Face") + use_preprocess_img = gr.Checkbox(True, label="Use Preprocess image If exists in /controlnet_preprocess") + gr.HTML(value="

\ + Please enable the following settings to use controlnet from this script.
\ + \ + Settings->ControlNet->Allow other script to control this extension\ + \ +

") + + with gr.Accordion("Loopback option"): + img2img_repeat_count = gr.Slider(minimum=1, maximum=30, step=1, value=1, label="Img2Img Repeat Count (Loop Back)") + inc_seed = gr.Slider(minimum=0, maximum=9999999, step=1, value=1, label="Add N to seed when repeating ") + + with gr.Accordion("Auto Tagging option"): + auto_tag_mode = gr.Dropdown(choices=["None","DeepDanbooru","CLIP"], value="None" ,label="Auto Tagging") + add_tag_to_head = gr.Checkbox(False, label="Add additional prompts to the head") + add_tag_replace_underscore = gr.Checkbox(False, label="Replace '_' with ' '(Does not affect the function to add tokens using add_token.txt.)") + gr.HTML(value="

\ + The results are stored in timestamp_prompts.txt.
\ + If you want to use the same tagging results the next time you run img2img, rename the file to prompts.txt
\ + Recommend enabling the following settings.
\ + \ + Settings->Interrogate Option->Interrogate: include ranks of model tags matches in results\ + \ +

") + + with gr.Accordion("Face Crop option"): + is_facecrop = gr.Checkbox(False, label="use Face Crop img2img") + + with gr.Row(): + face_detection_method = gr.Dropdown(choices=["YuNet","Yolov5_anime"], value="YuNet" ,label="Face Detection Method") + gr.HTML(value="

\ + If loading of the Yolov5_anime model fails, check\ + [this] solution.\ +

") + face_crop_resolution = gr.Slider(minimum=128, maximum=2048, step=1, value=512, label="Face Crop Resolution") + max_crop_size = gr.Slider(minimum=0, maximum=2048, step=1, value=1024, label="Max Crop Size") + face_denoising_strength = gr.Slider(minimum=0.00, maximum=1.00, step=0.01, value=0.5, label="Face Denoising Strength") + face_area_magnification = gr.Slider(minimum=1.00, maximum=10.00, step=0.01, value=1.5, label="Face Area Magnification ") + disable_facecrop_lpbk_last_time = gr.Checkbox(False, label="Disable at the last loopback time") + + with gr.Column(): + enable_face_prompt = gr.Checkbox(False, label="Enable Face Prompt") + face_prompt = gr.Textbox(label="Face Prompt", show_label=False, lines=2, + placeholder="Prompt for Face", + value = "face close up," + ) + + return [project_dir, generation_test, mask_mode, inpaint_area, use_depth, img2img_repeat_count, inc_seed, auto_tag_mode, add_tag_to_head, add_tag_replace_underscore, is_facecrop, face_detection_method, face_crop_resolution, max_crop_size, face_denoising_strength, face_area_magnification, enable_face_prompt, face_prompt, controlnet_weight, controlnet_weight_for_face, disable_facecrop_lpbk_last_time,use_preprocess_img] + + + def detect_face_from_img(self, img_array): + if not self.face_detector: + dnn_model_path = autocrop.download_and_cache_models(os.path.join(models_path, "opencv")) + self.face_detector = cv2.FaceDetectorYN.create(dnn_model_path, "", (0, 0)) + + self.face_detector.setInputSize((img_array.shape[1], img_array.shape[0])) + _, result = self.face_detector.detect(img_array) + return result + + def detect_anime_face_from_img(self, img_array): + import sys + + if not self.anime_face_detector: + if 'models' in sys.modules: + del sys.modules['models'] + + anime_model_path = download_and_cache_models(os.path.join(models_path, "yolov5_anime")) + + if not os.path.isfile(anime_model_path): + print( "WARNING!! " + anime_model_path + " not found.") + print( "use YuNet instead.") + return self.detect_face_from_img(img_array) + + self.anime_face_detector = torch.hub.load('ultralytics/yolov5', 'custom', path=anime_model_path) + + # warmup + test = np.zeros([512,512,3],dtype=np.uint8) + _ = self.anime_face_detector(test) + + result = self.anime_face_detector(img_array) + #models.common.Detections + faces = [] + for x_c, y_c, w, h, _, _ in result.xywh[0].tolist(): + faces.append( [ x_c - w/2 , y_c - h/2, w, h ] ) + + return faces + + def detect_face(self, img, mask, face_detection_method, max_crop_size): + img_array = np.array(img) + + # image without alpha + if img_array.shape[2] == 4: + img_array = img_array[:,:,:3] + + if mask is not None: + if self.is_invert_mask: + mask = ImageOps.invert(mask) + mask_array = np.array(mask)/255 + if mask_array.ndim == 2: + mask_array = mask_array[:, :, np.newaxis] + + if mask_array.shape[2] == 4: + mask_array = mask_array[:,:,:3] + + img_array = mask_array * img_array + img_array = img_array.astype(np.uint8) + + if face_detection_method == "YuNet": + faces = self.detect_face_from_img(img_array) + elif face_detection_method == "Yolov5_anime": + faces = self.detect_anime_face_from_img(img_array) + else: + faces = self.detect_face_from_img(img_array) + + if faces is None or len(faces) == 0: + return [] + + face_coords = [] + for face in faces: + x = int(face[0]) + y = int(face[1]) + w = int(face[2]) + h = int(face[3]) + if max(w,h) > max_crop_size: + print("ignore big face") + continue + if w == 0 or h == 0: + print("ignore w,h = 0 face") + continue + + face_coords.append( [ x/img_array.shape[1],y/img_array.shape[0],w/img_array.shape[1],h/img_array.shape[0]] ) + + return face_coords + + def get_mask(self): + def create_mask( output, x_rate, y_rate, k_size ): + img = np.zeros((512, 512, 3)) + img = cv2.ellipse(img, ((256, 256), (int(512 * x_rate), int(512 * y_rate)), 0), (255, 255, 255), thickness=-1) + img = cv2.GaussianBlur(img, (k_size, k_size), 0) + cv2.imwrite(output, img) + + if self.face_merge_mask_image is None: + mask_file_path = os.path.join( get_my_dir() , self.face_merge_mask_filename) + if not os.path.isfile(mask_file_path): + create_mask( mask_file_path, 0.9, 0.9, 91) + + m = cv2.imread( mask_file_path )[:,:,0] + m = m[:, :, np.newaxis] + self.face_merge_mask_image = m / 255 + + return self.face_merge_mask_image + + def face_img_crop(self, img, face_coords,face_area_magnification): + img_array = np.array(img) + face_imgs =[] + new_coords = [] + + for face in face_coords: + x = int(face[0] * img_array.shape[1]) + y = int(face[1] * img_array.shape[0]) + w = int(face[2] * img_array.shape[1]) + h = int(face[3] * img_array.shape[0]) + print([x,y,w,h]) + + cx = x + int(w/2) + cy = y + int(h/2) + + x = cx - int(w*face_area_magnification / 2) + x = x if x > 0 else 0 + w = cx + int(w*face_area_magnification / 2) - x + w = w if x+w < img.width else img.width - x + + y = cy - int(h*face_area_magnification / 2) + y = y if y > 0 else 0 + h = cy + int(h*face_area_magnification / 2) - y + h = h if y+h < img.height else img.height - y + + print([x,y,w,h]) + + face_imgs.append( img_array[y: y+h, x: x+w] ) + new_coords.append( [x,y,w,h] ) + + resized = [] + for face_img in face_imgs: + if face_img.shape[1] < face_img.shape[0]: + re_w = self.face_crop_resolution + re_h = int(x_ceiling( (self.face_crop_resolution / face_img.shape[1]) * face_img.shape[0] , 64)) + else: + re_w = int(x_ceiling( (self.face_crop_resolution / face_img.shape[0]) * face_img.shape[1] , 64)) + re_h = self.face_crop_resolution + + face_img = resize_img(face_img, re_w, re_h) + resized.append( Image.fromarray(face_img)) + + return resized, new_coords + + def face_crop_img2img(self, p, face_coords, face_denoising_strength, face_area_magnification, enable_face_prompt, face_prompt, controlnet_input_img, controlnet_input_face_imgs, preprocess_img_exist): + + def merge_face(img, face_img, face_coord, base_img_size, mask): + x_rate = img.width / base_img_size[0] + y_rate = img.height / base_img_size[1] + + img_array = np.array(img) + x = int(face_coord[0] * x_rate) + y = int(face_coord[1] * y_rate) + w = int(face_coord[2] * x_rate) + h = int(face_coord[3] * y_rate) + + face_array = np.array(face_img) + face_array = resize_img(face_array, w, h) + mask = resize_img(mask, w, h) + if mask.ndim == 2: + mask = mask[:, :, np.newaxis] + + bg = img_array[y: y+h, x: x+w] + img_array[y: y+h, x: x+w] = mask * face_array + (1-mask)*bg + + return Image.fromarray(img_array) + + base_img = p.init_images[0] + + base_img_size = (base_img.width, base_img.height) + + if face_coords is None or len(face_coords) == 0: + print("no face detected") + return process_images(p) + + print(face_coords) + face_imgs, new_coords = self.face_img_crop(base_img, face_coords, face_area_magnification) + + if not face_imgs: + return process_images(p) + + face_p = copy.copy(p) + + ### img2img base img + proc = self.process_images(p, controlnet_input_img, self.controlnet_weight, preprocess_img_exist) + print(proc.seed) + + ### img2img for each face + face_img2img_results = [] + + for face, coord, controlnet_input_face in zip(face_imgs, new_coords, controlnet_input_face_imgs): + # cv2.imwrite("scripts/face.png", np.array(face)[:, :, ::-1]) + face_p.init_images = [face] + face_p.width = face.width + face_p.height = face.height + face_p.denoising_strength = face_denoising_strength + + if enable_face_prompt: + face_p.prompt = face_prompt + else: + face_p.prompt = "close-up face ," + face_p.prompt + + if p.image_mask is not None: + x,y,w,h = coord + cropped_face_mask = Image.fromarray(np.array(p.image_mask)[y: y+h, x: x+w]) + face_p.image_mask = modules.images.resize_image(0, cropped_face_mask, face.width, face.height) + + face_proc = self.process_images(face_p, controlnet_input_face, self.controlnet_weight_for_face, preprocess_img_exist) + print(face_proc.seed) + + face_img2img_results.append((face_proc.images[0], coord)) + + ### merge faces + bg = proc.images[0] + mask = self.get_mask() + + for face_img, coord in face_img2img_results: + bg = merge_face(bg, face_img, coord, base_img_size, mask) + + proc.images[0] = bg + + return proc + + def get_depth_map(self, mask, depth_path ,img_basename, is_invert_mask): + depth_img_path = os.path.join( depth_path , img_basename ) + + depth = None + + if os.path.isfile( depth_img_path ): + depth = Image.open(depth_img_path) + else: + # try 00001-0000.png + os.path.splitext(img_basename)[0] + depth_img_path = os.path.join( depth_path , os.path.splitext(img_basename)[0] + "-0000.png" ) + if os.path.isfile( depth_img_path ): + depth = Image.open(depth_img_path) + + if depth: + if mask: + mask_array = np.array(mask) + depth_array = np.array(depth) + + if is_invert_mask == False: + depth_array[mask_array[:,:,0] == 0] = 0 + else: + depth_array[mask_array[:,:,0] != 0] = 0 + + depth = Image.fromarray(depth_array) + + tmp_path = os.path.join( depth_path , "tmp" ) + os.makedirs(tmp_path, exist_ok=True) + tmp_path = os.path.join( tmp_path , img_basename ) + depth_array = depth_array.astype(np.uint16) + cv2.imwrite(tmp_path, depth_array) + + mask = depth + + return depth!=None, mask + +### auto tagging + debug_count = 0 + + def get_masked_image(self, image, mask_image): + + if mask_image == None: + return image.convert("RGB") + + mask = mask_image.convert('L') + if self.is_invert_mask: + mask = ImageOps.invert(mask) + crop_region = masking.get_crop_region(np.array(mask), 0) +# crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) +# x1, y1, x2, y2 = crop_region + image = image.crop(crop_region).convert("RGB") + mask = mask.crop(crop_region) + + base_img = Image.new("RGB", image.size, (255, 190, 200)) + + image = Image.composite( image, base_img, mask ) + +# image.save("scripts/get_masked_image_test_"+ str(self.debug_count) + ".png") +# self.debug_count += 1 + + return image + + def interrogate_deepdanbooru(self, imgs, masks): + prompts_dict = {} + cause_err = False + + try: + deepbooru.model.start() + + for img,mask in zip(imgs,masks): + key = os.path.basename(img) + print(key + " interrogate deepdanbooru") + + image = Image.open(img) + mask_image = Image.open(mask) if mask else None + image = self.get_masked_image(image, mask_image) + + prompt = deepbooru.model.tag_multi(image) + + prompts_dict[key] = prompt + except Exception as e: + import traceback + traceback.print_exc() + print(e) + cause_err = True + finally: + deepbooru.model.stop() + if cause_err: + print("Exception occurred during auto-tagging(deepdanbooru)") + return Processed() + + return prompts_dict + + + def interrogate_clip(self, imgs, masks): + from modules import devices, shared, lowvram, paths + import importlib + import models + + caption_list = [] + prompts_dict = {} + cause_err = False + + try: + if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: + lowvram.send_everything_to_cpu() + devices.torch_gc() + + with paths.Prioritize("BLIP"): + importlib.reload(models) + shared.interrogator.load() + + for img,mask in zip(imgs,masks): + key = os.path.basename(img) + print(key + " generate caption") + + image = Image.open(img) + mask_image = Image.open(mask) if mask else None + image = self.get_masked_image(image, mask_image) + + caption = shared.interrogator.generate_caption(image) + caption_list.append(caption) + + shared.interrogator.send_blip_to_ram() + devices.torch_gc() + + for img,mask,caption in zip(imgs,masks,caption_list): + key = os.path.basename(img) + print(key + " interrogate clip") + + image = Image.open(img) + mask_image = Image.open(mask) if mask else None + image = self.get_masked_image(image, mask_image) + + clip_image = shared.interrogator.clip_preprocess(image).unsqueeze(0).type(shared.interrogator.dtype).to(devices.device_interrogate) + + res = "" + + with torch.no_grad(), devices.autocast(): + image_features = shared.interrogator.clip_model.encode_image(clip_image).type(shared.interrogator.dtype) + image_features /= image_features.norm(dim=-1, keepdim=True) + + for name, topn, items in shared.interrogator.categories(): + matches = shared.interrogator.rank(image_features, items, top_count=topn) + for match, score in matches: + if shared.opts.interrogate_return_ranks: + res += f", ({match}:{score/100:.3f})" + else: + res += ", " + match + + prompts_dict[key] = (caption + res) + + except Exception as e: + import traceback + traceback.print_exc() + print(e) + cause_err = True + finally: + shared.interrogator.unload() + if cause_err: + print("Exception occurred during auto-tagging(blip/clip)") + return Processed() + + return prompts_dict + + + def remove_reserved_token(self, token_list): + reserved_list = ["pink_background","simple_background","pink","pink_theme"] + + result_list = [] + + head_token = token_list[0] + + if head_token[2] == "normal": + head_token_str = head_token[0].replace('pink background', '') + token_list[0] = (head_token_str, head_token[1], head_token[2]) + + for token in token_list: + if token[0] in reserved_list: + continue + result_list.append(token) + + return result_list + + def remove_blacklisted_token(self, token_list): + black_list_path = os.path.join(self.prompts_dir, "blacklist.txt") + if not os.path.isfile(black_list_path): + print(black_list_path + " not found.") + return token_list + + with open(black_list_path) as f: + black_list = [s.strip() for s in f.readlines()] + + result_list = [] + + for token in token_list: + if token[0] in black_list: + continue + result_list.append(token) + + token_list = result_list + + return token_list + + def add_token(self, token_list): + add_list_path = os.path.join(self.prompts_dir, "add_token.txt") + if not os.path.isfile(add_list_path): + print(add_list_path + " not found.") + + if self.add_tag_replace_underscore: + token_list = [ (x[0].replace("_"," "), x[1], x[2]) for x in token_list ] + + return token_list + + if not self.calc_parser: + self.calc_parser = CalcParser() + + with open(add_list_path) as f: + add_list = json.load(f) + ''' + [ + { + "target":"test_token", + "min_score":0.8, + "token": ["lora_name_A", "0.5"], + "type":"lora" + }, + { + "target":"test_token", + "min_score":0.5, + "token": ["bbbb", "score - 0.1"], + "type":"normal" + }, + { + "target":"test_token2", + "min_score":0.8, + "token": ["hypernet_name_A", "score"], + "type":"hypernet" + }, + { + "target":"test_token3", + "min_score":0.0, + "token": ["dddd", "score"], + "type":"normal" + } + ] + ''' + result_list = [] + + for token in token_list: + for add_item in add_list: + if token[0] == add_item["target"]: + if token[1] > add_item["min_score"]: + # hit + formula = str(add_item["token"][1]) + formula = formula.replace("score",str(token[1])) + print('Input: %s' % str(add_item["token"][1])) + + try: + score = self.calc_parser.parse(formula) + score = round(score, 3) + except (ParseError, ZeroDivisionError) as e: + print('Input: %s' % str(add_item["token"][1])) + print('Error: %s' % e) + print("ignore this token") + continue + + print("score = " + str(score)) + result_list.append( ( add_item["token"][0], score, add_item["type"] ) ) + + if self.add_tag_replace_underscore: + token_list = [ (x[0].replace("_"," "), x[1], x[2]) for x in token_list ] + + token_list = token_list + result_list + + return token_list + + def create_prompts_dict(self, imgs, masks, auto_tag_mode): + prompts_dict = {} + + if auto_tag_mode == "DeepDanbooru": + raw_dict = self.interrogate_deepdanbooru(imgs, masks) + elif auto_tag_mode == "CLIP": + raw_dict = self.interrogate_clip(imgs, masks) + + repatter = re.compile(r'\((.+)\:([0-9\.]+)\)') + + for key, value_str in raw_dict.items(): + value_list = [x.strip() for x in value_str.split(',')] + + value = [] + for v in value_list: + m = repatter.fullmatch(v) + if m: + value.append((m.group(1), float(m.group(2)), "normal")) + else: + value.append((v, 1, "no_score")) + +# print(value) + value = self.remove_reserved_token(value) +# print(value) + value = self.remove_blacklisted_token(value) +# print(value) + value = self.add_token(value) +# print(value) + + def create_token_str(x): + print(x) + if x[2] == "no_score": + return x[0] + elif x[2] == "lora": + return "" + elif x[2] == "hypernet": + return "" + else: + return "(" + x[0] + ":" + str(x[1]) + ")" + + value_list = [create_token_str(x) for x in value] + value = ",".join(value_list) + + prompts_dict[key] = value + + return prompts_dict + + def load_prompts_dict(self, imgs, default_token): + prompts_path = os.path.join(self.prompts_dir, "prompts.txt") + if not os.path.isfile(prompts_path): + print(prompts_path + " not found.") + return {} + + prompts_dict = {} + + print(prompts_path + " found!!") + print("skip auto tagging.") + + with open(prompts_path) as f: + raw_dict = json.load(f) + prev_value = default_token + for img in imgs: + key = os.path.basename(img) + + if key in raw_dict: + prompts_dict[key] = raw_dict[key] + prev_value = raw_dict[key] + else: + prompts_dict[key] = prev_value + + return prompts_dict + + def process_images(self, p, input_img, controlnet_weight, input_img_is_preprocessed): + p.control_net_input_image = input_img + p.control_net_weight = controlnet_weight + if input_img_is_preprocessed: + p.control_net_module = "none" + return process_images(p) + +# This is where the additional processing is implemented. The parameters include +# self, the model object "p" (a StableDiffusionProcessing class, see +# processing.py), and the parameters returned by the ui method. +# Custom functions can be defined here, and additional libraries can be imported +# to be used in processing. The return value should be a Processed object, which is +# what is returned by the process_images method. + def run(self, p, project_dir, generation_test, mask_mode, inpaint_area, use_depth, img2img_repeat_count, inc_seed, auto_tag_mode, add_tag_to_head, add_tag_replace_underscore, is_facecrop, face_detection_method, face_crop_resolution, max_crop_size, face_denoising_strength, face_area_magnification, enable_face_prompt, face_prompt, controlnet_weight, controlnet_weight_for_face, disable_facecrop_lpbk_last_time, use_preprocess_img): + args = locals() + + if generation_test: + print("generation_test") + test_proj_dir = os.path.join( get_my_dir() , "generation_test_proj") + os.makedirs(test_proj_dir, exist_ok=True) + test_video_key_path = os.path.join( test_proj_dir , "video_key") + os.makedirs(test_video_key_path, exist_ok=True) + test_video_mask_path = os.path.join( test_proj_dir , "video_mask") + os.makedirs(test_video_mask_path, exist_ok=True) + + controlnet_input_path = os.path.join(test_proj_dir, "controlnet_input") + if os.path.isdir(controlnet_input_path): + shutil.rmtree(controlnet_input_path) + + remove_pngs_in_dir(test_video_key_path) + remove_pngs_in_dir(test_video_mask_path) + + test_base_img = p.init_images[0] + test_mask = p.image_mask + + if test_base_img: + test_base_img.save( os.path.join( test_video_key_path , "00001.png") ) + if test_mask: + test_mask.save( os.path.join( test_video_mask_path , "00001.png") ) + + project_dir = test_proj_dir + else: + if not os.path.isdir(project_dir): + print("project_dir not found") + return Processed() + + self.controlnet_weight = controlnet_weight + self.controlnet_weight_for_face = controlnet_weight_for_face + + self.add_tag_replace_underscore = add_tag_replace_underscore + self.face_crop_resolution = face_crop_resolution + + if p.seed == -1: + p.seed = int(random.randrange(4294967294)) + + if mask_mode == "Normal": + p.inpainting_mask_invert = 0 + elif mask_mode == "Invert": + p.inpainting_mask_invert = 1 + + if inpaint_area in (0,1): #"Whole picture","Only masked" + p.inpaint_full_res = inpaint_area + + is_invert_mask = False + if mask_mode == "Invert": + is_invert_mask = True + + inv_path = os.path.join(project_dir, "inv") + if not os.path.isdir(inv_path): + print("project_dir/inv not found") + return Processed() + + org_key_path = os.path.join(inv_path, "video_key") + img2img_key_path = os.path.join(inv_path, "img2img_key") + depth_path = os.path.join(inv_path, "video_key_depth") + + preprocess_path = os.path.join(inv_path, "controlnet_preprocess") + + controlnet_input_path = os.path.join(inv_path, "controlnet_input") + + self.prompts_dir = inv_path + self.is_invert_mask = True + else: + org_key_path = os.path.join(project_dir, "video_key") + img2img_key_path = os.path.join(project_dir, "img2img_key") + depth_path = os.path.join(project_dir, "video_key_depth") + + preprocess_path = os.path.join(project_dir, "controlnet_preprocess") + + controlnet_input_path = os.path.join(project_dir, "controlnet_input") + + self.prompts_dir = project_dir + self.is_invert_mask = False + + frame_mask_path = os.path.join(project_dir, "video_mask") + + if not use_depth: + depth_path = None + + if not os.path.isdir(org_key_path): + print(org_key_path + " not found") + print("Generate key frames first." if is_invert_mask == False else \ + "Generate key frames first.(with [Ebsynth Utility] Tab -> [configuration] -> [etc]-> [Mask Mode] = Invert setting)") + return Processed() + + if not os.path.isdir(controlnet_input_path): + print(controlnet_input_path + " not found") + print("copy {0} -> {1}".format(org_key_path,controlnet_input_path)) + + os.makedirs(controlnet_input_path, exist_ok=True) + + imgs = glob.glob( os.path.join(org_key_path ,"*.png") ) + for img in imgs: + img_basename = os.path.basename(img) + shutil.copy( img , os.path.join(controlnet_input_path, img_basename) ) + + remove_pngs_in_dir(img2img_key_path) + os.makedirs(img2img_key_path, exist_ok=True) + + + def get_mask_of_img(img): + img_basename = os.path.basename(img) + + if mask_mode != "None": + mask_path = os.path.join( frame_mask_path , img_basename ) + if os.path.isfile( mask_path ): + return mask_path + return "" + + def get_pair_of_img(img, target_dir): + img_basename = os.path.basename(img) + + pair_path = os.path.join( target_dir , img_basename ) + if os.path.isfile( pair_path ): + return pair_path + print("!!! pair of "+ img + " not in " + target_dir) + return "" + + def get_controlnet_input_img(img): + pair_img = get_pair_of_img(img, controlnet_input_path) + if not pair_img: + pair_img = get_pair_of_img(img, org_key_path) + return pair_img + + imgs = glob.glob( os.path.join(org_key_path ,"*.png") ) + masks = [ get_mask_of_img(i) for i in imgs ] + controlnet_input_imgs = [ get_controlnet_input_img(i) for i in imgs ] + + for mask in masks: + m = cv2.imread(mask) if mask else None + if m is not None: + if m.max() == 0: + print("{0} blank mask found".format(mask)) + if m.ndim == 2: + m[0,0] = 255 + else: + m = m[:,:,:3] + m[0,0,0:3] = 255 + cv2.imwrite(mask, m) + + ###################### + # face crop + face_coords_dict={} + for img,mask in zip(imgs,masks): + face_detected = False + if is_facecrop: + image = Image.open(img) + mask_image = Image.open(mask) if mask else None + face_coords = self.detect_face(image, mask_image, face_detection_method, max_crop_size) + if face_coords is None or len(face_coords) == 0: + print("no face detected") + else: + print("face detected") + face_detected = True + + key = os.path.basename(img) + face_coords_dict[key] = face_coords if face_detected else [] + + with open( os.path.join( project_dir if is_invert_mask == False else inv_path,"faces.txt" ), "w") as f: + f.write(json.dumps(face_coords_dict,indent=4)) + + ###################### + # prompts + prompts_dict = self.load_prompts_dict(imgs, p.prompt) + + if not prompts_dict: + if auto_tag_mode != "None": + prompts_dict = self.create_prompts_dict(imgs, masks, auto_tag_mode) + + for key, value in prompts_dict.items(): + prompts_dict[key] = (value + "," + p.prompt) if add_tag_to_head else (p.prompt + "," + value) + + else: + for img in imgs: + key = os.path.basename(img) + prompts_dict[key] = p.prompt + + with open( os.path.join( project_dir if is_invert_mask == False else inv_path, time.strftime("%Y%m%d-%H%M%S_") + "prompts.txt" ), "w") as f: + f.write(json.dumps(prompts_dict,indent=4)) + + + ###################### + # img2img + for img, mask, controlnet_input_img, face_coords, prompts in zip(imgs, masks, controlnet_input_imgs, face_coords_dict.values(), prompts_dict.values()): + + # Generation cancelled. + if shared.state.interrupted: + print("Generation cancelled.") + break + + image = Image.open(img) + mask_image = Image.open(mask) if mask else None + + img_basename = os.path.basename(img) + + _p = copy.copy(p) + + _p.init_images=[image] + _p.image_mask = mask_image + _p.prompt = prompts + resized_mask = None + + repeat_count = img2img_repeat_count + + if mask_mode != "None" or use_depth: + if use_depth: + depth_found, _p.image_mask = self.get_depth_map( mask_image, depth_path ,img_basename, is_invert_mask ) + mask_image = _p.image_mask + if depth_found: + _p.inpainting_mask_invert = 0 + + preprocess_img_exist = False + controlnet_input_base_img = Image.open(controlnet_input_img) if controlnet_input_img else None + + if use_preprocess_img: + preprocess_img = os.path.join(preprocess_path, img_basename) + if os.path.isfile( preprocess_img ): + controlnet_input_base_img = Image.open(preprocess_img) + preprocess_img_exist = True + + if face_coords: + controlnet_input_face_imgs, _ = self.face_img_crop(controlnet_input_base_img, face_coords, face_area_magnification) + + while repeat_count > 0: + + if disable_facecrop_lpbk_last_time: + if img2img_repeat_count > 1: + if repeat_count == 1: + face_coords = None + + if face_coords: + proc = self.face_crop_img2img(_p, face_coords, face_denoising_strength, face_area_magnification, enable_face_prompt, face_prompt, controlnet_input_base_img, controlnet_input_face_imgs, preprocess_img_exist) + else: + proc = self.process_images(_p, controlnet_input_base_img, self.controlnet_weight, preprocess_img_exist) + print(proc.seed) + + repeat_count -= 1 + + if repeat_count > 0: + _p.init_images=[proc.images[0]] + + if mask_image is not None and resized_mask is None: + resized_mask = resize_img(np.array(mask_image) , proc.images[0].width, proc.images[0].height) + resized_mask = Image.fromarray(resized_mask) + _p.image_mask = resized_mask + _p.seed += inc_seed + + proc.images[0].save( os.path.join( img2img_key_path , img_basename ) ) + + with open( os.path.join( project_dir if is_invert_mask == False else inv_path,"param.txt" ), "w") as f: + f.write(pprint.pformat(proc.info)) + with open( os.path.join( project_dir if is_invert_mask == False else inv_path ,"args.txt" ), "w") as f: + f.write(pprint.pformat(args)) + + return proc diff --git a/ebsynth_utility/scripts/ui.py b/ebsynth_utility/scripts/ui.py new file mode 100644 index 0000000000000000000000000000000000000000..0fdc58bdb09ea33dbe899072be80d97ffb4d888b --- /dev/null +++ b/ebsynth_utility/scripts/ui.py @@ -0,0 +1,199 @@ + +import gradio as gr + +from ebsynth_utility import ebsynth_utility_process +from modules import script_callbacks +from modules.call_queue import wrap_gradio_gpu_call + +def on_ui_tabs(): + + with gr.Blocks(analytics_enabled=False) as ebs_interface: + with gr.Row().style(equal_height=False): + with gr.Column(variant='panel'): + + with gr.Row(): + with gr.Tabs(elem_id="ebs_settings"): + with gr.TabItem('project setting', elem_id='ebs_project_setting'): + project_dir = gr.Textbox(label='Project directory', lines=1) + original_movie_path = gr.Textbox(label='Original Movie Path', lines=1) + + org_video = gr.Video(interactive=True, mirror_webcam=False) + def fn_upload_org_video(video): + return video + org_video.upload(fn_upload_org_video, org_video, original_movie_path) + gr.HTML(value="

\ + If you have trouble entering the video path manually, you can also use drag and drop.For large videos, please enter the path manually. \ +

") + + with gr.TabItem('configuration', elem_id='ebs_configuration'): + with gr.Tabs(elem_id="ebs_configuration_tab"): + with gr.TabItem(label="stage 1",elem_id='ebs_configuration_tab1'): + with gr.Row(): + frame_width = gr.Number(value=-1, label="Frame Width", precision=0, interactive=True) + frame_height = gr.Number(value=-1, label="Frame Height", precision=0, interactive=True) + gr.HTML(value="

\ + -1 means that it is calculated automatically. If both are -1, the size will be the same as the source size. \ +

") + + st1_masking_method_index = gr.Radio(label='Masking Method', choices=["transparent-background","clipseg","transparent-background AND clipseg"], value="transparent-background", type="index") + + with gr.Accordion(label="transparent-background options"): + st1_mask_threshold = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Mask Threshold', value=0.0) + + # https://pypi.org/project/transparent-background/ + gr.HTML(value="

\ + configuration for \ + [transparent-background]\ +

") + tb_use_fast_mode = gr.Checkbox(label="Use Fast Mode(It will be faster, but the quality of the mask will be lower.)", value=False) + tb_use_jit = gr.Checkbox(label="Use Jit", value=False) + + with gr.Accordion(label="clipseg options"): + clipseg_mask_prompt = gr.Textbox(label='Mask Target (e.g., girl, cats)', lines=1) + clipseg_exclude_prompt = gr.Textbox(label='Exclude Target (e.g., finger, book)', lines=1) + clipseg_mask_threshold = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Mask Threshold', value=0.4) + clipseg_mask_blur_size = gr.Slider(minimum=0, maximum=150, step=1, label='Mask Blur Kernel Size(MedianBlur)', value=11) + clipseg_mask_blur_size2 = gr.Slider(minimum=0, maximum=150, step=1, label='Mask Blur Kernel Size(GaussianBlur)', value=11) + + with gr.TabItem(label="stage 2", elem_id='ebs_configuration_tab2'): + key_min_gap = gr.Slider(minimum=0, maximum=500, step=1, label='Minimum keyframe gap', value=10) + key_max_gap = gr.Slider(minimum=0, maximum=1000, step=1, label='Maximum keyframe gap', value=300) + key_th = gr.Slider(minimum=0.0, maximum=100.0, step=0.1, label='Threshold of delta frame edge', value=8.5) + key_add_last_frame = gr.Checkbox(label="Add last frame to keyframes", value=True) + + with gr.TabItem(label="stage 3.5", elem_id='ebs_configuration_tab3_5'): + gr.HTML(value="

\ + [color-matcher]\ +

") + + color_matcher_method = gr.Radio(label='Color Transfer Method', choices=['default', 'hm', 'reinhard', 'mvgd', 'mkl', 'hm-mvgd-hm', 'hm-mkl-hm'], value="hm-mkl-hm", type="value") + color_matcher_ref_type = gr.Radio(label='Color Matcher Ref Image Type', choices=['original video frame', 'first frame of img2img result'], value="original video frame", type="index") + gr.HTML(value="

\ + If an image is specified below, it will be used with highest priority.\ +

") + color_matcher_ref_image = gr.Image(label="Color Matcher Ref Image", source='upload', mirror_webcam=False, type='pil') + st3_5_use_mask = gr.Checkbox(label="Apply mask to the result", value=True) + st3_5_use_mask_ref = gr.Checkbox(label="Apply mask to the Ref Image", value=False) + st3_5_use_mask_org = gr.Checkbox(label="Apply mask to original image", value=False) + #st3_5_number_of_itr = gr.Slider(minimum=1, maximum=10, step=1, label='Number of iterations', value=1) + + with gr.TabItem(label="stage 7", elem_id='ebs_configuration_tab7'): + blend_rate = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Crossfade blend rate', value=1.0) + export_type = gr.Dropdown(choices=["mp4","webm","gif","rawvideo"], value="mp4" ,label="Export type") + + with gr.TabItem(label="stage 8", elem_id='ebs_configuration_tab8'): + bg_src = gr.Textbox(label='Background source(mp4 or directory containing images)', lines=1) + bg_type = gr.Dropdown(choices=["Fit video length","Loop"], value="Fit video length" ,label="Background type") + mask_blur_size = gr.Slider(minimum=0, maximum=150, step=1, label='Mask Blur Kernel Size', value=5) + mask_threshold = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Mask Threshold', value=0.0) + #is_transparent = gr.Checkbox(label="Is Transparent", value=True, visible = False) + fg_transparency = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Foreground Transparency', value=0.0) + + with gr.TabItem(label="etc", elem_id='ebs_configuration_tab_etc'): + mask_mode = gr.Dropdown(choices=["Normal","Invert","None"], value="Normal" ,label="Mask Mode") + + with gr.Column(variant='panel'): + with gr.Column(scale=1): + with gr.Group(): + debug_info = gr.HTML(elem_id="ebs_info_area", value=".") + + with gr.Column(scale=2): + stage_index = gr.Radio(label='Process Stage', choices=["stage 1","stage 2","stage 3","stage 3.5","stage 4","stage 5","stage 6","stage 7","stage 8"], value="stage 1", type="index") + gr.HTML(value="

\ + The process of creating a video can be divided into the following stages.
\ + (Stage 3, 4, and 6 only show a guide and do nothing actual processing.)

\ + stage 1
\ + Extract frames from the original video.
\ + Generate a mask image.

\ + stage 2
\ + Select keyframes to be given to ebsynth.

\ + stage 3
\ + img2img keyframes.

\ + stage 3.5
\ + (this is optional. Perform color correction on the img2img results and expect flickering to decrease. Or, you can simply change the color tone from the generated result.)

\ + stage 4
\ + and upscale to the size of the original video.

\ + stage 5
\ + Rename keyframes.
\ + Generate .ebs file.(ebsynth project file)

\ + stage 6
\ + Running ebsynth.(on your self)
\ + Open the generated .ebs under project directory and press [Run All] button.
\ + If ""out-*"" directory already exists in the Project directory, delete it manually before executing.
\ + If multiple .ebs files are generated, run them all.

\ + stage 7
\ + Concatenate each frame while crossfading.
\ + Composite audio files extracted from the original video onto the concatenated video.

\ + stage 8
\ + This is an extra stage.
\ + You can put any image or images or video you like in the background.
\ + You can specify in this field -> [Ebsynth Utility]->[configuration]->[stage 8]->[Background source]
\ + If you have already created a background video in Invert Mask Mode([Ebsynth Utility]->[configuration]->[etc]->[Mask Mode]),
\ + You can specify \"path_to_project_dir/inv/crossfade_tmp\".
\ +

") + + with gr.Row(): + generate_btn = gr.Button('Generate', elem_id="ebs_generate_btn", variant='primary') + + with gr.Group(): + html_info = gr.HTML() + + + ebs_args = dict( + fn=wrap_gradio_gpu_call(ebsynth_utility_process), + inputs=[ + stage_index, + + project_dir, + original_movie_path, + + frame_width, + frame_height, + st1_masking_method_index, + st1_mask_threshold, + tb_use_fast_mode, + tb_use_jit, + clipseg_mask_prompt, + clipseg_exclude_prompt, + clipseg_mask_threshold, + clipseg_mask_blur_size, + clipseg_mask_blur_size2, + + key_min_gap, + key_max_gap, + key_th, + key_add_last_frame, + + color_matcher_method, + st3_5_use_mask, + st3_5_use_mask_ref, + st3_5_use_mask_org, + color_matcher_ref_type, + color_matcher_ref_image, + + blend_rate, + export_type, + + bg_src, + bg_type, + mask_blur_size, + mask_threshold, + fg_transparency, + + mask_mode, + + ], + outputs=[ + debug_info, + html_info, + ], + show_progress=False, + ) + generate_btn.click(**ebs_args) + + return (ebs_interface, "Ebsynth Utility", "ebs_interface"), + + + +script_callbacks.on_ui_tabs(on_ui_tabs) + diff --git a/ebsynth_utility/stage1.py b/ebsynth_utility/stage1.py new file mode 100644 index 0000000000000000000000000000000000000000..90acf441395d6c10320b75112d28ccd0c2475e94 --- /dev/null +++ b/ebsynth_utility/stage1.py @@ -0,0 +1,258 @@ +import os +import subprocess +import glob +import cv2 +import re + +from transformers import AutoProcessor, CLIPSegForImageSegmentation +from PIL import Image +import torch +import numpy as np + + +def resize_img(img, w, h): + if img.shape[0] + img.shape[1] < h + w: + interpolation = interpolation=cv2.INTER_CUBIC + else: + interpolation = interpolation=cv2.INTER_AREA + + return cv2.resize(img, (w, h), interpolation=interpolation) + +def resize_all_img(path, frame_width, frame_height): + if not os.path.isdir(path): + return + + pngs = glob.glob( os.path.join(path, "*.png") ) + img = cv2.imread(pngs[0]) + org_h,org_w = img.shape[0],img.shape[1] + + if frame_width == -1 and frame_height == -1: + return + elif frame_width == -1 and frame_height != -1: + frame_width = int(frame_height * org_w / org_h) + elif frame_width != -1 and frame_height == -1: + frame_height = int(frame_width * org_h / org_w) + else: + pass + print("({0},{1}) resize to ({2},{3})".format(org_w, org_h, frame_width, frame_height)) + + for png in pngs: + img = cv2.imread(png) + img = resize_img(img, frame_width, frame_height) + cv2.imwrite(png, img) + +def remove_pngs_in_dir(path): + if not os.path.isdir(path): + return + + pngs = glob.glob( os.path.join(path, "*.png") ) + for png in pngs: + os.remove(png) + +def create_and_mask(mask_dir1, mask_dir2, output_dir): + masks = glob.glob( os.path.join(mask_dir1, "*.png") ) + + for mask1 in masks: + base_name = os.path.basename(mask1) + print("combine {0}".format(base_name)) + + mask2 = os.path.join(mask_dir2, base_name) + if not os.path.isfile(mask2): + print("{0} not found!!! -> skip".format(mask2)) + continue + + img_1 = cv2.imread(mask1) + img_2 = cv2.imread(mask2) + img_1 = np.minimum(img_1,img_2) + + out_path = os.path.join(output_dir, base_name) + cv2.imwrite(out_path, img_1) + + +def create_mask_clipseg(input_dir, output_dir, clipseg_mask_prompt, clipseg_exclude_prompt, clipseg_mask_threshold, mask_blur_size, mask_blur_size2): + from modules import devices + + devices.torch_gc() + + device = devices.get_optimal_device_name() + + processor = AutoProcessor.from_pretrained("CIDAS/clipseg-rd64-refined") + model = CLIPSegForImageSegmentation.from_pretrained("CIDAS/clipseg-rd64-refined") + model.to(device) + + imgs = glob.glob( os.path.join(input_dir, "*.png") ) + texts = [x.strip() for x in clipseg_mask_prompt.split(',')] + exclude_texts = [x.strip() for x in clipseg_exclude_prompt.split(',')] if clipseg_exclude_prompt else None + + if exclude_texts: + all_texts = texts + exclude_texts + else: + all_texts = texts + + + for img_count,img in enumerate(imgs): + image = Image.open(img) + base_name = os.path.basename(img) + + inputs = processor(text=all_texts, images=[image] * len(all_texts), padding="max_length", return_tensors="pt") + inputs = inputs.to(device) + + with torch.no_grad(), devices.autocast(): + outputs = model(**inputs) + + if len(all_texts) == 1: + preds = outputs.logits.unsqueeze(0) + else: + preds = outputs.logits + + mask_img = None + + for i in range(len(all_texts)): + x = torch.sigmoid(preds[i]) + x = x.to('cpu').detach().numpy() + +# x[x < clipseg_mask_threshold] = 0 + x = x > clipseg_mask_threshold + + if i < len(texts): + if mask_img is None: + mask_img = x + else: + mask_img = np.maximum(mask_img,x) + else: + mask_img[x > 0] = 0 + + mask_img = mask_img*255 + mask_img = mask_img.astype(np.uint8) + + if mask_blur_size > 0: + mask_blur_size = mask_blur_size//2 * 2 + 1 + mask_img = cv2.medianBlur(mask_img, mask_blur_size) + + if mask_blur_size2 > 0: + mask_blur_size2 = mask_blur_size2//2 * 2 + 1 + mask_img = cv2.GaussianBlur(mask_img, (mask_blur_size2, mask_blur_size2), 0) + + mask_img = resize_img(mask_img, image.width, image.height) + + mask_img = cv2.cvtColor(mask_img, cv2.COLOR_GRAY2RGB) + save_path = os.path.join(output_dir, base_name) + cv2.imwrite(save_path, mask_img) + + print("{0} / {1}".format( img_count+1,len(imgs) )) + + devices.torch_gc() + + +def create_mask_transparent_background(input_dir, output_dir, tb_use_fast_mode, tb_use_jit, st1_mask_threshold): + fast_str = " --fast" if tb_use_fast_mode else "" + jit_str = " --jit" if tb_use_jit else "" + venv = "venv" + if 'VIRTUAL_ENV' in os.environ: + venv = os.environ['VIRTUAL_ENV'] + bin_path = os.path.join(venv, "Scripts") + bin_path = os.path.join(bin_path, "transparent-background") + + if os.path.isfile(bin_path) or os.path.isfile(bin_path + ".exe"): + subprocess.call(bin_path + " --source " + input_dir + " --dest " + output_dir + " --type map" + fast_str + jit_str, shell=True) + else: + subprocess.call("transparent-background --source " + input_dir + " --dest " + output_dir + " --type map" + fast_str + jit_str, shell=True) + + mask_imgs = glob.glob( os.path.join(output_dir, "*.png") ) + + for m in mask_imgs: + img = cv2.imread(m) + img[img < int( 255 * st1_mask_threshold )] = 0 + cv2.imwrite(m, img) + + p = re.compile(r'([0-9]+)_[a-z]*\.png') + + for mask in mask_imgs: + base_name = os.path.basename(mask) + m = p.fullmatch(base_name) + if m: + os.rename(mask, os.path.join(output_dir, m.group(1) + ".png")) + +def ebsynth_utility_stage1(dbg, project_args, frame_width, frame_height, st1_masking_method_index, st1_mask_threshold, tb_use_fast_mode, tb_use_jit, clipseg_mask_prompt, clipseg_exclude_prompt, clipseg_mask_threshold, clipseg_mask_blur_size, clipseg_mask_blur_size2, is_invert_mask): + dbg.print("stage1") + dbg.print("") + + if st1_masking_method_index == 1 and (not clipseg_mask_prompt): + dbg.print("Error: clipseg_mask_prompt is Empty") + return + + project_dir, original_movie_path, frame_path, frame_mask_path, _, _, _ = project_args + + if is_invert_mask: + if os.path.isdir( frame_path ) and os.path.isdir( frame_mask_path ): + dbg.print("Skip as it appears that the frame and normal masks have already been generated.") + return + + # remove_pngs_in_dir(frame_path) + + if frame_mask_path: + remove_pngs_in_dir(frame_mask_path) + + if frame_mask_path: + os.makedirs(frame_mask_path, exist_ok=True) + + if os.path.isdir( frame_path ): + dbg.print("Skip frame extraction") + else: + os.makedirs(frame_path, exist_ok=True) + + png_path = os.path.join(frame_path , "%05d.png") + # ffmpeg.exe -ss 00:00:00 -y -i %1 -qscale 0 -f image2 -c:v png "%05d.png" + subprocess.call("ffmpeg -ss 00:00:00 -y -i " + original_movie_path + " -qscale 0 -f image2 -c:v png " + png_path, shell=True) + + dbg.print("frame extracted") + + frame_width = max(frame_width,-1) + frame_height = max(frame_height,-1) + + if frame_width != -1 or frame_height != -1: + resize_all_img(frame_path, frame_width, frame_height) + + if frame_mask_path: + if st1_masking_method_index == 0: + create_mask_transparent_background(frame_path, frame_mask_path, tb_use_fast_mode, tb_use_jit, st1_mask_threshold) + elif st1_masking_method_index == 1: + create_mask_clipseg(frame_path, frame_mask_path, clipseg_mask_prompt, clipseg_exclude_prompt, clipseg_mask_threshold, clipseg_mask_blur_size, clipseg_mask_blur_size2) + elif st1_masking_method_index == 2: + tb_tmp_path = os.path.join(project_dir , "tb_mask_tmp") + if not os.path.isdir( tb_tmp_path ): + os.makedirs(tb_tmp_path, exist_ok=True) + create_mask_transparent_background(frame_path, tb_tmp_path, tb_use_fast_mode, tb_use_jit, st1_mask_threshold) + create_mask_clipseg(frame_path, frame_mask_path, clipseg_mask_prompt, clipseg_exclude_prompt, clipseg_mask_threshold, clipseg_mask_blur_size, clipseg_mask_blur_size2) + create_and_mask(tb_tmp_path,frame_mask_path,frame_mask_path) + + + dbg.print("mask created") + + dbg.print("") + dbg.print("completed.") + + +def ebsynth_utility_stage1_invert(dbg, frame_mask_path, inv_mask_path): + dbg.print("stage 1 create_invert_mask") + dbg.print("") + + if not os.path.isdir( frame_mask_path ): + dbg.print( frame_mask_path + " not found") + dbg.print("Normal masks must be generated previously.") + dbg.print("Do stage 1 with [Ebsynth Utility] Tab -> [configuration] -> [etc]-> [Mask Mode] = Normal setting first") + return + + os.makedirs(inv_mask_path, exist_ok=True) + + mask_imgs = glob.glob( os.path.join(frame_mask_path, "*.png") ) + + for m in mask_imgs: + img = cv2.imread(m) + inv = cv2.bitwise_not(img) + + base_name = os.path.basename(m) + cv2.imwrite(os.path.join(inv_mask_path,base_name), inv) + + dbg.print("") + dbg.print("completed.") diff --git a/ebsynth_utility/stage2.py b/ebsynth_utility/stage2.py new file mode 100644 index 0000000000000000000000000000000000000000..762affbb829785029ceb93c553eef6ce42665658 --- /dev/null +++ b/ebsynth_utility/stage2.py @@ -0,0 +1,173 @@ +import cv2 +import os +import glob +import shutil +import numpy as np +import math + +#--------------------------------- +# Copied from PySceneDetect +def mean_pixel_distance(left: np.ndarray, right: np.ndarray) -> float: + """Return the mean average distance in pixel values between `left` and `right`. + Both `left and `right` should be 2 dimensional 8-bit images of the same shape. + """ + assert len(left.shape) == 2 and len(right.shape) == 2 + assert left.shape == right.shape + num_pixels: float = float(left.shape[0] * left.shape[1]) + return (np.sum(np.abs(left.astype(np.int32) - right.astype(np.int32))) / num_pixels) + + +def estimated_kernel_size(frame_width: int, frame_height: int) -> int: + """Estimate kernel size based on video resolution.""" + size: int = 4 + round(math.sqrt(frame_width * frame_height) / 192) + if size % 2 == 0: + size += 1 + return size + +_kernel = None + +def _detect_edges(lum: np.ndarray) -> np.ndarray: + global _kernel + """Detect edges using the luma channel of a frame. + Arguments: + lum: 2D 8-bit image representing the luma channel of a frame. + Returns: + 2D 8-bit image of the same size as the input, where pixels with values of 255 + represent edges, and all other pixels are 0. + """ + # Initialize kernel. + if _kernel is None: + kernel_size = estimated_kernel_size(lum.shape[1], lum.shape[0]) + _kernel = np.ones((kernel_size, kernel_size), np.uint8) + + # Estimate levels for thresholding. + sigma: float = 1.0 / 3.0 + median = np.median(lum) + low = int(max(0, (1.0 - sigma) * median)) + high = int(min(255, (1.0 + sigma) * median)) + + # Calculate edges using Canny algorithm, and reduce noise by dilating the edges. + # This increases edge overlap leading to improved robustness against noise and slow + # camera movement. Note that very large kernel sizes can negatively affect accuracy. + edges = cv2.Canny(lum, low, high) + return cv2.dilate(edges, _kernel) + +#--------------------------------- + +def detect_edges(img_path, mask_path, is_invert_mask): + im = cv2.imread(img_path) + if mask_path: + mask = cv2.imread(mask_path)[:,:,0] + mask = mask[:, :, np.newaxis] + im = im * ( (mask == 0) if is_invert_mask else (mask > 0) ) +# im = im * (mask/255) +# im = im.astype(np.uint8) +# cv2.imwrite( os.path.join( os.path.dirname(mask_path) , "tmp.png" ) , im) + + hue, sat, lum = cv2.split(cv2.cvtColor( im , cv2.COLOR_BGR2HSV)) + return _detect_edges(lum) + +def get_mask_path_of_img(img_path, mask_dir): + img_basename = os.path.basename(img_path) + mask_path = os.path.join( mask_dir , img_basename ) + return mask_path if os.path.isfile( mask_path ) else None + +def analyze_key_frames(png_dir, mask_dir, th, min_gap, max_gap, add_last_frame, is_invert_mask): + keys = [] + + frames = sorted(glob.glob( os.path.join(png_dir, "[0-9]*.png") )) + + key_frame = frames[0] + keys.append( int(os.path.splitext(os.path.basename(key_frame))[0]) ) + key_edges = detect_edges( key_frame, get_mask_path_of_img( key_frame, mask_dir ), is_invert_mask ) + gap = 0 + + for frame in frames: + gap += 1 + if gap < min_gap: + continue + + edges = detect_edges( frame, get_mask_path_of_img( frame, mask_dir ), is_invert_mask ) + + delta = mean_pixel_distance( edges, key_edges ) + + _th = th * (max_gap - gap)/max_gap + + if _th < delta: + basename_without_ext = os.path.splitext(os.path.basename(frame))[0] + keys.append( int(basename_without_ext) ) + key_frame = frame + key_edges = edges + gap = 0 + + if add_last_frame: + basename_without_ext = os.path.splitext(os.path.basename(frames[-1]))[0] + last_frame = int(basename_without_ext) + if not last_frame in keys: + keys.append( last_frame ) + + return keys + +def remove_pngs_in_dir(path): + if not os.path.isdir(path): + return + + pngs = glob.glob( os.path.join(path, "*.png") ) + for png in pngs: + os.remove(png) + +def ebsynth_utility_stage2(dbg, project_args, key_min_gap, key_max_gap, key_th, key_add_last_frame, is_invert_mask): + dbg.print("stage2") + dbg.print("") + + _, original_movie_path, frame_path, frame_mask_path, org_key_path, _, _ = project_args + + remove_pngs_in_dir(org_key_path) + os.makedirs(org_key_path, exist_ok=True) + + fps = 30 + clip = cv2.VideoCapture(original_movie_path) + if clip: + fps = clip.get(cv2.CAP_PROP_FPS) + clip.release() + + if key_min_gap == -1: + key_min_gap = int(10 * fps/30) + else: + key_min_gap = max(1, key_min_gap) + key_min_gap = int(key_min_gap * fps/30) + + if key_max_gap == -1: + key_max_gap = int(300 * fps/30) + else: + key_max_gap = max(10, key_max_gap) + key_max_gap = int(key_max_gap * fps/30) + + key_min_gap,key_max_gap = (key_min_gap,key_max_gap) if key_min_gap < key_max_gap else (key_max_gap,key_min_gap) + + dbg.print("fps: {}".format(fps)) + dbg.print("key_min_gap: {}".format(key_min_gap)) + dbg.print("key_max_gap: {}".format(key_max_gap)) + dbg.print("key_th: {}".format(key_th)) + + keys = analyze_key_frames(frame_path, frame_mask_path, key_th, key_min_gap, key_max_gap, key_add_last_frame, is_invert_mask) + + dbg.print("keys : " + str(keys)) + + for k in keys: + filename = str(k).zfill(5) + ".png" + shutil.copy( os.path.join( frame_path , filename) , os.path.join(org_key_path, filename) ) + + + dbg.print("") + dbg.print("Keyframes are output to [" + org_key_path + "]") + dbg.print("") + dbg.print("[Ebsynth Utility]->[configuration]->[stage 2]->[Threshold of delta frame edge]") + dbg.print("The smaller this value, the narrower the keyframe spacing, and if set to 0, the keyframes will be equally spaced at the value of [Minimum keyframe gap].") + dbg.print("") + dbg.print("If you do not like the selection, you can modify it manually.") + dbg.print("(Delete keyframe, or Add keyframe from ["+frame_path+"])") + + dbg.print("") + dbg.print("completed.") + diff --git a/ebsynth_utility/stage3_5.py b/ebsynth_utility/stage3_5.py new file mode 100644 index 0000000000000000000000000000000000000000..26607a7465c43c6d5c3aaf9477f5e1d664eeb4b8 --- /dev/null +++ b/ebsynth_utility/stage3_5.py @@ -0,0 +1,178 @@ +import cv2 +import os +import glob +import shutil +import numpy as np +from PIL import Image + +from color_matcher import ColorMatcher +from color_matcher.normalizer import Normalizer + +def resize_img(img, w, h): + if img.shape[0] + img.shape[1] < h + w: + interpolation = interpolation=cv2.INTER_CUBIC + else: + interpolation = interpolation=cv2.INTER_AREA + + return cv2.resize(img, (w, h), interpolation=interpolation) + +def get_pair_of_img(img_path, target_dir): + img_basename = os.path.basename(img_path) + target_path = os.path.join( target_dir , img_basename ) + return target_path if os.path.isfile( target_path ) else None + +def remove_pngs_in_dir(path): + if not os.path.isdir(path): + return + + pngs = glob.glob( os.path.join(path, "*.png") ) + for png in pngs: + os.remove(png) + +def get_pair_of_img(img, target_dir): + img_basename = os.path.basename(img) + + pair_path = os.path.join( target_dir , img_basename ) + if os.path.isfile( pair_path ): + return pair_path + print("!!! pair of "+ img + " not in " + target_dir) + return "" + +def get_mask_array(mask_path): + if not mask_path: + return None + mask_array = np.asarray(Image.open( mask_path )) + if mask_array.ndim == 2: + mask_array = mask_array[:, :, np.newaxis] + mask_array = mask_array[:,:,:1] + mask_array = mask_array/255 + return mask_array + +def color_match(imgs, ref_image, color_matcher_method, dst_path): + cm = ColorMatcher(method=color_matcher_method) + + i = 0 + total = len(imgs) + + for fname in imgs: + + img_src = Image.open(fname) + img_src = Normalizer(np.asarray(img_src)).type_norm() + + img_src = cm.transfer(src=img_src, ref=ref_image, method=color_matcher_method) + + img_src = Normalizer(img_src).uint8_norm() + Image.fromarray(img_src).save(os.path.join(dst_path, os.path.basename(fname))) + + i += 1 + print("{0}/{1}".format(i, total)) + + imgs = sorted( glob.glob( os.path.join(dst_path, "*.png") ) ) + + +def ebsynth_utility_stage3_5(dbg, project_args, color_matcher_method, st3_5_use_mask, st3_5_use_mask_ref, st3_5_use_mask_org, color_matcher_ref_type, color_matcher_ref_image): + dbg.print("stage3.5") + dbg.print("") + + _, _, frame_path, frame_mask_path, org_key_path, img2img_key_path, _ = project_args + + backup_path = os.path.join( os.path.join( img2img_key_path, "..") , "st3_5_backup_img2img_key") + backup_path = os.path.normpath(backup_path) + + if not os.path.isdir( backup_path ): + dbg.print("{0} not found -> create backup.".format(backup_path)) + os.makedirs(backup_path, exist_ok=True) + + imgs = glob.glob( os.path.join(img2img_key_path, "*.png") ) + + for img in imgs: + img_basename = os.path.basename(img) + pair_path = os.path.join( backup_path , img_basename ) + shutil.copy( img , pair_path) + + else: + dbg.print("{0} found -> Treat the images here as originals.".format(backup_path)) + + org_imgs = sorted( glob.glob( os.path.join(backup_path, "*.png") ) ) + head_of_keyframe = org_imgs[0] + + # open ref img + ref_image = color_matcher_ref_image + if not ref_image: + dbg.print("color_matcher_ref_image not set") + + if color_matcher_ref_type == 0: + #'original video frame' + dbg.print("select -> original video frame") + ref_image = Image.open( get_pair_of_img(head_of_keyframe, frame_path) ) + else: + #'first frame of img2img result' + dbg.print("select -> first frame of img2img result") + ref_image = Image.open( get_pair_of_img(head_of_keyframe, backup_path) ) + + ref_image = np.asarray(ref_image) + + if st3_5_use_mask_ref: + mask = get_pair_of_img(head_of_keyframe, frame_mask_path) + if mask: + mask_array = get_mask_array( mask ) + ref_image = ref_image * mask_array + ref_image = ref_image.astype(np.uint8) + + else: + dbg.print("select -> color_matcher_ref_image") + ref_image = np.asarray(ref_image) + + + if color_matcher_method in ('mvgd', 'hm-mvgd-hm'): + sample_img = Image.open(head_of_keyframe) + ref_image = resize_img( ref_image, sample_img.width, sample_img.height ) + + ref_image = Normalizer(ref_image).type_norm() + + + if st3_5_use_mask_org: + tmp_path = os.path.join( os.path.join( img2img_key_path, "..") , "st3_5_tmp") + tmp_path = os.path.normpath(tmp_path) + dbg.print("create {0} for masked original image".format(tmp_path)) + + remove_pngs_in_dir(tmp_path) + os.makedirs(tmp_path, exist_ok=True) + + for org_img in org_imgs: + image_basename = os.path.basename(org_img) + + org_image = np.asarray(Image.open(org_img)) + + mask = get_pair_of_img(org_img, frame_mask_path) + if mask: + mask_array = get_mask_array( mask ) + org_image = org_image * mask_array + org_image = org_image.astype(np.uint8) + + Image.fromarray(org_image).save( os.path.join( tmp_path, image_basename ) ) + + org_imgs = sorted( glob.glob( os.path.join(tmp_path, "*.png") ) ) + + + color_match(org_imgs, ref_image, color_matcher_method, img2img_key_path) + + + if st3_5_use_mask or st3_5_use_mask_org: + imgs = sorted( glob.glob( os.path.join(img2img_key_path, "*.png") ) ) + for img in imgs: + mask = get_pair_of_img(img, frame_mask_path) + if mask: + mask_array = get_mask_array( mask ) + bg = get_pair_of_img(img, frame_path) + bg_image = np.asarray(Image.open( bg )) + fg_image = np.asarray(Image.open( img )) + + final_img = fg_image * mask_array + bg_image * (1-mask_array) + final_img = final_img.astype(np.uint8) + + Image.fromarray(final_img).save(img) + + dbg.print("") + dbg.print("completed.") + diff --git a/ebsynth_utility/stage5.py b/ebsynth_utility/stage5.py new file mode 100644 index 0000000000000000000000000000000000000000..c70b358a3f06b946ca2344c8e0a384da7e5f3ba8 --- /dev/null +++ b/ebsynth_utility/stage5.py @@ -0,0 +1,279 @@ +import cv2 +import re +import os +import glob +import time + +from sys import byteorder +import binascii +import numpy as np + +SYNTHS_PER_PROJECT = 15 + +def to_float_bytes(f): + if byteorder == 'little': + return np.array([ float(f) ], dtype=' cur_clip+1 else -1 + + current_frame = 0 + + print(str(start) + " -> " + str(end+1)) + + black_img = np.zeros_like( cv2.imread( os.path.join(out_dirs[cur_clip]['path'], str(start).zfill(number_of_digits) + ".png") ) ) + + for i in range(start, end+1): + + print(str(i) + " / " + str(end)) + + if next_clip == -1: + break + + if i in range( out_dirs[cur_clip]['startframe'], out_dirs[cur_clip]['endframe'] +1): + pass + elif i in range( out_dirs[next_clip]['startframe'], out_dirs[next_clip]['endframe'] +1): + cur_clip = next_clip + next_clip = cur_clip+1 if len(out_dirs) > cur_clip+1 else -1 + if next_clip == -1: + break + else: + ### black + # front ... none + # back ... none + cv2.imwrite( os.path.join(tmp_dir, filename) , black_img) + current_frame = i + continue + + filename = str(i).zfill(number_of_digits) + ".png" + + # front ... cur_clip + # back ... next_clip or none + + if i in range( out_dirs[next_clip]['startframe'], out_dirs[next_clip]['endframe'] +1): + # front ... cur_clip + # back ... next_clip + img_f = cv2.imread( os.path.join(out_dirs[cur_clip]['path'] , filename) ) + img_b = cv2.imread( os.path.join(out_dirs[next_clip]['path'] , filename) ) + + back_rate = (i - out_dirs[next_clip]['startframe'])/ max( 1 , (out_dirs[cur_clip]['endframe'] - out_dirs[next_clip]['startframe']) ) + + img = cv2.addWeighted(img_f, 1.0 - back_rate, img_b, back_rate, 0) + + cv2.imwrite( os.path.join(tmp_dir , filename) , img) + else: + # front ... cur_clip + # back ... none + filename = str(i).zfill(number_of_digits) + ".png" + shutil.copy( os.path.join(out_dirs[cur_clip]['path'] , filename) , os.path.join(tmp_dir , filename) ) + + current_frame = i + + + start2 = current_frame+1 + + print(str(start2) + " -> " + str(end+1)) + + for i in range(start2, end+1): + filename = str(i).zfill(number_of_digits) + ".png" + shutil.copy( os.path.join(out_dirs[cur_clip]['path'] , filename) , os.path.join(tmp_dir , filename) ) + + ### create movie + movie_base_name = time.strftime("%Y%m%d-%H%M%S") + if is_invert_mask: + movie_base_name = "inv_" + movie_base_name + + nosnd_path = os.path.join(project_dir , movie_base_name + get_ext(export_type)) + + start = out_dirs[0]['startframe'] + end = out_dirs[-1]['endframe'] + + create_movie_from_frames( tmp_dir, start, end, number_of_digits, fps, nosnd_path, export_type) + + dbg.print("exported : " + nosnd_path) + + if export_type == "mp4": + + with_snd_path = os.path.join(project_dir , movie_base_name + '_with_snd.mp4') + + if trying_to_add_audio(original_movie_path, nosnd_path, with_snd_path, tmp_dir): + dbg.print("exported : " + with_snd_path) + + dbg.print("") + dbg.print("completed.") + diff --git a/ebsynth_utility/stage8.py b/ebsynth_utility/stage8.py new file mode 100644 index 0000000000000000000000000000000000000000..8f0f1121d5a13996452cb513967d5642eb7d6a4a --- /dev/null +++ b/ebsynth_utility/stage8.py @@ -0,0 +1,146 @@ +import os +import re +import subprocess +import glob +import shutil +import time +import cv2 +import numpy as np +import itertools +from extensions.ebsynth_utility.stage7 import create_movie_from_frames, get_ext, trying_to_add_audio + +def clamp(n, smallest, largest): + return sorted([smallest, n, largest])[1] + +def resize_img(img, w, h): + if img.shape[0] + img.shape[1] < h + w: + interpolation = interpolation=cv2.INTER_CUBIC + else: + interpolation = interpolation=cv2.INTER_AREA + + return cv2.resize(img, (w, h), interpolation=interpolation) + +def merge_bg_src(base_frame_dir, bg_dir, frame_mask_path, tmp_dir, bg_type, mask_blur_size, mask_threshold, fg_transparency): + + base_frames = sorted(glob.glob( os.path.join(base_frame_dir, "[0-9]*.png"), recursive=False) ) + + bg_frames = sorted(glob.glob( os.path.join(bg_dir, "*.png"), recursive=False) ) + + def bg_frame(total_frames): + bg_len = len(bg_frames) + + if bg_type == "Loop": + itr = itertools.cycle(bg_frames) + while True: + yield next(itr) + else: + for i in range(total_frames): + yield bg_frames[ int(bg_len * (i/total_frames))] + + bg_itr = bg_frame(len(base_frames)) + + for base_frame in base_frames: + im = cv2.imread(base_frame) + bg = cv2.imread( next(bg_itr) ) + bg = resize_img(bg, im.shape[1], im.shape[0] ) + + basename = os.path.basename(base_frame) + mask_path = os.path.join(frame_mask_path, basename) + mask = cv2.imread(mask_path)[:,:,0] + + mask[mask < int( 255 * mask_threshold )] = 0 + + if mask_blur_size > 0: + mask_blur_size = mask_blur_size//2 * 2 + 1 + mask = cv2.GaussianBlur(mask, (mask_blur_size, mask_blur_size), 0) + mask = mask[:, :, np.newaxis] + + fore_rate = (mask/255) * (1 - fg_transparency) + + im = im * fore_rate + bg * (1- fore_rate) + im = im.astype(np.uint8) + cv2.imwrite( os.path.join( tmp_dir , basename ) , im) + +def extract_frames(movie_path , output_dir, fps): + png_path = os.path.join(output_dir , "%05d.png") + # ffmpeg.exe -ss 00:00:00 -y -i %1 -qscale 0 -f image2 -c:v png "%05d.png" + subprocess.call("ffmpeg -ss 00:00:00 -y -i " + movie_path + " -vf fps=" + str( round(fps, 2)) + " -qscale 0 -f image2 -c:v png " + png_path, shell=True) + +def ebsynth_utility_stage8(dbg, project_args, bg_src, bg_type, mask_blur_size, mask_threshold, fg_transparency, export_type): + dbg.print("stage8") + dbg.print("") + + if not bg_src: + dbg.print("Fill [configuration] -> [stage 8] -> [Background source]") + return + + project_dir, original_movie_path, _, frame_mask_path, _, _, _ = project_args + + fps = 30 + clip = cv2.VideoCapture(original_movie_path) + if clip: + fps = clip.get(cv2.CAP_PROP_FPS) + clip.release() + + dbg.print("bg_src: {}".format(bg_src)) + dbg.print("bg_type: {}".format(bg_type)) + dbg.print("mask_blur_size: {}".format(mask_blur_size)) + dbg.print("export_type: {}".format(export_type)) + dbg.print("fps: {}".format(fps)) + + base_frame_dir = os.path.join( project_dir , "crossfade_tmp") + + if not os.path.isdir(base_frame_dir): + dbg.print(base_frame_dir + " base frame not found") + return + + tmp_dir = os.path.join( project_dir , "bg_merge_tmp") + if os.path.isdir(tmp_dir): + shutil.rmtree(tmp_dir) + os.mkdir(tmp_dir) + + ### create frame imgs + if os.path.isfile(bg_src): + bg_ext = os.path.splitext(os.path.basename(bg_src))[1] + if bg_ext == ".mp4": + bg_tmp_dir = os.path.join( project_dir , "bg_extract_tmp") + if os.path.isdir(bg_tmp_dir): + shutil.rmtree(bg_tmp_dir) + os.mkdir(bg_tmp_dir) + + extract_frames(bg_src, bg_tmp_dir, fps) + + bg_src = bg_tmp_dir + else: + dbg.print(bg_src + " must be mp4 or directory") + return + elif not os.path.isdir(bg_src): + dbg.print(bg_src + " must be mp4 or directory") + return + + merge_bg_src(base_frame_dir, bg_src, frame_mask_path, tmp_dir, bg_type, mask_blur_size, mask_threshold, fg_transparency) + + ### create movie + movie_base_name = time.strftime("%Y%m%d-%H%M%S") + movie_base_name = "merge_" + movie_base_name + + nosnd_path = os.path.join(project_dir , movie_base_name + get_ext(export_type)) + + merged_frames = sorted(glob.glob( os.path.join(tmp_dir, "[0-9]*.png"), recursive=False) ) + start = int(os.path.splitext(os.path.basename(merged_frames[0]))[0]) + end = int(os.path.splitext(os.path.basename(merged_frames[-1]))[0]) + + create_movie_from_frames(tmp_dir,start,end,5,fps,nosnd_path,export_type) + + dbg.print("exported : " + nosnd_path) + + if export_type == "mp4": + + with_snd_path = os.path.join(project_dir , movie_base_name + '_with_snd.mp4') + + if trying_to_add_audio(original_movie_path, nosnd_path, with_snd_path, tmp_dir): + dbg.print("exported : " + with_snd_path) + + dbg.print("") + dbg.print("completed.") + diff --git a/ebsynth_utility/style.css b/ebsynth_utility/style.css new file mode 100644 index 0000000000000000000000000000000000000000..611823d399aee6bc253026433ff3d7b9f8149e76 --- /dev/null +++ b/ebsynth_utility/style.css @@ -0,0 +1,39 @@ +#ebs_info_area { + border: black 2px solid; + border-radius: 5px; + font-size: 15px; + margin: 10px; + padding: 10px; +} + +#ebs_configuration_tab1>div{ + margin: 5px; + padding: 5px; +} + +#ebs_configuration_tab2>div{ + margin: 5px; + padding: 5px; +} + +#ebs_configuration_tab3_5>div{ + margin: 5px; + padding: 5px; +} + +#ebs_configuration_tab7>div{ + margin: 5px; + padding: 5px; +} + +#ebs_configuration_tab8>div{ + margin: 5px; + padding: 5px; +} + +#ebs_configuration_tab_etc>div{ + margin: 5px; + padding: 5px; +} + + diff --git a/microsoftexcel-controlnet/__pycache__/preload.cpython-310.pyc b/microsoftexcel-controlnet/__pycache__/preload.cpython-310.pyc index 62f8c63cdd5cadb60cddf7915982f79e22a5b474..bf59e705d5dfbc2579eaf42c374c3b81702f1313 100644 Binary files a/microsoftexcel-controlnet/__pycache__/preload.cpython-310.pyc and b/microsoftexcel-controlnet/__pycache__/preload.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/annotator/__pycache__/util.cpython-310.pyc b/microsoftexcel-controlnet/annotator/__pycache__/util.cpython-310.pyc index c589ccb9d9efe8cc25cce1886d501be067cfe1a2..bd4cdfd38b97b32babd5d08ae00e89062ed7af98 100644 Binary files a/microsoftexcel-controlnet/annotator/__pycache__/util.cpython-310.pyc and b/microsoftexcel-controlnet/annotator/__pycache__/util.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/adapter.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/adapter.cpython-310.pyc index d22c524e0bf5ce8c3627a4b2c42ecc06677d94d1..cfe6e7212701dc0c4bc5ae6de9cdc950a39afa6a 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/adapter.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/adapter.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/api.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/api.cpython-310.pyc index d099bba12ab3d15d5aa79ebd633b518f0f535ce8..59f8074bc10de40ef6bfe98bc8ab9074f4921b17 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/api.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/api.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/batch_hijack.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/batch_hijack.cpython-310.pyc index 1512d2123e9ea4b464ea2273f4d3822a25f82f13..5e1045086797819098cbc7e1146de6ccbda5389c 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/batch_hijack.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/batch_hijack.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/cldm.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/cldm.cpython-310.pyc index 129b6e9415e0eb10ea5007b9eaa773f1114662b7..38947cf90e2719e5cb7252c0849b5167ba20fdcf 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/cldm.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/cldm.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/controlnet.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/controlnet.cpython-310.pyc index daf338f3a4c20f067ca653179ace042d44e4d6d9..7d82497607d48844387ec351f560b0617ea97d10 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/controlnet.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/controlnet.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/controlnet_version.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/controlnet_version.cpython-310.pyc index 938afb622309b466e2c3c741232d06dc0b5fc862..3d9a71c322fc440d2d5e19db9b3ca4848645d3a4 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/controlnet_version.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/controlnet_version.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/external_code.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/external_code.cpython-310.pyc index bc5aa33c03f2ecdc17c10ceaf22df3c0395eb029..c215eb51f556afb0fab1279b13f9d5c2dfe332fe 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/external_code.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/external_code.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/global_state.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/global_state.cpython-310.pyc index e72eaf3f8615b4449228b01e34e8eb74eddb77ea..d0d0ff69b1d95e635c0d648c7d0112a9ee0b76db 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/global_state.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/global_state.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/hook.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/hook.cpython-310.pyc index 8dca8f603dcfd9852495cbbb24b240fe7b48479d..75bc3ccd03cec854a1b3988e6653a754f425476f 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/hook.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/hook.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/lvminthin.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/lvminthin.cpython-310.pyc index b01561ee6f0b54f08c04af47f2c547f7336e41c3..c8467ff499dc5b8294ee564d1f847e80bdd825a0 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/lvminthin.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/lvminthin.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/movie2movie.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/movie2movie.cpython-310.pyc index ad5f3393a4cd28ebfe9d417185d6e193b9f3fa7c..e56669315d9735ea5cf827b45bf72386b2517099 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/movie2movie.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/movie2movie.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/processor.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/processor.cpython-310.pyc index 0c4998b2a9df23d32a5f329385d2e32a50f18d79..4b85b9d43bcb823e1dc0bed16d48758e68149622 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/processor.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/processor.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/utils.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/utils.cpython-310.pyc index d214f9ccebf8691304b2ee40d122948777ed2e69..51df97080f3a2aa8865e6786d8511fd8e999a0b3 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/utils.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/utils.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/__pycache__/xyz_grid_support.cpython-310.pyc b/microsoftexcel-controlnet/scripts/__pycache__/xyz_grid_support.cpython-310.pyc index a90b2f2b66bb3a66a761949dac87f611046409b6..410b13bd0b51324428a809da1f4e48d943bb9555 100644 Binary files a/microsoftexcel-controlnet/scripts/__pycache__/xyz_grid_support.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/__pycache__/xyz_grid_support.cpython-310.pyc differ diff --git a/microsoftexcel-controlnet/scripts/ui/__pycache__/controlnet_ui_group.cpython-310.pyc b/microsoftexcel-controlnet/scripts/ui/__pycache__/controlnet_ui_group.cpython-310.pyc index df6c653668ddd097ef98c5c2c235f122c9b0aae2..d4bd318032f6f23650a894f38f352cd2a62efeb2 100644 Binary files a/microsoftexcel-controlnet/scripts/ui/__pycache__/controlnet_ui_group.cpython-310.pyc and b/microsoftexcel-controlnet/scripts/ui/__pycache__/controlnet_ui_group.cpython-310.pyc differ diff --git a/microsoftexcel-supermerger/scripts/__pycache__/supermerger.cpython-310.pyc b/microsoftexcel-supermerger/scripts/__pycache__/supermerger.cpython-310.pyc index 70008de725465132965d0b53a29e04b9f93b829f..722b392b9547c67d7f426b9d9f0f3524ecdb01de 100644 Binary files a/microsoftexcel-supermerger/scripts/__pycache__/supermerger.cpython-310.pyc and b/microsoftexcel-supermerger/scripts/__pycache__/supermerger.cpython-310.pyc differ diff --git a/microsoftexcel-supermerger/scripts/mergers/__pycache__/mergers.cpython-310.pyc b/microsoftexcel-supermerger/scripts/mergers/__pycache__/mergers.cpython-310.pyc index 05d1f968249a940ad79673898f1df17182dbc4b9..c3bb2b807085f5b05b102c7d0dba119d316080a0 100644 Binary files a/microsoftexcel-supermerger/scripts/mergers/__pycache__/mergers.cpython-310.pyc and b/microsoftexcel-supermerger/scripts/mergers/__pycache__/mergers.cpython-310.pyc differ diff --git a/microsoftexcel-supermerger/scripts/mergers/__pycache__/model_util.cpython-310.pyc b/microsoftexcel-supermerger/scripts/mergers/__pycache__/model_util.cpython-310.pyc index 8bc5439b9a095278bc83375269ff75a2cbf5d2d4..4ae198789464ecbc5829bafe79e0925d04a55931 100644 Binary files a/microsoftexcel-supermerger/scripts/mergers/__pycache__/model_util.cpython-310.pyc and b/microsoftexcel-supermerger/scripts/mergers/__pycache__/model_util.cpython-310.pyc differ diff --git a/microsoftexcel-supermerger/scripts/mergers/__pycache__/pluslora.cpython-310.pyc b/microsoftexcel-supermerger/scripts/mergers/__pycache__/pluslora.cpython-310.pyc index 543be050237e0ff3080c36c9e27ff419cdd6eada..9babbb7bfe5e7d0c9408417275f719c094f5bcb2 100644 Binary files a/microsoftexcel-supermerger/scripts/mergers/__pycache__/pluslora.cpython-310.pyc and b/microsoftexcel-supermerger/scripts/mergers/__pycache__/pluslora.cpython-310.pyc differ diff --git a/microsoftexcel-supermerger/scripts/mergers/__pycache__/xyplot.cpython-310.pyc b/microsoftexcel-supermerger/scripts/mergers/__pycache__/xyplot.cpython-310.pyc index af6bf122071ee1fde7b97257b2a7b47b73287129..756f9d98289c98cbcb98c1d010e232afc10715b1 100644 Binary files a/microsoftexcel-supermerger/scripts/mergers/__pycache__/xyplot.cpython-310.pyc and b/microsoftexcel-supermerger/scripts/mergers/__pycache__/xyplot.cpython-310.pyc differ diff --git a/microsoftexcel-tunnels/__pycache__/preload.cpython-310.pyc b/microsoftexcel-tunnels/__pycache__/preload.cpython-310.pyc index 1933b19792290e3b5f64a7d2b984d62866a579bf..0b6f93df32806769c77fa79efb6484bd602679d4 100644 Binary files a/microsoftexcel-tunnels/__pycache__/preload.cpython-310.pyc and b/microsoftexcel-tunnels/__pycache__/preload.cpython-310.pyc differ diff --git a/microsoftexcel-tunnels/scripts/__pycache__/ssh_tunnel.cpython-310.pyc b/microsoftexcel-tunnels/scripts/__pycache__/ssh_tunnel.cpython-310.pyc index d299dc8875b10ffae11ba4497867bf3e8d9bee04..df6619b69b17ae60018c552abab6ede089fada58 100644 Binary files a/microsoftexcel-tunnels/scripts/__pycache__/ssh_tunnel.cpython-310.pyc and b/microsoftexcel-tunnels/scripts/__pycache__/ssh_tunnel.cpython-310.pyc differ diff --git a/microsoftexcel-tunnels/scripts/__pycache__/try_cloudflare.cpython-310.pyc b/microsoftexcel-tunnels/scripts/__pycache__/try_cloudflare.cpython-310.pyc index 00c027e72e63848d96457940a635190ed1e191fa..53f4605d739eb3c25c171d7db5b8cedf44d12442 100644 Binary files a/microsoftexcel-tunnels/scripts/__pycache__/try_cloudflare.cpython-310.pyc and b/microsoftexcel-tunnels/scripts/__pycache__/try_cloudflare.cpython-310.pyc differ diff --git a/openpose-editor/scripts/__pycache__/main.cpython-310.pyc b/openpose-editor/scripts/__pycache__/main.cpython-310.pyc index 65853f4ada9fccf4bc796dfbb95e0b10cda64bd6..41ca9efa88a5248b948f0adb49250afe731e2dac 100644 Binary files a/openpose-editor/scripts/__pycache__/main.cpython-310.pyc and b/openpose-editor/scripts/__pycache__/main.cpython-310.pyc differ diff --git a/openpose-editor/scripts/openpose/__pycache__/body.cpython-310.pyc b/openpose-editor/scripts/openpose/__pycache__/body.cpython-310.pyc index fdb48d3157511fa6e4b6290e2fd8e6ae68c91742..da51746e0067d6797fbf20a0131bdee0f5201ac6 100644 Binary files a/openpose-editor/scripts/openpose/__pycache__/body.cpython-310.pyc and b/openpose-editor/scripts/openpose/__pycache__/body.cpython-310.pyc differ diff --git a/openpose-editor/scripts/openpose/__pycache__/model.cpython-310.pyc b/openpose-editor/scripts/openpose/__pycache__/model.cpython-310.pyc index 1b42a54324f3a701e7ad47fd2320a73334018420..bffc7e6490f2882ec93043e81960b29da496558d 100644 Binary files a/openpose-editor/scripts/openpose/__pycache__/model.cpython-310.pyc and b/openpose-editor/scripts/openpose/__pycache__/model.cpython-310.pyc differ diff --git a/openpose-editor/scripts/openpose/__pycache__/util.cpython-310.pyc b/openpose-editor/scripts/openpose/__pycache__/util.cpython-310.pyc index c504d97f9867de8841c66e19daff166ad8628d62..3d53d4786459d55b71a13318cf04494b24dbbc5d 100644 Binary files a/openpose-editor/scripts/openpose/__pycache__/util.cpython-310.pyc and b/openpose-editor/scripts/openpose/__pycache__/util.cpython-310.pyc differ diff --git a/posex/common/__pycache__/posex_utils.cpython-310.pyc b/posex/common/__pycache__/posex_utils.cpython-310.pyc index 3a97fa546c0d98da62a07f8dae8a03c5291430ba..4e0336995bdf05f41b5c5e1f28bc9efe1861c006 100644 Binary files a/posex/common/__pycache__/posex_utils.cpython-310.pyc and b/posex/common/__pycache__/posex_utils.cpython-310.pyc differ diff --git a/posex/scripts/__pycache__/posex.cpython-310.pyc b/posex/scripts/__pycache__/posex.cpython-310.pyc index 38cd18f138bfde76fb3a8fbc1d4135ebe486df58..8195f3f1744384d112575b5d595d429916c2df65 100644 Binary files a/posex/scripts/__pycache__/posex.cpython-310.pyc and b/posex/scripts/__pycache__/posex.cpython-310.pyc differ diff --git a/sd-3dmodel-loader/scripts/__pycache__/main.cpython-310.pyc b/sd-3dmodel-loader/scripts/__pycache__/main.cpython-310.pyc index 0167f187cfb6f9aec9b024fa5e6a2ce113bb0af6..917491a3645119a9a5764b830c0a3c73269a844a 100644 Binary files a/sd-3dmodel-loader/scripts/__pycache__/main.cpython-310.pyc and b/sd-3dmodel-loader/scripts/__pycache__/main.cpython-310.pyc differ diff --git a/sd-webui-3d-open-pose-editor/downloads/config.json b/sd-webui-3d-open-pose-editor/downloads/config.json index 1ee2a873ae8448a04897adc042f577f34c2693e3..738bf49f41ce38b35f58d5d595a3ff0f96636ae0 100644 --- a/sd-webui-3d-open-pose-editor/downloads/config.json +++ b/sd-webui-3d-open-pose-editor/downloads/config.json @@ -1 +1 @@ -{"assets": {"models/hand.fbx": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/models/hand.fbx?v=1685857223.112788", "models/foot.fbx": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/models/foot.fbx?v=1685857223.1107879", "src/poses/data.bin": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/src/poses/data.bin?v=1685857223.1317894", "pose_landmark_full.tflite": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_landmark_full.tflite?v=1685857223.7808428", "pose_web.binarypb": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_web.binarypb?v=1685857223.8048449", "pose_solution_packed_assets.data": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_packed_assets.data?v=1685857223.8708503", "pose_solution_simd_wasm_bin.wasm": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_simd_wasm_bin.wasm?v=1685857223.954857", "pose_solution_packed_assets_loader.js": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_packed_assets_loader.js?v=1685857223.977859", "pose_solution_simd_wasm_bin.js": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_simd_wasm_bin.js?v=1685857224.012862"}} \ No newline at end of file +{"assets": {"models/hand.fbx": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/models/hand.fbx?v=1687338775.0501518", "models/foot.fbx": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/models/foot.fbx?v=1687338775.0471516", "src/poses/data.bin": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/src/poses/data.bin?v=1687338868.781117", "pose_landmark_full.tflite": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_landmark_full.tflite?v=1687338817.7202463", "pose_web.binarypb": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_web.binarypb?v=1687338775.0451515", "pose_solution_packed_assets.data": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_packed_assets.data?v=1687338822.8687432", "pose_solution_simd_wasm_bin.wasm": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_simd_wasm_bin.wasm?v=1687338819.4364119", "pose_solution_packed_assets_loader.js": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_packed_assets_loader.js?v=1687338775.0431511", "pose_solution_simd_wasm_bin.js": "/file=/content/microsoftexcel/extensions/sd-webui-3d-open-pose-editor/downloads/pose/0.5.1675469404/pose_solution_simd_wasm_bin.js?v=1687338775.0441513"}} \ No newline at end of file diff --git a/sd-webui-3d-open-pose-editor/scripts/__pycache__/openpose_editor.cpython-310.pyc b/sd-webui-3d-open-pose-editor/scripts/__pycache__/openpose_editor.cpython-310.pyc index a8458e1bc461ceea921f55c9560eca694b25b605..80bb7fe6fe7baa64fa5a758f9e6128c250d5c805 100644 Binary files a/sd-webui-3d-open-pose-editor/scripts/__pycache__/openpose_editor.cpython-310.pyc and b/sd-webui-3d-open-pose-editor/scripts/__pycache__/openpose_editor.cpython-310.pyc differ diff --git a/sd-webui-lora-block-weight/scripts/__pycache__/lora_block_weight.cpython-310.pyc b/sd-webui-lora-block-weight/scripts/__pycache__/lora_block_weight.cpython-310.pyc index ab0e94633241e1e623f0d904007dd1350387d412..5d97d5c4922cb50f7ce14e09d55212298d65648d 100644 Binary files a/sd-webui-lora-block-weight/scripts/__pycache__/lora_block_weight.cpython-310.pyc and b/sd-webui-lora-block-weight/scripts/__pycache__/lora_block_weight.cpython-310.pyc differ diff --git a/sd-webui-stablesr/scripts/__pycache__/stablesr.cpython-310.pyc b/sd-webui-stablesr/scripts/__pycache__/stablesr.cpython-310.pyc index 45cd2a9529321fd9741898a1207857f8bedcb836..8211d8d1f99ed6f16d8d2a697ca259bc053e8142 100644 Binary files a/sd-webui-stablesr/scripts/__pycache__/stablesr.cpython-310.pyc and b/sd-webui-stablesr/scripts/__pycache__/stablesr.cpython-310.pyc differ diff --git a/sd-webui-stablesr/srmodule/__pycache__/attn.cpython-310.pyc b/sd-webui-stablesr/srmodule/__pycache__/attn.cpython-310.pyc index 6047b4349ddf48719d75fa6cedffcf168ca87b7b..29712e7125f54136d2061d501a6bde82584f4da1 100644 Binary files a/sd-webui-stablesr/srmodule/__pycache__/attn.cpython-310.pyc and b/sd-webui-stablesr/srmodule/__pycache__/attn.cpython-310.pyc differ diff --git a/sd-webui-stablesr/srmodule/__pycache__/colorfix.cpython-310.pyc b/sd-webui-stablesr/srmodule/__pycache__/colorfix.cpython-310.pyc index ca13bff8f7ebb770a97b46a593e42ef44a3e845f..b6d082cdbe5a4fbc37182ce478e0f934d03c2eeb 100644 Binary files a/sd-webui-stablesr/srmodule/__pycache__/colorfix.cpython-310.pyc and b/sd-webui-stablesr/srmodule/__pycache__/colorfix.cpython-310.pyc differ diff --git a/sd-webui-stablesr/srmodule/__pycache__/spade.cpython-310.pyc b/sd-webui-stablesr/srmodule/__pycache__/spade.cpython-310.pyc index 621238acdbac296649cd2f0b928310f9ea081cda..f47eb89343e493e7c601ca24ddf3421806546bb0 100644 Binary files a/sd-webui-stablesr/srmodule/__pycache__/spade.cpython-310.pyc and b/sd-webui-stablesr/srmodule/__pycache__/spade.cpython-310.pyc differ diff --git a/sd-webui-stablesr/srmodule/__pycache__/struct_cond.cpython-310.pyc b/sd-webui-stablesr/srmodule/__pycache__/struct_cond.cpython-310.pyc index 0837c2bb3e7946bd110d4e91d499129ab73c5bef..7762aad510263282281529ea71219bfe39d45860 100644 Binary files a/sd-webui-stablesr/srmodule/__pycache__/struct_cond.cpython-310.pyc and b/sd-webui-stablesr/srmodule/__pycache__/struct_cond.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/__pycache__/__init__.cpython-310.pyc b/sd_feed/newtype_v3/__pycache__/__init__.cpython-310.pyc index 0f821a50a6bc1c6f479dcf1fc380188100efd47a..e4a8a23ec20d71989ae27f4ff7d56efe0265804a 100644 Binary files a/sd_feed/newtype_v3/__pycache__/__init__.cpython-310.pyc and b/sd_feed/newtype_v3/__pycache__/__init__.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/__pycache__/images.cpython-310.pyc b/sd_feed/newtype_v3/__pycache__/images.cpython-310.pyc index 0ea2f9b5bda336389990c292a2de2533743e8d6f..44c8389a56a7984f7be87773a1f965a43e62af19 100644 Binary files a/sd_feed/newtype_v3/__pycache__/images.cpython-310.pyc and b/sd_feed/newtype_v3/__pycache__/images.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/__pycache__/locs.cpython-310.pyc b/sd_feed/newtype_v3/__pycache__/locs.cpython-310.pyc index 181e9baf27bdf57b70da63d05565b1e341f94a5f..5202f398280ddc906a118d6ee9e87020a9154db3 100644 Binary files a/sd_feed/newtype_v3/__pycache__/locs.cpython-310.pyc and b/sd_feed/newtype_v3/__pycache__/locs.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/__pycache__/lora.cpython-310.pyc b/sd_feed/newtype_v3/__pycache__/lora.cpython-310.pyc index bf02685ab213ec6df8fb3bbbb88ac0540f718b1b..5f74478a6bbd17a4997f059291fdb1eb3ef12228 100644 Binary files a/sd_feed/newtype_v3/__pycache__/lora.cpython-310.pyc and b/sd_feed/newtype_v3/__pycache__/lora.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/__pycache__/lora_types.cpython-310.pyc b/sd_feed/newtype_v3/__pycache__/lora_types.cpython-310.pyc index 60a96b27eaf9e3a1c3edd6aedc7477fdf5d6b291..6caa0687f1219d3d2ccfe15724e2d6fc09981a54 100644 Binary files a/sd_feed/newtype_v3/__pycache__/lora_types.cpython-310.pyc and b/sd_feed/newtype_v3/__pycache__/lora_types.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/__pycache__/newtype_v3.cpython-310.pyc b/sd_feed/newtype_v3/__pycache__/newtype_v3.cpython-310.pyc index de3fb5276a98950334da9fdbca7bbf7b4961f095..e21e423d137aa71fbf908369325ac77a32bd0c2f 100644 Binary files a/sd_feed/newtype_v3/__pycache__/newtype_v3.cpython-310.pyc and b/sd_feed/newtype_v3/__pycache__/newtype_v3.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/__pycache__/users.cpython-310.pyc b/sd_feed/newtype_v3/__pycache__/users.cpython-310.pyc index 04bb2dfdec912a94b35560981850a7bc382f81c3..76a2130851cd387e79cff4e934ab8164f4f0bf4b 100644 Binary files a/sd_feed/newtype_v3/__pycache__/users.cpython-310.pyc and b/sd_feed/newtype_v3/__pycache__/users.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/tabs/__pycache__/__init__.cpython-310.pyc b/sd_feed/newtype_v3/tabs/__pycache__/__init__.cpython-310.pyc index 46db3330fe6464ef260274623fe4723a7b2f0d67..3fc2415bd8c512d1b9ae9f7ceffea367175fef71 100644 Binary files a/sd_feed/newtype_v3/tabs/__pycache__/__init__.cpython-310.pyc and b/sd_feed/newtype_v3/tabs/__pycache__/__init__.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/tabs/__pycache__/feed.cpython-310.pyc b/sd_feed/newtype_v3/tabs/__pycache__/feed.cpython-310.pyc index 565232b049427452450ae958bae584ede115ae07..e17e4c66eb793cf75d6cf474b9598501b7e5d83c 100644 Binary files a/sd_feed/newtype_v3/tabs/__pycache__/feed.cpython-310.pyc and b/sd_feed/newtype_v3/tabs/__pycache__/feed.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/utils/__pycache__/__init__.cpython-310.pyc b/sd_feed/newtype_v3/utils/__pycache__/__init__.cpython-310.pyc index 0dfc89609db0a4bd33c7046b16f6ba26c3a21f2e..d8b1558ff9f750d1a9f271ddefbe832b0a7cb321 100644 Binary files a/sd_feed/newtype_v3/utils/__pycache__/__init__.cpython-310.pyc and b/sd_feed/newtype_v3/utils/__pycache__/__init__.cpython-310.pyc differ diff --git a/sd_feed/newtype_v3/utils/__pycache__/string_utils.cpython-310.pyc b/sd_feed/newtype_v3/utils/__pycache__/string_utils.cpython-310.pyc index 2bc9367627bf51082f85470185ed69b242b9e1fb..afe19ad74b76fe36fbbfff53c8a246cd35c88a89 100644 Binary files a/sd_feed/newtype_v3/utils/__pycache__/string_utils.cpython-310.pyc and b/sd_feed/newtype_v3/utils/__pycache__/string_utils.cpython-310.pyc differ diff --git a/sd_feed/scripts/__pycache__/main.cpython-310.pyc b/sd_feed/scripts/__pycache__/main.cpython-310.pyc index 447d68b3953faee2f990b19aebfa02c6af901c53..577ce8ac5ed4ad68995582ca17d2817645299d6b 100644 Binary files a/sd_feed/scripts/__pycache__/main.cpython-310.pyc and b/sd_feed/scripts/__pycache__/main.cpython-310.pyc differ diff --git a/stable-diffusion-webui-composable-lora/__pycache__/composable_lora.cpython-310.pyc b/stable-diffusion-webui-composable-lora/__pycache__/composable_lora.cpython-310.pyc index 806609e2326c052a0a3e12fd45b9f1235ca528d6..794cdd8d51f3df353af4ed4fd7e3d78d536f2d4d 100644 Binary files a/stable-diffusion-webui-composable-lora/__pycache__/composable_lora.cpython-310.pyc and b/stable-diffusion-webui-composable-lora/__pycache__/composable_lora.cpython-310.pyc differ diff --git a/stable-diffusion-webui-composable-lora/scripts/__pycache__/composable_lora_script.cpython-310.pyc b/stable-diffusion-webui-composable-lora/scripts/__pycache__/composable_lora_script.cpython-310.pyc index c664309d7ffa4f5f0ed3521c860a895dee352fbb..849f44ddac837ef627026f63de982e0505039048 100644 Binary files a/stable-diffusion-webui-composable-lora/scripts/__pycache__/composable_lora_script.cpython-310.pyc and b/stable-diffusion-webui-composable-lora/scripts/__pycache__/composable_lora_script.cpython-310.pyc differ diff --git a/stable-diffusion-webui-images-browser/scripts/__pycache__/image_browser.cpython-310.pyc b/stable-diffusion-webui-images-browser/scripts/__pycache__/image_browser.cpython-310.pyc index 85377e5457bc069369faa58120a2ff01df58ab61..981061538fc6f5729e6a93806def7c56d5d78041 100644 Binary files a/stable-diffusion-webui-images-browser/scripts/__pycache__/image_browser.cpython-310.pyc and b/stable-diffusion-webui-images-browser/scripts/__pycache__/image_browser.cpython-310.pyc differ diff --git a/stable-diffusion-webui-images-browser/scripts/wib/__pycache__/wib_db.cpython-310.pyc b/stable-diffusion-webui-images-browser/scripts/wib/__pycache__/wib_db.cpython-310.pyc index 6951102f981d893523fba60c8a5ebe6fd6c3d035..5dc9f381511d518ce2f846912353f994e2a3e895 100644 Binary files a/stable-diffusion-webui-images-browser/scripts/wib/__pycache__/wib_db.cpython-310.pyc and b/stable-diffusion-webui-images-browser/scripts/wib/__pycache__/wib_db.cpython-310.pyc differ diff --git a/ultimate-upscale-for-automatic1111/scripts/__pycache__/ultimate-upscale.cpython-310.pyc b/ultimate-upscale-for-automatic1111/scripts/__pycache__/ultimate-upscale.cpython-310.pyc index 034e238c89901db69d1a7e25da061c67dc7ae660..63b6439bbfb96218b734701bfa1d929fc2e49165 100644 Binary files a/ultimate-upscale-for-automatic1111/scripts/__pycache__/ultimate-upscale.cpython-310.pyc and b/ultimate-upscale-for-automatic1111/scripts/__pycache__/ultimate-upscale.cpython-310.pyc differ