http://andersource.dev/2024/05/30/spraying-digital-graffiti.html andersource [ ] postsaboutprojectsworkshopsRSS Spraying Digital Graffiti May 30, 2024 Background My partner has an upcoming show she's promoting. She has a couple of nice pictures with colored walls, and I thought it could be cool to spray "digital graffiti" on the walls with info about the show. My options were: * Plaster semi-transparent text on the wall, which would look cheap and lousy * Fiddle with some gen AI, but where's the sport in that * Spend half a day to figure out how to do that computationally Since we're here, we all know which option I chose! Let's get rolling Say we have this nice picture of a textured wall (Image by kues1 on Freepik): Textured cement wall First, using image color replacement from a while back, we can paint the wall pink, which already looks neat: Textured cement wall painted pink Now, suppose we also have a binary mask depicting the graffiti we'd like to spray: Binary mask with circle in the center We can use the mask to mix between the two wall images (original and painted): Textured cement wall with pink circle That looks pretty nice, but the mask boundary is too smooth and detached from the texture. Real paint would behave slightly differently based on the bumps in the cement. How do we modify the boundary? Pathfinding to the rescue We want to modify the mask's contour, such that it follows the original contour pretty closely, but also tries to avoid crossing "high-energy" areas of the image. We can use edge detection to define these high-energy areas. Defining the path To define the path, we sample the contour uniformly, but then wiggle the indices a bit to start from low-energy areas of the image. Edge map with contour and sampled points Next we stitch the new contour as a series of paths using skimage's graph.route_through_array, which finds the minimum-cost-path through a cost landscape. To define the cost landscape, we compose the edge map with the distsance transform of the original contour. We can vary the weighing between the distance transform and the edge map to balance how strictly we want the new contour to adhere to the original one. Here's an example cost landscape: Cost map Here's the minimal cost path in this case: Cost map with minimum cost path Finally, after we obtain the new contour, we can use it to define a new mask and mix the images: Textured cement wall with pink circle The effect is subtle but, in my opinion, makes the result a lot more realistic. Gimme the code * Image color replacement is here * Rest of the process described is here. This code is specifically tailored to what I wanted, but it shouldn't be hard to make it more general Textured wall with text graffiti * hi@andersource.dev * andersource Experiments and musings (c) andersource 2019 - 2024