๐ฑ [ํค์ ํค์ค์คํฌ] ๋์งํธ ์ฝ์๋ฅผ ์ํ AI ํ๋ฉด ํค์ ํค์ค์คํฌ
๐ฆ ๊น์ง์๐ฆ | ๐ป๋ฐ์์๐ป | ๐ณ์๋ค์ฐ๐ณ |
---|---|---|
Deeplearning / BE | Deeplearning / FE | Deeplearning / BE |
50์ธ ๋ฏธ๋ง ์ฌ์ฉ์ UI | 50์ธ ์ด์ ์ฌ์ฉ์ UI |
- ๋์งํธ ์ ํ๊ณผ ์จ๋ผ์ธ ์๋น์ค ํ์ฐ์ผ๋ก ์ธํด ๋ค์ํ ์ฐ๋ น๋์ ์ฌ๋๋ค์ด ํค์ค์คํฌ์ ๊ฐ์ ๋์งํธ ๊ธฐ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ๋น๋๊ฐ ์ฆ๊ฐํ๊ณ ์์.
- ๊ณ ๋ น์๋ ๋์งํธ ์ฝ์๋ค์ ์์ ํ
์คํธ์ ๋ณต์กํ UI ๋๋ฌธ์ ๋์งํธ ๊ธฐ๊ธฐ ์ฌ์ฉ์ ์ด๋ ค์์ ๊ฒช๊ณ ์์.
- ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ฐ๋ น๋์ ๋ง์ถ ์ฌ์ฉ์ ์ธํฐํ์ด์ค(UI)๋ฅผ ์ ๊ณตํ๋ ๊ธฐ์ ์ด ํ์ํจ.
- ์ธ๊ณต์ง๋ฅ๊ณผ ์๋ฉด ์ธ์ ๊ธฐ์ ์ ํ์ฉํ์ฌ ์ฐ๋ น๋๋ฅผ ์์ธกํ๊ณ , ์ด์ ๋ง์ถฐ UI์ ํ ์คํธ ๋ณผ๋ฅจ ํฌ๊ธฐ๋ฅผ ์๋์ผ๋ก ์กฐ์ ํ๋ ์์คํ ์ด ํจ๊ณผ์ ์ธ ๋์์ด ๋ ์ ์์.
- ๋ณธ ์ฐ๊ตฌ๊ฐ๋ฐ์์๋ ์๋ฉด ์ธ์ ๊ธฐ๋ฐ์ ์ฐ๋ น๋ ์์ธก ๊ธฐ์ ์ ๊ฐ๋ฐํ๊ณ , ์ด๋ฅผ ๋ฐํ์ผ๋ก ํค์ค์คํฌ UI์ ํ ์คํธ ํฌ๊ธฐ๋ฅผ ์๋ ์กฐ์ ํ๋ ์์คํ ์ ๊ตฌ์ถํ๋ ๊ฒ์ ๋ชฉํ๋ก ํจ.
- ์ถ๊ฐ์ ์ผ๋ก ์ฌ์ฉ์์ ์ฐ๋ น๋์ ๋ฐ๋ผ ์ต์ ํ๋ UI ์์๋ฅผ ์ ์ํ๊ณ , ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๊ณ ์ ํจ.
- ์ธ๊ณต์ง๋ฅ ๊ธฐ๋ฐ ์๋ฉด ์ธ์ ๋ฐ ์ฐ๋ น๋ ์์ธก ๊ธฐ์ ํ๋ณด.
- ์ฐ๋ น๋ ๋ง์ถคํ UI ์๋ ์กฐ์ ๊ธฐ์ ์ ํตํด ๋ค์ํ ์ฌ์ฉ์์๊ฒ ์ ํฉํ ํค์ค์คํฌ ํ๊ฒฝ ์ ๊ณต.
- ๋ค์ํ ๋์งํธ ๊ธฐ๊ธฐ ๋ฐ ์๋น์ค์ ์ ์ฉ ๊ฐ๋ฅํ ๋ง์ถคํ ์ธํฐํ์ด์ค ๊ธฐ์ ํ๋ณด.
- ์ฐ๋ น๋์ ๋ง์ถ ์ฌ์ฉ์ ๊ฒฝํ ์ ๊ณต์ ํตํด ๋์งํธ ์ฝ์์ ๋์งํธ ๊ธฐ๊ธฐ ์ ๊ทผ์ฑ ํฅ์.
- ๊ณ ๋ น์ ๋ฐ ๋์งํธ ์ฝ์์ ์ฌํ์ ยท๊ด๊ณ์ ๊ณ ๋ฆฝ๊ฐ ์ํ ๋ฐ ๋์งํธ ์์ธ ๋ฐฉ์ง.
- ์ฌ์ฉ์์ ๋ง์กฑ๋ ๋ฐ ํธ์์ฑ ์ฆ๋๋ฅผ ํตํ ๋์งํธ ์๋น์ค์ ์ด์ฉ๋ฅ ํฅ์.
๐Kiwoom-Kiosk ๋์ด ์์ธก ๋ชจ๋ธ ์์ธ๋ณด๊ธฐ
๐ ์ซ์๋ก ํํ๋ ์ธ์ข , ์ฑ๋ณ ๋ฐ์ดํฐ๋ฅผ ๋ฌธ์์ด๋ก ๋ณํ / ๋ฐ์ดํฐ์ ํด๋์์ ํ์ผ๋ช ์ ๋ถ์ํ์ฌ ๋์ด, ์ฑ๋ณ, ์ธ์ข ์ ๋ณด ์ถ์ถ
dataset_dict = {
'race_id': {
0: 'white',
1: 'black',
2: 'asian',
3: 'indian',
4: 'others'
},
'gender_id': {
0: 'male',
1: 'female'
}
}
def parse_dataset(dataset_path, ext='jpg'):
def parse_info_from_file(path):
try:
filename = os.path.split(path)[1]
filename = os.path.splitext(filename)[0]
age, gender, race, _ = filename.split('_')
return int(age), dataset_dict['gender_id'][int(gender)], dataset_dict['race_id'][int(race)]
except Exception as ex:
return None, None, None
files = glob(os.path.join(dataset_path, "*.%s" % ext))
records = []
for file in files:
info = parse_info_from_file(file)
records.append(info)
df = pd.DataFrame(records)
df['file'] = files
df.columns = ['age', 'gender', 'race', 'file']
df = df.dropna()
return df
df = parse_dataset('UTKFace')
df.head()
def load_and_preprocess_image(filepath, target_size=(200, 200)):
img = cv2.imread(filepath)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, target_size)
return img
files = df['file'].tolist()
ages = df['age'].tolist()
images = [load_and_preprocess_image(file) for file in files]
age = np.array(ages, dtype=np.int64)
images = np.array(images)
x_train_age, x_test_age, y_train_age, y_test_age = train_test_split(images, age, random_state=42, test_size=0.2)
model = Sequential([
Flatten(input_shape=(200, 200, 3)),
Dense(512, activation='relu'),
Dropout(0.5),
Dense(1, activation='linear')
])
model.compile(loss='mse', optimizer=Adam(), metrics=['mae'])
history = model.fit(
datagen.flow(x_train_age, y_train_age, batch_size=batch_size),
validation_data=(x_valid_age, y_valid_age),
epochs=epochs,
callbacks=callbacks
)
# ํ์ต ๊ฒฐ๊ณผ ์๊ฐํ
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.legend()
plt.title('Loss')
plt.subplot(1, 2, 2)
plt.plot(history.history['mae'], label='Training MAE')
plt.plot(history.history['val_mae'], label='Validation MAE')
plt.legend()
plt.title('Mean Absolute Error')
plt.show()
loss, mae = model.evaluate(x_test_age, y_test_age, verbose=0)
print(f'Test MAE: {mae}')
new_image = load_and_preprocess_image('test_image.jpg')
predicted_age = model.predict(np.expand_dims(new_image, axis=0))
print(f'Predicted Age: {predicted_age[0][0]}')
โ๏ธ Django์ CNN ๋ชจ๋ธ์ ์ฐ๊ฒฐํ์ฌ ์ด๋ฏธ์ง์์ ๋์ด๋ฅผ ์์ธกํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ๊ตฌ์ฑ
def custom_mse(y_true, y_pred):
return MeanSquaredError()(y_true, y_pred)
custom_mse
- ํ๊ท ์ ๊ณฑ ์ค์ฐจ(MSE)๋ฅผ ๊ณ์ฐํ๊ณ , ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋๋
MeanSquaredError
ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํ - ์ถํ ๋ชจ๋ธ์ ์ปดํ์ผํ ๋ ์ฌ์ฉ
model = load_model(settings.MODEL_PATH, custom_objects={'mse': custom_mse})
load_model
- ๋ฏธ๋ฆฌ ํ์ต๋ ๋ชจ๋ธ์ ๋ก๋ํ๊ณ
settings.MODEL_PATH
๋ ๋ชจ๋ธ ํ์ผ์ ๊ฒฝ๋ก๋ฅผ ๋ํ๋ด๋ฉฐ,custom_objects
์ธ์๋ฅผ ํตํด ์ฌ์ฉ์ ์ ์ ์์ค ํจ์์ธcustom_mse
๋ฅผ ๋ชจ๋ธ์ ์ถ๊ฐ - ๋ชจ๋ธ์ ๋ก๋ํ๋ฉด์ ์ฌ์ฉ์ ์ ์ ์์ค ํจ์๋ฅผ ์ ์๋ ์ด๋ฆ('mse')์ผ๋ก ์ธ์ํ๋๋ก ๊ตฌํ
input_shape = model.layers[0].input_shape
target_size = input_shape[1:3]
def load_and_preprocess_image(filepath, target_size=(200, 200)):
img = cv2.imread(filepath)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, target_size)
img = img.astype('float32') / 255.0 # ์ค์ผ์ผ๋ง
return img
input_shape
: ๋ชจ๋ธ์ ์ฒซ ๋ฒ์งธ ๋ ์ด์ด์ ์ ๋ ฅ ํํ๋ฅผ ํ์ธํ๋ ๋ณ์๋ก, ๋ชจ๋ธ์ ์ ๋ ฅํ ์ด๋ฏธ์ง์ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐ ์ฌ์ฉtarget_size
: ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ ์ ๋ฆฌ์ฌ์ด์งํ ํฌ๊ธฐ๋ฅผ ์ง์ ํ๊ณ , ๋ชจ๋ธ์ ์ ๋ ฅ ํํ์ ์ผ์น์ํค๊ธฐ ์ํด ์ฌ์ฉload_and_preprocess_image
: ์ด๋ฏธ์ง๋ฅผ ์ ์ฒ๋ฆฌํ๋ ํจ์์ด๊ณ , OpenCV๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ฝ๊ณ RGB๋ก ๋ณํํ ํ, ์ง์ ๋ ํฌ๊ธฐ๋ก ๋ฆฌ์ฌ์ด์ฆํ๋ฉฐ, 0์์ 1 ์ฌ์ด์ ๊ฐ์ผ๋ก ์ ๊ทํํ๋๋ก ๊ตฌํ
def home(request):
return render(request, 'home.html')
def upload_image(request):
if request.method == 'POST' and request.FILES['image']:
img = request.FILES['image']
fs = FileSystemStorage()
filename = fs.save(img.name, img)
uploaded_file_url = fs.url(filename)
# ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ ๋ฐ ์์ธก
img_path = os.path.join(settings.MEDIA_ROOT, filename)
img = load_and_preprocess_image(img_path, target_size=target_size)
img_array = np.expand_dims(img, axis=0) # ๋ฐฐ์น ์ฐจ์ ์ถ๊ฐ
prediction = model.predict(img_array)
age = int(prediction[0][0])
return render(request, 'result.html', {
'uploaded_file_url': uploaded_file_url,
'age': age
})
return redirect('home')
home ํจ์
: ํ ํ์ด์ง๋ฅผ ๋ ๋๋งupload_image ํจ์
: POST ์์ฒญ์ด ์ค๋ฉด ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ๊ณload_and_preprocess_image
ํจ์๋ฅผ ํตํด ์ด๋ฏธ์ง๋ฅผ ์ ์ฒ๋ฆฌํ ํ, ์ ์ฒ๋ฆฌ๋ ์ด๋ฏธ์ง๋ฅผ ๋ชจ๋ธ์ ์ ๋ ฅํ์ฌ ๋์ด๋ฅผ ์์ธกํ๊ณ , ์์ธก ๊ฒฐ๊ณผ๋ฅผresult.html
ํ ํ๋ฆฟ์ ์ ๋ฌํ์ฌ ์ฌ์ฉ์์๊ฒ ์ ๊ณต
from pathlib import Path
import os
BASE_DIR = Path(__file__).resolve().parent.parent
# ๋ชจ๋ธ ํ์ผ ๊ฒฝ๋ก ์ค์
MODEL_PATH = os.path.join(BASE_DIR, 'age_model_vgg16.h5')
# Django ๊ธฐ๋ณธ ์ค์ ์ ์๋ต, ์ถ๊ฐ๋ ๋ถ๋ถ ์ค๋ช
- MODEL_PATH: CNN ๋ชจ๋ธ ํ์ผ(
age_model_vgg16.h5
)์ ์ ๋ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ๊ณ , ํด๋น ๊ฒฝ๋ก๋ก ๋ชจ๋ธ์load_model
ํจ์๋ก ๋ก๋ - MEDIA_ROOT ๋ฐ MEDIA_URL:
settings.py
์์MEDIA_ROOT
๋ ์ ๋ก๋๋ ํ์ผ์ ์ ์ฅ ๊ฒฝ๋ก๋ฅผ ์ค์ ํ๊ณ ,MEDIA_URL
์ ์ ๋ก๋๋ ํ์ผ์ ์ ๊ทผํ ์ ์๋ URL์ ์ค์ ํ๋๋ก ๊ตฌํ - ์ ์ฒด์ ์ธ ๋์ ํ๋ฆ
- ์ฌ์ฉ์๊ฐ ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ๋ฉด, ์ด๋
upload_image
ํจ์์์ ์ฒ๋ฆฌํ๋๋ก ๊ตฌํ ์ด๋ฏธ์ง๊ฐ ์ ๋ก๋๋๋ฉด ํด๋น ์ด๋ฏธ์ง๋ฅผ ์ ์ฒ๋ฆฌํ๊ณ , CNN ๋ชจ๋ธ์ ์ ๋ ฅํ์ฌ ๋์ด๋ฅผ ์์ธกํ ํ, ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์์ผ๋ก ๋์
- ์ฌ์ฉ์๊ฐ ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ๋ฉด, ์ด๋
|-- ๐ CLIENT
| |
| |-- ๐ asset
| | |
| | |-- ๐ icon
| | |-- ๐ img
| |
| |-- ๐ feature
| | |-- cart.js // ์ฅ๋ฐ๊ตฌ๋ ์ถ๊ฐ ๊ธฐ๋ฅ
| | |-- filter.js // home.html -> ๋ฉ๋ด ์นดํ
๊ณ ๋ฆฌ ์ ํ์ ๋ฐ๋ฅธ ํํฐ ๊ธฐ๋ฅ
| | |-- shoppingList.js // ์์ ๋ฐ์ดํฐ ๋ฆฌ์คํธ
| |
| | ๐ pages
| | | - predictor.html // ์ฐ๋ น ์์ธก page
| | | - home.html // home page
| |
| | ๐ Styles
| | | - predictor.css// ์ฐ๋ น ์์ธก๊ธฐ page css
| | | - home_Eldery.css// ํค์ค์คํฌ ๋์งํธ ์ฝ์ ๋ทฐ css
| | | - home._Youth.css // ํค์ค์คํฌ ์ผ๋ฐ ๋ทฐ css
document.getElementById("uploadForm").onsubmit = async function (event) {
event.preventDefault();
const formData = new FormData();
const fileInput = document.getElementById("fileInput");
formData.append("file", fileInput.files[0]);
const uploadedImage = document.getElementById("uploadedImage");
uploadedImage.src = URL.createObjectURL(fileInput.files[0]);
uploadedImage.style.display = "block";
const result = await uploadFile(formData);
if (result.age !== undefined) {
const age = result.age;
}
};
- ๋์: ํ์ผ์ ์
๋ก๋ํ๊ณ ,
FormData
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ํ์ผ์ ์ถ๊ฐํฉ๋๋ค. ์ดํuploadFile
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฒ์ ํ์ผ์ ์ ์กํ๊ณ , ์ฐ๋ น ์์ธก ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์ด - ๊ธฐ๋ฅ: ํ์ผ์ ์ ๋ก๋ํ ํ์๋ ์ ๋ก๋๋ ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ํ์ํ๊ณ , ์์ธก๋ ์ฐ๋ น์ ๋ฐ์์ ์ถ๊ฐ์ ์ธ ์ฒ๋ฆฌ ์งํ
function goToNextPage() {
const fileInput = document.getElementById("fileInput");
const result = document.getElementById("result");
const age = result.age;
if (age >= 50) {
setElderyStatus(true);
} else {
setElderyStatus(false);
}
}
- ๋์:
isEldery
๋ผ๋ boolean ๋ณ์๋ฅผ ๋ฐ์์, ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ค์ ํ์ด์ง URL์ ์์ฑํ๊ณ , ํด๋น URL๋ก ํ์ด์ง๋ฅผ ์ด๋ - ๊ธฐ๋ฅ: ์ฐ๋ น ์์ธก ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ์ฒญ๋ ๊ณผ ๊ณ ๋ น์ ๋ถ๋ฅํ๊ณ , ๊ทธ์ ๋ฐ๋ฅธ ๋ค์ ํ์ด์ง๋ก์ ์๋ ์ด๋์ ๋ด๋น
function displayAge() {
const fileInput = document.getElementById("fileInput");
const result = document.getElementById("result");
const age = result.age;
if (age >= 50) {
result.innerHTML = '<div class="large-ui">์์ธก ์ฐ๋ น: ' + age + "</div>";
} else {
result.innerHTML = '<div class="regular-ui">์์ธก ์ฐ๋ น: ' + age + "</div>";
}
}
- ๋์: ์์ธก๋ ์ฐ๋ น์ ํ์ํ๋ ํจ์๋ก,
result
์๋ฆฌ๋จผํธ์ ์ฐ๋ น์ ๋ฐ๋ผ ๋ค๋ฅธ ์คํ์ผ์ ํ ์คํธ๋ฅผ ์ฝ์ - ๊ธฐ๋ฅ: ์์ธก๋ ์ฐ๋ น์ ์ฌ์ฉ์์๊ฒ ์๊ฐ์ ์ผ๋ก ์ ๊ณต. ์์ธก๋ ์ฐ๋ น์ด 50์ธ ์ด์์ด๋ฉด ํฐ ํ ์คํธ ์คํ์ผ์, ๊ทธ๋ ์ง ์์ผ๋ฉด ์ผ๋ฐ์ ์ธ ํ ์คํธ ์คํ์ผ์ ์ ์ฉ
50์ธ ๋ฏธ๋ง ์ฌ์ฉ์ ์ฐ๋ น ์์ธก | 50์ธ ์ด์ ์ฌ์ฉ์ ์ฐ๋ น ์์ธก |
4๏ธโฃ ์ฐ๋ น ์์ธก์ ๋ฐ๋ฅธ ์ฒญ๋ /๊ณ ๋ น ๋ถ๋ฅ ๊ฒฐ๊ณผ๋ฅผ isEldery๋ณ์์ boolean ๊ฐ์ผ๋ก ์ ๋ ฅ ๋ฐ์ ๋ถ๋ฅ์ ๋ฐ๋ฅธ ํค์ค์คํฌ ํ์ด์ง ์ด๋
function setElderyStatus(isEldery) {
var nextPageUrl = "/pages/home.html";
nextPageUrl += "?isEldery=" + encodeURIComponent(isEldery);
window.location.href = nextPageUrl;
}
- ๋์: ์์ธก๋ ์ฐ๋ น์ ๊ธฐ๋ฐ์ผ๋ก
setElderyStatus
ํจ์๋ฅผ ํธ์ถํ์ฌ ์ฒญ๋ ๊ณผ ๊ณ ๋ น์ ๋ถ๋ฅํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ๋ค์ ํ์ด์ง๋ก ์ด๋ - ๊ธฐ๋ฅ: ์ฐ๋ น ์์ธก ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์์ ์ฒญ๋ ๊ณผ ๊ณ ๋ น์ ๋ถ๋ฅํ๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ค์ ๋จ๊ณ์ ํค์ค์คํฌ ํ์ด์ง๋ก ์ฌ์ฉ์๋ฅผ ์ด๋
50์ธ ๋ฏธ๋ง ์ฌ์ฉ์ ํค์ค์คํฌ ๋ฉ๋ด | 50์ธ ์ด์ ์ฌ์ฉ์ ํค์ค์คํฌ ๋ฉ๋ด |