Improving image classification accuracy for K-Pop Celeb Like Me

When I started developing K-Pop Celeb Like Me, I wanted the app to find a K-pop celebrity who looked similar to a user's photo. The app uses an image classification model trained with Google's Teachable Machine.

Getting the model to run inside a mobile app was only one part of the challenge. I also needed to prepare a useful training dataset, choose how to crop the images, compare training settings, and check whether the model behaved reasonably when I tested it with photos that were not part of the training process.

In this post, I will share the workflow I developed using Python and OpenCV, along with the experiments that helped me decide which model settings to use.


Why image preprocessing mattered

My first concern was how Teachable Machine processes training images.

For my image classification project, the model uses a square input image resized to 224 × 224 pixels. If I uploaded images with very different aspect ratios and compositions without checking them, important facial features could be cropped out, or the model could learn from inconsistent compositions.

For example, one image might show a celebrity's face close up, while another might show the same person from a distance with a large amount of background. A third image might place the face near the edge of the frame.

I wanted to reduce these inconsistencies while retaining enough variation for the model to learn from different photos.

My approach was to create a Python preprocessing script that detected faces, generated several square crops from each original image, and saved the results for manual inspection before training.


Collecting images with Python

I first collected images for each celebrity class. My main.py script allowed me to select an image search engine and specify the search keyword and target number of results.

For example, the configuration included:

ENGINE = "duckduckgo"
# ENGINE = "bing"
# ENGINE = "baidu"

keyword = "TWICE Jihyo"
count = 100

The script supported three collection methods:

  • DuckDuckGo image search, using ddgs

  • Bing image search, using icrawler

  • Baidu image search, using icrawler

The downloaded files were organized into a folder for each keyword. The script also checked the existing numeric filenames and continued numbering from the next available number. This helped me add more images without immediately overwriting the previously collected files.

For the DuckDuckGo option, the script retrieved image URLs, downloaded the image data using requests, and saved the files locally. The Bing and Baidu options used temporary download folders before moving the results into the destination folder.

This was a practical starting point because I could collect images for different celebrity classes using the same script instead of manually downloading every file.

However, automated collection did not guarantee that every image was suitable. Search results could contain low-quality images, unusual crops, group photos, or images that were not useful for identifying a person's facial appearance. That was why I treated image collection and dataset preparation as two separate steps.

A note on image rights: Finding an image through a search engine does not mean that it is free to reuse. For an app distributed publicly or commercially, image licensing, copyright, and the terms of the original source need to be considered separately from the technical process of downloading images.


Creating face-centered crops with OpenCV

After collecting the images, I used a second Python script, face_crop.py, to prepare them for training.

The script used OpenCV's built-in Haar Cascade frontal-face detector. It first converted each image to grayscale and applied histogram equalization before detecting faces.

face_cascade = cv2.CascadeClassifier(
    cv2.data.haarcascades
    + "haarcascade_frontalface_default.xml"
)

The purpose was to locate the face and use its bounding box as a reference for cropping. I did not want to create only one type of crop, because a very tight face crop could remove useful information about the person's overall appearance.

Instead, I defined three framing options.

Tight crop

{
    "name": "tight",
    "type": "face",
    "scale": 1.7,
    "vertical_offset": 0.0
}

This crop focused more closely on the face, including the forehead, ears, and jawline.

Wide crop

{
    "name": "wide",
    "type": "face",
    "scale": 2.4,
    "vertical_offset": 0.18
}

This crop included more of the surrounding area, allowing some of the neck and shoulders to remain visible. The vertical offset moved the crop center downward relative to the detected face.

Original center crop

{
    "name": "orig",
    "type": "center"
}

This option created a square crop from the center of the original image without using the detected face position. I kept this option because it preserved more of the original composition and could retain useful appearance information outside the face.

The three approaches gave me different views of the same original image. The intention was to make the training data less dependent on one particular framing style.


How the crop script handled difficult images

A face detector does not work perfectly on every image. Some photos have unusual angles, poor lighting, small faces, or multiple people.

I therefore included logic to handle cases that needed attention.

When no face was detected, the script saved the original image to a review folder rather than silently treating it as a successfully cropped image.

When multiple faces were detected, the script could either skip the image for review or select the largest detected face. I used the largest option for my workflow, which was intended to select the main face in group photos.

MULTI_FACE_STRATEGY = "largest"

This is a useful automation shortcut, but it is not foolproof. The largest detected face is not necessarily the celebrity I intended to classify, so manual inspection is still important.

The script also resized the generated crops to 400 × 400 pixels:

RESIZE_TO = 400

I used this size to standardize the processed files and make them easier to inspect. Teachable Machine would still resize the images for its model input, so the 400 × 400 size was a preprocessing choice rather than a requirement of the model itself.

The resulting files were stored under images_cropped, with the original class-folder structure preserved. Images that required inspection were stored separately under _review.

My workflow therefore became:

  1. Download the original images.

  2. Detect faces and generate different square crops.

  3. Review the images that could not be processed automatically.

  4. Manually remove unsuitable images.

  5. Check the balance and quality of the final classes.

  6. Upload the prepared dataset to Teachable Machine.

I aimed for roughly 300–400 suitable images per celebrity class. The important point was not simply reaching a particular image count, but making sure that the images were relevant and usable.

Because one original image could generate multiple crops, I also needed to review the final dataset rather than assume that every generated file added useful diversity.


Manual image review: An essential step

Automated image crawling and face cropping can save a lot of time, but they cannot guarantee that every image is suitable for training. I found that manual inspection of the entire dataset was essential before uploading the images to Teachable Machine.

I reviewed the cropped images one by one and removed unsuitable samples. Here are some of the things I checked:

  • Identity: Does the image actually show the person assigned to that class? I removed images containing a different person or multiple people when the intended subject could not be identified clearly.

  • Lighting and makeup: Is the lighting so unusual, or is the makeup so heavy, that the face looks significantly different from typical photos?

  • Facial expressions: Is the face distorted by an exaggerated expression, an unusual camera angle, or an extreme pose?

  • Face size and visibility: Is the face large and clear enough to provide useful training data, rather than appearing as a tiny figure in the image?

  • Overall image quality: Is the face sufficiently clear and unobstructed for the model to learn useful visual features?

These checks mattered because incorrectly labeled or unsuitable images could teach the model misleading patterns. For example, if images of another person accidentally ended up in a class, the model could learn features that did not belong to the intended celebrity. Similarly, if many images had extreme lighting or unusual expressions, the model might perform inconsistently on ordinary user photos.

After reviewing the images, I removed unsuitable samples and kept approximately 300–400 images per class. The goal was not simply to collect as many images as possible, but to build a dataset that was consistent and useful for training.

This step was time-consuming, but I learned that dataset quality is just as important as the model's training settings. Increasing the number of images or changing the number of epochs cannot reliably compensate for incorrect labels or poor-quality samples.


Comparing Teachable Machine training settings

Once the images were prepared, I trained a standard image model in Teachable Machine rather than an embedded image model.

For my initial experiments, I used ten classes and compared different epoch counts. I changed the batch size from 16 to 32 and kept the learning rate at its default value of 0.001.

The results I recorded for the female model were:

Epochs Recorded accuracy
20 0.733
40 0.748
50 0.765

These results showed that the recorded accuracy increased as I trained for more epochs. However, the improvement was relatively small compared with the additional training.

I also tested the model with a webcam rather than relying only on the training results. Occasionally, the model produced a strongly biased prediction. For example, a photo could be classified as BLACKPINK Jisoo with a confidence score of 100%, even when that result did not appear reasonable.

This made me question whether the highest recorded accuracy was necessarily the best choice for the app.

Why I chose 20 epochs for the female model

My development notes indicated that validation accuracy had begun to plateau after roughly 10–15 epochs, while training accuracy continued to improve.

When training accuracy keeps increasing but validation performance stops improving, one possible explanation is overfitting. The model may be learning details specific to its training images without improving its ability to classify new images.

Based on the validation behavior I observed and the biased predictions during my webcam tests, I chose the 20-epoch model for the female classes.

This was a practical decision based on my experiments, not proof that 20 epochs is universally better than 40 or 50. A different dataset or model could produce different results, and a controlled comparison on representative unseen photos would be needed to establish which model generalizes best.

Why I used 40 epochs for the male model

The male model behaved somewhat differently in my experiments. When I trained it for 40 epochs, the recorded accuracy reached approximately 0.79, which I considered a meaningful improvement for my project.

I still noticed occasional predictions that seemed biased, but the behavior did not appear as severe as it had with the female model during my informal tests.

I therefore used 40 epochs for the male model.

These two choices illustrate why I did not apply the same epoch count automatically to every model. I considered both the recorded metrics and the behavior I observed when testing the model with photos.


Training accuracy is not the same as real-world accuracy

One of the most important lessons from these experiments was that a single accuracy number cannot describe the complete user experience.

The model's behavior depends on more than the epoch count. The quality and variety of the training images, the balance between classes, the similarity between different celebrities, and the characteristics of the user's photo can all affect the result.

For example, if most images of one celebrity have a similar background, pose, or lighting, the model may learn those patterns instead of focusing primarily on facial appearance.

Similarly, if two celebrities have visually similar features, distinguishing them can be difficult even when the dataset contains hundreds of images per class.

My webcam tests helped me notice problems that were not obvious from the training metrics alone. However, these tests were informal and were not a substitute for a separate, representative validation dataset.

If I wanted to improve the model further, I would focus on reviewing the training data, increasing useful variation, checking class balance, and testing the model with unseen photos under different lighting conditions, poses, and backgrounds.


Exporting the model for a mobile app

After choosing the training settings, I exported the model for use in my mobile application.

Because K-Pop Celeb Like Me performs image classification on mobile devices, I selected TensorFlow Lite in Teachable Machine and chose the floating-point conversion option.

I chose this option because I wanted to prioritize model accuracy during my initial experiments rather than automatically selecting a quantized model. Quantization can reduce model size and improve performance in some situations, but it can also change model outputs, so the trade-off should be measured for the specific model.

The Edge TPU option was not the target for my ordinary iOS and Android app.

Exporting the model was only one part of the process. I still needed to integrate it into the React Native app and verify that image preprocessing during inference matched the preprocessing expected by the trained model.


What I learned from this process

The work involved more than collecting a large number of images and increasing the epoch count.

My practical workflow combined several steps:

  • Python scripts to collect and organize images.

  • OpenCV face detection to generate multiple square crops.

  • Manual review to remove unsuitable images and inspect detection failures.

  • Experiments with Teachable Machine training settings.

  • Informal webcam tests to look for suspiciously biased predictions.

  • TensorFlow Lite export for the mobile application.

The biggest lesson was that dataset quality and model evaluation need to be considered together. Increasing the number of training epochs can improve a training metric without necessarily improving the experience for someone using the app.

For my project, 20 epochs for the female model and 40 epochs for the male model were the choices that best matched my initial experiments. I would not treat these values as universal recommendations; they were decisions made for the particular datasets and behavior I observed.

If I continue improving the model, my next priority would be to refine the dataset and evaluate predictions more systematically with photos that were not used during training.

That is how I approached the first stage of improving image classification for K-Pop Celeb Like Me: not by relying on one setting, but by building a repeatable workflow and checking how each decision affected the model.

Comments

Mostly viewed post

My Mobile App Projects

About this blog

How I prepared my first web app for search engines