การดึงค่าสี RGB ในรูปภาพ จากการสร้างเว็บไซต์ โดยใช้ Python และ K-Means Clustering
Published: 2024-04-13 12:01:13


โดยบทความนี้เราจะมารู้วิธีการสร้างเว็บไซต์และการใช้ image processing ที่ช่วยทำให้เราสามารถดึงค่าสี RGB จากรูปต้นแบบ


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97g6uua8hlc1d48g8edr.png)


อย่างแรกเรามาทำความรู้จักกับ K-Means Clustering กันก่อนนะคะว่ามันคืออะไร ใช้ทำอะไรได้บ้าง

K-Means Clustering เป็นเทคนิคการแบ่งกลุ่มข้อมูลหรือการจัดกลุ่มข้อมูลเป็นกลุ่มย่อยๆ โดยการจัดกลุ่มเหล่านี้ตามลักษณะความคล้ายคลึงกันของข้อมูลที่มีอยู่ในชุดข้อมูล ซึ่งหนึ่งในวิธีการที่นิยมใช้กันมากคือ K-Means Clustering algorithm ซึ่งมีการทำงานอย่างง่ายด้วยขั้นตอนดังนี้:

1.เลือกจุดเริ่มต้น (Centroids):กำหนดจำนวน K ของกลุ่มที่ต้องการแบ่งข้อมูลเป็น โดยจุดเหล่านี้เรียกว่า Centroids และจะถูกสุ่มเลือกจากข้อมูลที่มีอยู่ในชุดข้อมูล

2.กำหนดข้อมูลใกล้เคียง (Assign Data Points):กำหนดข้อมูลแต่ละจุดในชุดข้อมูลให้เข้ากลุ่มที่ใกล้เคียงกับ Centroids ที่มีระยะทางสั้นที่สุด

3.ปรับ Centroids (Update Centroids):คำนวณ Centroids ใหม่โดยหาจุดกึ่งกลางของข้อมูลในแต่ละกลุ่มที่สร้างขึ้นจากขั้นตอนก่อนหน้า

4.ทำซ้ำขั้นตอนที่2 และ3:ทำขั้นตอนการกำหนดข้อมูลใกล้เคียงและปรับ Centroids ไปเรื่อยๆ จนกว่าจะไม่มีการเปลี่ยนแปลงใน Centroids หรือมีการเปลี่ยนแปลงน้อยมาก

5.สิ้นสุด:เมื่อไม่มีการเปลี่ยนแปลงใน Centroids หรือมีการเปลี่ยนแปลงน้อยมากแล้ว ขั้นตอนการแบ่งกลุ่มจะสิ้นสุดลงและแสดงผลลัพธ์ที่ได้จากการแบ่งกลุ่ม

K-Means Clustering ใช้งานได้หลากหลายในงานที่ต้องการ การจัดกลุ่มข้อมูล เช่น:

1.Segmentation: การแบ่งภาพออกเป็นพื้นที่ที่มีลักษณะคล้ายกัน เช่น ภาพการแยกสีหรือภาพการแยกส่วนของวัตถุ

2.Customer Segmentation: การจัดกลุ่มลูกค้าตามลักษณะที่คล้ายกัน เพื่อให้บริการหรือโปรโมชั่นที่เหมาะสมกับแต่ละกลุ่ม

3.Anomaly Detection: การตรวจจับข้อมูลที่ไม่เป็นไปตามแบบแผนหรือไม่ปกติภายในกลุ่ม

4.การจัดการการสแปม: การจัดกลุ่มอีเมล์เพื่อตรวจจับและป้องกันการสแปม

5.Market Analysis: การวิเคราะห์ทัศนคติของตลาดโดยการแบ่งกลุ่มผู้บริโภคตามพฤติกรรมการซื้อสินค้า

โดย K-Means Clustering เป็นเทคนิคที่มีประสิทธิภาพในการจัดกลุ่มข้อมูลใหญ่ๆ และไม่มีการระบุค่าเป้าหมายของกลุ่มล่วงหน้า แต่อาจต้องการ การทดลองหรือการปรับพารามิเตอร์ เพื่อหาจำนวนกลุ่มที่เหมาะสมที่สุดสำหรับชุดข้อมูลที่กำหนด

หลังจากที่เราทำความรู้จัก K-Means Clustering กันพอเล็กน้อยแล้ว จากนี้เรามาเริ่มการสร้างเว็บไซต์ในการดึงค่าสี RGB กันเลยย

**ขั้นตอนที่ 1 ** เริ่มต้นโดยการ import Flask และฟังก์ชันอื่นๆที่จำเป็นทั้งหมด ดังรูปภาพ 

```
from flask import Flask, render_template, request
import cv2
import numpy as np
import os
import base64
```
**ขั้นตอนที่ 2 ** กำหนดตัวแปร app ให้เป็น Flask object ซึ่งใช้ในการสร้างและรันแอปพลิเคชัน Flask

```
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
```
 และเราจะกำหนดตัวแปร UPLOAD_FOLDER เพื่อระบุโฟลเดอร์ที่ใช้เก็บไฟล์ที่ผู้ใช้อัปโหลด

**ขั้นตอนที่ 3 ** สร้างฟังก์ชัน get_dominant_colors เพื่อคำนวณสีหลักจากภาพที่ให้มา โดยใช้วิธีการ K-Means clustering

```
def get_dominant_colors(image_path, num_colors=5):
    """
    Extracts the dominant colors from an image using K-Means clustering.

    Args:
        image_path: Path to the image file.
        num_colors: Number of dominant colors to extract (default: 5).

    Returns:
        A list of dominant colors as RGB tuples.
    """
```

**ขั้นตอนที่ 4** สร้างฟังก์ชันการดึงสีของภาพ 

```
 img = cv2.imread(image_path)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    pixels = img_rgb.reshape((-1, 3))
    pixels = np.float32(pixels)
    _, _, centers = cv2.kmeans(pixels, num_colors, None, criteria=(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0), 
                               attempts=10, flags=cv2.KMEANS_RANDOM_CENTERS)
    dominant_colors = [tuple(color) for color in centers.astype(np.uint8)]
    return dominant_colors
```

**ขั้นตอนที่ 5** สร้าง("/") ที่เชื่อมโยงกับฟังก์ชัน index() ซึ่งเป็นหน้าหลักของเว็บแอปพลิเคชัน โดยฟังก์ชันนี้จะดำเนินการตามขั้นตอนดังนี้:
   1.เมื่อมีการส่งคำขอ POST มาที่เส้นทาง "/" (หน้าหลัก) โดยมีการอัปโหลด      ไฟล์รูปภาพ และรับค่าจำนวนสีหลักที่ต้องการ
   2.จะทำการบันทึกไฟล์รูปภาพที่อัปโหลดลงในโฟลเดอร์ UPLOAD_FOLDER
   3.คำนวณสีหลักจากภาพที่อัปโหลดด้วยฟังก์ชัน get_dominant_colors()
   4.แปลงรูปภาพเป็นรหัส base64 เพื่อแสดงในหน้าเว็บ
   5.แสดงผลหน้า HTML โดยส่งข้อมูลภาพที่แปลงแล้วและสีหลักที่คำนวณได้ไปยัง template ชื่อ index.html

```
@app.route('/', methods=['GET', 'POST'])
def index():
    preview_image = None
    dominant_colors = None
    num_colors = 5 

    if request.method == 'POST':
        if 'file' not in request.files:
            return render_template('index.html', error='No file part')

        file = request.files['file']
        if file.filename == '':
            return render_template('index.html', error='No selected file')

        num_colors_str = request.form.get('num_colors', '')
        if num_colors_str:
            try:
                num_colors = int(num_colors_str)
            except ValueError:
                return render_template('index.html', error='Invalid number of colors')

        if file:
            filename = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
            file.save(filename)
            with open(filename, "rb") as img_file:
                preview_image = base64.b64encode(img_file.read()).decode('utf-8')
            dominant_colors = get_dominant_colors(filename, num_colors)
            os.remove(filename)

    return render_template('index.html', preview_image=preview_image, dominant_colors=dominant_colors, num_colors=num_colors)


if __name__ == '__main__':
    app.run(debug=True)
```


หลังจากที่เราสร้างไฟล์ Python ขึ้นมาได้แล้ว ขั้นตอนต่อไปเราจะมาเริ่มสร้างส่วนของหน้าเว็บเพจ โดยใช้ภาษา HTML

**ขั้นตอนที่ 1 ** การสร้างหน้าเว็บเพจ โดยมีฟังก์ชันดังนี้

1.โครงสร้างพื้นฐานของหน้าเว็บเพจ `(<!DOCTYPE html>)` และปิด `tag </html>` โดยมีส่วนประกอบต่างๆ เช่น `<head>` และ `<body>` เพื่อกำหนดรายละเอียดของหน้าเว็บและเนื้อหาของเว็บ
2.`<style> `กำหนดการตั้งค่ารูปแบบและขนาดของข้อความ การจัดวางข้อความ และการจัดรูปแบบของกล่องสี

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dominant Colors Extractor</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin: 0;
            padding: 0;
        }
        h1 {
            margin-top: 50px;
            margin-bottom: 20px;
            color: #333;
        }
        .upload-form {
            margin-bottom: 50px;
        }
        #preview {
            margin-bottom: 20px;
        }
        #preview img {
            max-width: 500px;
            max-height: 500px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        #color-preview {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
        }
        .color-box {
            width: 100px;
            height: 100px;
            margin: 10px;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            cursor: pointer;
        }
        .color-info {
            margin-top: 5px;
            font-size: 14px;
            color: #666;
        }
    </style>
```
3.`<script>` มีฟังก์ชัน `JavaScript` ชื่อ `copyColor()`มีหน้าที่คัดลอกค่าสี RGB จากกล่องสีเมื่อถูกคลิก และแสดงข้อความแจ้งเตือนว่าค่าสีถูกคัดลอกไปยังคลิปบอร์ดเรียบร้อยแล้ว
```
  <script>
    function copyColor(rgb) {
        const rgbNumbers = rgb.match(/\d+/g).join(' ');
        const textarea = document.createElement('textarea');
        textarea.value = rgbNumbers;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
        const alertMessage = 'RGB values copied to clipboard: ' + rgbNumbers;
        const alertTimeout = 2000; 
        const alertElement = document.createElement('div');
        alertElement.classList.add('alert');
        alertElement.textContent = alertMessage;
        document.body.appendChild(alertElement);
        setTimeout(() => {
            document.body.removeChild(alertElement);
        }, alertTimeout);
    }
    </script>
```

4.`<form>` มีฟอร์มที่ให้ผู้ใช้เลือกไฟล์ภาพและกำหนดจำนวนสีหลักที่ต้องการให้คำนวณ ซึ่งมีปุ่มสำหรับส่งข้อมูลไปยังเซิร์ฟเวอร์

```
</head>
<body>
    <h1>Dominant Colors Extractor</h1>
    <div class="upload-form">
        <form method="post" enctype="multipart/form-data">
            <input type="file" name="file" accept=".png, .jpg, .jpeg">
            <br><br>
            <label for="num_colors">Number of Colors:</label>
            <input type="number" id="num_colors" name="num_colors" value="{{ num_colors }}" min="1" max="20">
            <br><br>
            <input type="submit" value="Upload">
        </form>
    </div>
```
5.`{% if ... %} และ {% for ... %} `ใช้ `Jinja template syntax` ในการแสดงผลภาพที่อัปโหลดและสีหลักที่คำนวณได้ โดยใช้ข้อมูลที่ได้รับจากการอัปโหลดและการประมวลผลในเซิร์ฟเวอร์ Flask หรือแสดงข้อความข้อผิดพลาดหากมีการร้องขอที่ไม่ถูกต้อง
```
 <div id="preview">
        {% if preview_image %}
            <img src="data:image/jpeg;base64,{{ preview_image }}" alt="Preview">
        {% endif %}
    </div>
    {% if dominant_colors %}
    <div id="color-preview">
        {% for color in dominant_colors %}
            <div class="color-box" style="background-color: rgb({{ color.0 }}, {{ color.1 }}, {{ color.2 }})"; onclick="copyColor('rgb({{ color.0 }}, {{ color.1 }}, {{ color.2 }})')">
                <div class="color-info">RGB: ({{ color.0 }}, {{ color.1 }}, {{ color.2 }})</div>
            </div>
        {% endfor %}
    </div>
    {% endif %}
    {% if error %}
        <p>{{ error }}</p>
    {% endif %}
</body>
</html>
``` 

สรุป 

เราจะเห็นได้ว่าเว็บเพจที่สร้างขึ้นนี้ สามารถทำให้สำหรับคนที่ต้องการที่จะดึงค่าสี RGB จากภาพได้อย่างง่ายๆ โดยไม่ต้องไปคำนวณหาค่าสีเอาเอง ซึ่งเว็บที่สร้างขึ้นนี้ก็จะดีกว่าการที่เราค่อยๆไปหาชาร์จสีใน google เพราะบางทีสีก็อาจจะไม่ตรงเอาสีที่เราต้องการ จากเว็บเราก็สามารถกำหนดได้ว่าเราต้องการกี่เฉดสีโดยเว็บจะดึงค่าสีที่มีความโดดเด่นที่สุด ดังตัวอย่างด้านบน 

ข้อมูลจาก
https://medium.com/technology-hits/reveal-the-dominant-colors-in-your-images-using-python-and-k-means-clustering-aa710625e096