ondelva

← Blog

I Trained Create ML on Twelve Korean Paintings. It Learned the Paper

Before building Hwagong, I trained thirteen Create ML style transfer models on public domain Korean paintings and ran them against a filter I wrote by hand. The models came out fast and small, and every one of them looked like a vintage sepia filter.

Yunseon Hong10 min read

Hwagong is an iPhone app that redraws photos as Korean woodblock prints, ink wash, line drawings and palace colors. Before I wrote any app code I had to pick between two ways of building it. One was to write the styles by hand, as image processing code with parameters I could tune. The other was to train a model on real Korean paintings and let it learn the look.

The second option sounded like less work and a better result. Apple ships style transfer in Create ML, the model runs on the Neural Engine, and most of the paintings are centuries old and free to use. I gave both approaches the same sixteen test photos and compared the results in one sheet. The custom filters won by a wide margin, and the reason is specific enough to be worth writing down.

The setup: twelve paintings, thirty-six photos, one sheet

I used only paintings whose image files I could confirm were free to use and modify commercially. Eight came from The Met and the Cleveland Museum of Art, whose open access images are CC0. Four came from Gongu Madang, the Korea Copyright Commission’s archive of works anyone can reuse, where I opened each work’s page to check its terms. Three of those, all by Kim Hong-do, carry a CC BY credit requirement. A painting from 1751 is no use if the scan of it is someone else’s copyright.

Three of the training paintings: Jeong Seon's Inwangjesaekdo, a mountain in heavy ink above white mist; Kim Hong-do's Ssireum, a wrestling match ringed by spectators on bare paper; and an eight-panel screen of colored peonies.

The set covered most of what people mean by Korean painting: Jeong Seon’s true view landscapes, Kim Hong-do’s genre scenes, tiger paintings, folk paintings of peonies and lotuses, and a bird and flower screen by Jang Seung-eop. Kim Hong-do’s Ssireum above is credited to 한국데이터베이스산업진흥원 (Korea Data Agency) via Gongu Madang.

Training ran through a short Swift script around MLStyleTransfer. The content set was 36 CC0 photos from Wikimedia Commons. The test set was 16 more, spread over portraits, landscapes, Seoul streets, food, pets and night scenes. Every result went into an HTML sheet with photos as rows and styles as columns, so I could judge by looking across a row rather than by remembering.

Create ML trains at 512 by 512, and that was easy to fix

The first problem was mechanical. A Create ML style transfer model takes a fixed 512×512 input, so a 4:3 photo gets squashed into a square. The network is fully convolutional, though, so the fixed size is a property of the model description, not of the weights. Rewriting the spec with coremltools to accept anything from 256 to 4096 pixels on each side made it run at the photo’s own shape:

spec = ct.utils.load_spec(sys.argv[1])
r = fsu.NeuralNetworkImageSizeRange()
r.add_height_range((256, 4096))
r.add_width_range((256, 4096))
for name in ("image", "stylizedImage"):
    fsu.update_image_size_range(spec, feature_name=name, size_range=r)
ct.utils.save_spec(spec, sys.argv[2])

One small trap on the way: coremltools 9 had no native module built for Python 3.14 when I ran this. Editing a spec never touches the native module, so the script worked anyway, but don’t read the warning about the missing module as a sign that it can’t.

The parameter for texture scale is spelled textelDensity in the Swift API, with the extra “e”. Autocomplete will save you. Guessing will not.

Speed and size were never the problem

The models were cheap by every number I measured. Each one is 6.4 MB, whatever painting it learned. On an Apple silicon Mac, a 1280 px photo took about 145 ms and a 640 px photo about 37 ms. Training cost around 0.56 seconds per iteration at texel density 256, so a 400-iteration model was ready in under four minutes.

My own filters started slower. My first versions were Python and OpenCV on the CPU, at 195 to 340 ms for the same 1280 px photo. Only after porting them to Metal did they pass the models.

Time per 1280 pixel photo: Create ML models 143 to 156 ms on a Mac; custom filters 195 to 340 ms in Python on a Mac CPU, 53 to 118 ms in Metal on a Mac, and 29 to 51 ms in Metal on an iPhone 17 Pro. Time per photo, 1280 px on the long side Create ML model, Mac 143–156 ms Filters, Python CPU, Mac 195–340 ms Filters, Metal, Mac 53–118 ms Filters, Metal, iPhone 29–51 ms 0 100 200 300 ms

The Mac numbers come from my own runs on an Apple silicon Mac. The iPhone row is the final filters on an iPhone 17 Pro, measured on the device after the Metal port. I never ran the models on the phone, because by then they were already out.

If the comparison had been about speed, size or effort, the models would have won. Inference is one Core ML call. The filters needed custom Metal kernels, a resize kernel of my own after Apple’s Lanczos upscaler drew a dark line around the image edge, and days of tuning. The models lost on the one thing the app is for.

The models learned the paper, not the brushwork

I started the parameter search with Jeong Seon’s Inwangjesaekdo, because ink wash was the style I cared about most.

SettingsWhat came out
Strength 5, density 256, 500 iterationsA color grade. Saturation drops, everything turns blue gray. No ink, no brush
Strength 10, density 256, 128 or 64, 300 iterationsEmbossed, metallic noise, inverted tones, halos around subjects
Strength 7, density 256, 500 iterationsMilder than 10, same kind of noise
Same model, inference at 640 pxBigger texture, same character

There was no setting in between that looked like ink. My reading of why: the style loss in this kind of model matches texture statistics across the whole painting, and most of the area in a Korean ink landscape is empty paper or silk. The mountain and the pines are a minority of the pixels. So the model learns the grain of the silk and the age of the paper, and when you turn the strength up, that grain turns into noise. Apple does not document the loss, so treat that as an explanation that fits what I saw, not a fact about Create ML.

The rest of the paintings, trained at strength 6, density 256 and 400 iterations, made the pattern clear.

A dog lying on grass, six ways: the original photo; the custom ink wash filter, a white dog in black outline on pale gray wash with a red seal; the Ssireum model, a pale sepia sketch over textured grass; the Inwangjesaekdo model, gray embossed noise with the dog barely visible; the peonies model, pink and green blotches over the grass; and the lotus model, a faded brown photo with the dog's legs tinted green.

The dog shows the failure modes in one photo. The Ssireum model, the best of the thirteen, gives an old pen sketch on brown paper, but the grass becomes a field of embossed marks. Inwangjesaekdo at strength 7 buries the dog in gray relief. The peony screen threw pink and green blotches across the lawn, and the lotus painting faded the photo and tinted the dog’s legs green. The custom ink wash filter, second in the grid, is the only one where the grass disappears into a wash and the dog stays.

Training paintingResult across 16 photos
Kim Hong-do, SsireumPen sketch on old paper. Cleanest of the models. Embossed foliage
Tiger Family, folk paintingSepia etching. Good on streets, cats and food
Jeong Seon, Mt. Inwang (The Met); Samoje landscapeEtching on brown paper. Relief noise behind portraits
Kim Hong-do, WeavingFaint brown sketch
Kim Hong-do, Tiger under a PineMuddy orange cast
Tigers and Leopard, inkGray sketch, low contrast
Peonies screenPink and green blotches. Failed
Lotuses, Insects, and BirdsFaded grade, skin turned green. Failed
Jang Seung-eop, screenVertical stripe noise. Failed
Golden Rooster and HenSoft sepia grade. Little noise, little effect

The Jang Seung-eop result is the clearest case of the model learning the object instead of the art. The painting is a folding screen, and the stripes in the output are the panel seams.

Gwangjang Market in Seoul, six ways: the original; the custom ink wash, with the building in clean black lines and empty paper around it; the Ssireum model as a brown sketch; the Inwangjesaekdo model as gray relief; the peonies model with pink and teal noise; and the lotus model as a faded photo with a green cast.

On a street scene the best models do better. The market building survives as a brown etching. But look across the bottom row and ask which painting each one was trained on. I could not tell, and I was the one who trained them. Every model reads as “vintage sketch” or “sepia”. None of them reads as Korean.

What the custom filter does that the models could not

The ink wash filter is not clever. It flattens the photo, quantizes it into a few ink tones, draws edges, bleeds the edges with noise, and lays the result on a hanji paper texture with a red seal. What makes it look like ink wash is two decisions a texture model has no way to make.

The first is empty space. Apple’s Vision framework separates the subject from the background, in 60 to 170 ms per photo on my Mac, and the filter turns the background into pale wash instead of drawing it. That is the move a Joseon painter makes: paint the dog, leave the lawn as paper. A style transfer model treats every pixel the same, so the lawn gets as much texture as the dog.

The second is that every part of the look is a parameter I can see. Ink color, paper color, how far the ink bleeds and how much of the background fades are numbers in a preset file, and the same input with the same preset gives the same output byte for byte. With a model, strength is fixed at training time. In the app you could only blend the result back toward the original.

I put the two approaches in a scoring table at the review gate, and quality decided it: three custom styles rated four stars, the best models two. The app shipped with four custom styles and no models. A fifth, a folk painting style, is on hold because its flat color areas band on smooth gradients.

When I would look at models again

Not Create ML style transfer, and not this kind of model. What a photo needs to become an ink painting is a change of structure: which parts get drawn, which get left out, where the line goes. Texture transfer can’t make that change. I wrote down two conditions for trying again: a model that can restructure an image without paired training data, such as a diffusion model built for image translation, running on device through Core ML, at a size and speed that fit a free camera app. Neither was true when I made the call on 14 October 2026.

The thirteen experimental models are still on my disk, 83 MB of vintage sketch filters. The one I would keep is Ssireum, and only because it is a pleasant sepia. It just isn’t Korean painting.

Filed under Build log