SSAFY 1ํ๊ธฐ ์ต์ข
ํ๋ก์ ํธ(21.11.17 ~ 21.11.24)
Content | Main | Detail |
---|---|---|
์์ฑ์ธ์ | Python 3.9.6 / JavaScript | webkitSpeechRecognition / Axios |
๋๋ก์ | JavaScript | Canvas / Blob Data/ Axios |
FE | HTML / CSS / JavaScript | Django 3.2.9 / HTML5 / CSS3 / ES6 |
ํด๋ฌ์คํฐ๋ง | C# .Net Framework 4.7.2 | C#(OpenCvSharp4 - V4.5.3.20210817) |
RGB Calculating | Python 3.9.6 | Python(Math) |
DB | Python 3.9.6 | Python(Shell_plus) |
Query ์ต์ ํ | Django 3.2.9 | prefetch_related / annotate / filter / exclude |
๋ฐฐํฌ | AWS | EC2(Ubuntu Server 18.04 LTS) / Cloud9 / Gunicorn / NGINX |
ent | Page | Description |
---|---|---|
User | accounts/ | ํ์๊ฐ์ / ๋ก๊ทธ์ธ / ๋ก๊ทธ์์ |
์๊ณ ํธ ๋ณด๊ธฐ | detail/ | ์ํ ํฌ์คํฐ ๋๋ฅผ ์, ์๊ณ ํธ ํ์ ์๋์ฌ์ |
์์ฑ ๊ณ ๋ฅด๊ธฐ | choice/ | ์ถ์ฒ ๋ฐ๊ณ ์ถ์ ์์ฑ ๊ณ ๋ฅด๊ธฐ |
ํ์ ์ ์ ๋ ฌ | base/ | TMDB ํ์ ์์ผ๋ก ์ ๋ ฌ / ํ์คํ|๋ฆฌ๋ทฐ ๋จ๊ธธ์ ์ถ์ฒ ์ํ๋๋ก ๊ตฌ์ฑ |
์ต์ ์ ์ ๋ ฌ | base/ | ๊ฐ๋ด์ผ ์ต์ ์์ผ๋ก ์ ๋ ฌ / ํ์คํ|๋ฆฌ๋ทฐ ๋จ๊ธธ์ ์ถ์ฒ ์ํ๋๋ก ๊ตฌ์ฑ |
์ด์ ์์ฑ๋ก ์ ๋ ฌ | base/ | AI Z Score ์์ผ๋ก ์ ๋ ฌ / ํ์คํ|๋ฆฌ๋ทฐ ๋จ๊ธธ์ ์ถ์ฒ ์ํ๋๋ก ๊ตฌ์ฑ |
์ฌ์ฉ์ ํ๊ท ํ์ | detail/ | ์ํ์ ๋ํด ์ ์ ๊ฐ ๋จ๊ธด ํ๊ท ํ์ ๊ณ์ฐ ๋ฐ ์ถ๋ ฅ |
์ํ ํค์๋ ๊ฒ์ | base/ | ๊ฒ์ ๊ธฐ๋ฅ์ผ๋ก ์ ๋ชฉ > ๋ด์ฉ์ ํค์๋ ํฌํจ์ฌ๋ถ์ ๋ฐ๋ผ ์ ๋ ฌํด์ ์ถ๋ ฅ |
ํด์ฆ ๋ง๋ค๊ธฐ | quiz_create/ | ์ฌ์ฉ์๊ฐ ์ํ์ ๋ํ ๊ทธ๋ฆผ ํด์ฆ๋ฅผ ๋ผ ์ ์๋๋ก ๊ตฌ์ฑ |
ํด์ฆ ํ๊ธฐ | quiz/ | ์ฌ์ฉ์๋ค๋ผ๋ฆฌ ๋ฌธ์ ๋ฅผ ํ๊ณ ํฐ์ด์ ๋ํ ์ ์๋ฅผ ์ป์ ์ ์๋๋ก ๊ตฌ์ฑ |
ํ์ ๋ฐ ํ์คํ | movie_comment/ | ์ํ์ ๋ํ ๋ณ์ ๊ณผ ํ์คํ์ ๋จ๊ธธ ์ ์๋๋ก ๊ตฌ์ฑ |
๋ฆฌ๋ทฐ(+๊ทธ๋ฆผ) | review/ | ๊ทธ๋ฆผ์ ๊ทธ๋ฆฌ๋ฉฐ ๋ฆฌ๋ทฐ๋ฅผ ๋จ๊ธธ ์ ์๋๋ก ๊ตฌ์ฑ |
๋ฆฌ๋ทฐ ์ ๋๊ธ | review_comment/ | ๋ฆฌ๋ทฐ๋ง๋ค ๋๊ธ์ ๋จ๊ธธ ์ ์๋๋ก ๊ตฌ์ฑ |
์ ์ ํฐ์ด | tier/ | ๋ธ๋ก ์ฆ๋ถํฐ ๋ง์คํฐ๊น์ง ๋ฆฌ๋ทฐ ์์ฑ, ๋ฌธ์ ํ์ด ๋ฑ์์ ์ ์ ํ๋ |
์ํ Frame ํ๋ | detail/ | detail ํ์ด์ง์์ ์ํ์ ๋ํ ํ๋ ์ ํด๋ฆญ์ Modal๋ก ํ๋ ์ถ๋ ฅ |
final-pjt
โโโ accounts/
โ โโโ migrations
โ โ โโโ ...
โ โโโ templates/accounts
โ โ โโโ ...
โ โโโ static/accounts
โ โโโ ...
โโโ movies/
โ โโโ migrations
โ โ โโโ ...
โ โโโ templates/movies
โ โ โโโ ...
โ โโโ static/movies
โ โโโ ...
โโโ final-pjt/
โ โโโ ...
โโโ staticfiles/static
โ โโโ css
โ โ โโโ ...
โ โโโ favicon
โ โ โโโ ...
โ โโโ images
โ โ โโโ ...
โ โโโ videos
โ โ โโโ ...
โ โโโ ...
โโโ templates
โ โโโ base.html
โโโ .gitignore
โโโ README.md
โโโ db.sqlite3
โโโ requirements.txt
-
- ํด๋ฌ์คํฐ๋ง / RGB Calculating / Tier / Quiz / DB / ์ฟผ๋ฆฌ ์ต์ ํ / FE
-
- ์์ฑ์ธ์ / ๋๋ก์ / Design / Quiz / DB / FE / ์ฟผ๋ฆฌ ์ต์ ํ / ๋ฐฐํฌ
-
- 90%
- ๋ชฉํ ์๋น์ค ๊ตฌํ
- ๋ชฉํ ๋์์ธ ๊ตฌํ
- ์๋: -10%, ์ฟผ๋ฆฌ ์ต์ ํ, Static ๋ฐ DB ๊ฒฝ๋ํ ์๋ฃํ์์ง๋ง, ๋๋ก์์ ํ์ฉ๋๋ Media File์ ๋ํ ๊ฒฝ๋ํ๊ฐ ์ด๋ฃจ์ด์ง์ง ์์
- 90%
-
- Part 1, ๊น์ฃผํธ
- Part 2, ์ด๊ฑดํฌ
- ๋ง์น๋ฉฐ
๊ธฐ์กด์ ์ ์๋ ค์ง ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ๊ณผ๋ ๋ค๋ฅธ ์๋ก์ด ๋ฐฉ๋ฒ์ ์ฐพ๋ค๊ฐ, '์ ์ ๊ฐ ๊ณ ๋ฅธ ์์ฑ๋ก ์ํ๋ฅผ ์ถ์ฒํด ์ฃผ๋ ์๊ณ ๋ฆฌ์ฆ'์ ๊ตฌํํ๊ธฐ๋ก ํ๋ค.
๋ฌผ๋ก ๊ทธ๋ฌํ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํด์ฃผ๋ ๊ณณ์ ์ฐพ๊ธฐ ํ๋ค์๊ธฐ์, ์ฐ๋ฆฌ๋ ์ง์ ์ํ ์์ฑ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ธฐ๋ก ํ๋ค.
์ฌ์ฉ ํ๋ก๊ทธ๋จ: Visual Studio 2019
์ฌ์ฉ ์ธ์ด: C#(WPF)
์ฌ์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ: OpencvSharp
๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ธฐ์ ์์, ์ด๋ค ๋ฐฉ์์ผ๋ก ์ํ์ ์์ฑ๋ฅผ ๋ฐ์์ฌ ์ง ์๋นํ ๊ณ ๋ฏผํ๋ค.
์ฒ์์ ์์ฑ๋ฅผ ์ ๊ณตํด์ฃผ๋ API๊ฐ ์์ ๊ฑฐ๋ผ ์๊ฐํ๊ณ , ์ค์ ๋ก ๊ฒ์์ด์ ์กํ์์ง๋ง ์ค์์ ๋์ด์ค๋ ๋ฐ์ดํฐ๊ฐ ์์๋ค.
์์ฑ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ค๊ณ ํ์ผ๋..
๊ทธ๋์ ์ง์ ๋ง๋ค์ด๋ณด์๋ ๊ฒฐ๋ก ์ ๋ด๋ ธ๋ค. ๊ทธ๋ผ ๋ฌด์์ผ๋ก, ์ด๋ป๊ฒ ๋ง๋ค ๊ฒ์ธ๊ฐ?
์ฐ์ ์ธ ๊ฐ์ง์ ์์ด๋์ด๊ฐ ๋์๋ค.
-
์ํ๋ฅผ ํ์ฅ์ ์ด๋ฏธ์ง๋ก ํํํ๋ ๋งํผ, ํด๋น ์ํ๋ฅผ ๋ํํ๋ค๊ณ ๋ณผ ์ ์์
-
๋ฐ์ดํฐ ์ถ์ถ ๋งค์ฐ ๋น ๋ฆ
-
๊ทธ๋ฌ๋, ์ํ์ ์์ฑ์ ํฌ์คํฐ์ ์์ฑ๊ฐ ๊ฐ์ ๊ฒ์ด๋ผ๋ ๋ณด์ฅ์ด ์์
- ๊ตญ๋ด ํฌ์คํฐ๋ ํ๋ํ ๊ธ๊ท์ ํด๋น ์ํ์ ์์๋ชฉ๋ก ๋ฑ ์ํ์์ ์ฃผ๋ ์์ ๋๋์ ์ ์ด๋ฆฌ์ง ๋ชป ํ ๊ฒฝ์ฐ๊ฐ ๋ง์
- ํด์ธ ํฌ์คํฐ๋ ๋์ฒด์ ์ผ๋ก ๊ด์ฐฎ์ผ๋, ํ์ด๋ก ๋ฌด๋น ๋ฑ ์ผ๋ถ์์๋ ์ํ์ ์์ฑ๋ณด๋ค ์ฃผ์ธ๊ณต ์บ๋ฆญํฐ์ ์์ ๊ฐ์กฐํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์
- ์ด ๋ํ ์ํ์ ๋๋์ ์ ์ ๊ณตํด์ฃผ๋ ๋งํผ, ํด๋น ์ํ๋ฅผ ๋ํํ๋ค๊ณ ๋ณผ ์ ์์
- ๋ฐ์ดํฐ ์ถ์ถ ์ค๋ ๊ฑธ๋ฆผ
- ์ํ์ ์์ฑ์ ๊ฐ์ ๊ฒ์ด๋ผ๋ ๋ณด์ฅ์ด ์์ง๋ง, ํฌ์คํฐ๋ณด๋ค๋ ํจ์ฌ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณต๋ฐ์ ์ ์์
- ์ํ ๊ทธ ์์ฒด๋ฅผ ๋ค์ด๋ฐ์ ํด๋น ์ํ์ ์์ ํ ์์ฑ ๋ฐ์ดํฐ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ
- ๋ฐ์ดํฐ ์ถ์ถ ๋งค์ฐ ์ค๋ ๊ฑธ๋ฆผ
- ๋ถ๋ฒ์ ์ธ ๊ฒฝ๋ก๋ก ์ํ๋ฅผ ๋ค์ด๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค์ด์ ์๋จ
- ๋๋ถ๋ถ์ ์ํ๋ค์ด ์ต๊ทผ ๊ฐ๋ด ๋ฐ ๊ฐ๋ด ์์ ์ด์๊ธฐ ๋๋ฌธ์, ๋ง๋ค ์ ์๋ ๋ฐ์ดํฐ๋ ์๋ค๊ณ ๋ด๋ ๋ฌด๋ฐฉ
์ด๋ฌํ ๊ณ ๋ฏผ์ ๊ฑฐ์น ๊ฒฐ๊ณผ, ์๊ณ ํธ์ ์ฌ์ฉํ์๋ ๊ฒฐ๋ก ์ด ๋์๋ค.
์ํ์ ์ผ๋ถ ์ฅ๋ฉด์ ๋ด๊ณ ์์ผ๋ฉฐ, ๊ณ ๊ฐ๋ค์๊ฒ 'ํด๋น ์ํ์ ์ ๋ฐ์ ์ธ ๋๋'์ ์ฃผ๋๋ก ์ค๊ณ๋์ด ์๊ธฐ ๋๋ฌธ์ ์์ฑ ๋ฐ์ดํฐ๋ฅผ ๋ฝ๊ธฐ์ ์ ์ ํ ๊ฒ์ด๋ผ ์๊ฐ๋์๋ค.
์ฌ์ฉํ ๋ฐ์ดํฐ๋ ์ ํ๊ณ , ์ด๋ป๊ฒ ๋ง๋ค์ง๋ฅผ ๊ณ ๋ฏผํ๋ค. ์์์ฒ๋ฆฌ ๊ด๋ จ ํ๋ก์ ํธ๋ฅผ ์ธํผ ์ด์ ๋ค๋ค๋ณธ ์ ์ด ์์๊ธฐ์, ์ธ ๊ฐ์ง ์ ๋์ ๊ธฐ์ค ๋ด์์ ๋ฐฉ๋ฒ๋ก ์ ๊ฒฐ์ ํ๊ธฐ๋ก ํ๋ค.
- ํด๋น ์์ ์ ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ์ ์ ๊ณตํ๊ธฐ ์ํ ๋ฐ์ดํฐ ์ ๋ณ๋ก, ๋จ๊ธฐ๊ฐ์ ๋๋ด์ผ ํ๋ค. ๋ฐฐ๋ณด๋ค ๋ฐฐ๊ผฝ์ด ์ปค์ง๋ ๊ฒฝ์ฐ๋ ์์ด์ ์๋๋ค.
- ๋ด๊ฒ ์์ด ํธํ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌ์ฑ๋์ด์ผ ์ถํ ๋ฐ๋ก ํ๋ก๊ทธ๋จ์ ์ ์ํ๊ณ ๋ฐฐํฌํ ๋๋ ๋ฌด๋ฆฌ๊ฐ ์์ ๋ฟ๋๋ฌ, ๋ฐ์ดํฐ ์์ฑ์ ํผ๋ก๋๋ฅผ ์ต์๋ก ์ค์ผ ์ ์๋ค.
- ์ํ ์๊ณ ํธ๋ง๋ค ํฌ๊ธฐ์ ๊ธธ์ด๊ฐ ์ ๊ฐ๊ฐ์ด๋ค. ํ์ง๋ง ํต์ผ์ฑ์ด ์๋ ๋ฐฉ๋ฒ์ ์๊ฐํด๋ธ๋ค๋ฉด, ์์ธ ์ฒ๋ฆฌ ๋ฑ์ ํ์ง ์๊ณ ๋ ์ข์ ๋ฐ์ดํฐ๋ฅผ ๋ฝ์๋ผ ์ ์์ ๊ฒ์ด๋ค.
์๋ง์ ๊ณ ๋ฏผ์ ํ ๋์, ๋ด๊ฒ ์ต์ํ๋ฉด์๋ ์ ์ ๋ค๋ค๋ณธ ๊ฒฝํ์ ์ด๋ ค ๋นจ๋ฆฌ ๋๋ผ ์ ์์ ๋ง ํ ๊ตฌ์กฐ์ธ C# WPF + OpencvSharp๋ฅผ ์ด์ฉํ๊ธฐ๋ก ํ๋ค.
visual studio 2019๋ฅผ ์ฌ์ฉํ์ฌ wpf ๋น ํ๋ก์ ํธ๋ฅผ ๋ง๋ ํ, xaml์ ๊ฐ๋จํ๊ฒ ๋ฒํผ์ ๋ง๋ค๊ณ ํด๋น ๋ฒํผ์ด ์ ๊ณตํ ์ด๋ฒคํธ๋ฅผ ์ฝ๋๋นํ์ธ๋์ ์ ๊ธฐ๋ก ํ๋ค.
์ด์ ์ ํ๋ ํ๋ก์ ํธ๋ค์ฒ๋ผ UI๋ฅผ ๊พธ๋ฐ ์๊ฐ์ ํ์ง ์์๊ธฐ์, mvvm ๊ตฌ์กฐ๋ ๋ฐ์ธ๋ฉ๊ฐ์ ๊ฑด ์ผ๋ํ์ง ์์๋ค. ์๊ฐ์ ์ต์ํ์ผ๋ก ์ฌ์ฉํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
nuget์ ์ฌ์ฉํด OpencvSharp๋ฅผ ์ค์นํ๊ณ , ์ํ๋ฅผ ์ ํํ dialog ๋ฑ์ ์ฒจ๋ถํ์๋ค.
OpenFileDialog openFile = new OpenFileDialog();
openFile.DefaultExt = "video&image";
openFile.Filter = "video&image|*.mov;*.mp4;*.png;";
์ดํ ์ํ ์๊ณ ํธ์ ์ ํํ๋ฉด ๋งค ํ๋ ์๋ง๋ค ํ์ฅ์ฉ mat์ผ๋ก ์ฎ๊ฒจ์๊ณ , OpencvSharp์์ ์ ๊ณตํ๋ Kmeans๋ฅผ ์ด์ฉํ์ฌ ๋น์ง๋ํ์ต์ธ ํด๋ฌ์คํฐ๋ง์ ์ํํด ํด๋น ํ๋ ์์์์ ๋ํ์ 5๊ฐ๋ฅผ ๋ฝ์ ํ๋์ ๋ฆฌ์คํธ์ ๊ธฐ๋กํ์๋ค.
if (openFile.ShowDialog() == true)
{
VideoCapture video = new VideoCapture(openFile.FileName);
int length = video.FrameCount;
int clustersCount = 5;
int total_x = 1920;
List<RGB> RGB_List = new List<RGB>(); // ํด๋น RGB ํด๋์ค๋ float 3๊ฐ๋ก ์ด๋ฃจ์ด์ง
RGB RGB_row;
Mat columnVector;
using (Mat src = new Mat(),
samples = new Mat(),
bestLabels = new Mat(),
centers = new Mat())
{
int cnt = 0;
while (true)
{
video.Read(src);
if (src.Empty())
break; // ์์์ด ๋๋ ๋๊น์ง ๋ฌดํ๋ฐ๋ณต
Cv2.Blur(src, src, new OpenCvSharp.Size(15, 15)); // ๋ธ๋ฌ๋ก ํ๋ ์์ ๋ญ๊ฐฌ
columnVector = src.Reshape(cn: 3, rows: src.Rows * src.Cols); // ์ฐจ์๋ณ๊ฒฝ
columnVector.ConvertTo(samples, MatType.CV_32FC3);
Cv2.Kmeans( // ํด๋ฌ์คํฐ๋ง์ ์ฌ์ฉ
data: samples,
k: clustersCount,
bestLabels: bestLabels,
criteria:
new TermCriteria(type: CriteriaTypes.Eps | CriteriaTypes.MaxIter, maxCount: 10, epsilon: 1.0),
attempts: 3,
flags: KMeansFlags.PpCenters,
centers: centers);
for (int idx = 0; idx < 5; idx++)
{
RGB_row = new RGB(centers.At<float>(idx, 0), centers.At<float>(idx, 1), centers.At<float>(idx, 2)); // ๊ฒฐ๊ณผ๋ก ๋์จ 5๊ฐ์ ํ๋ ์ ๋ํ์์ ๋ฆฌ์คํธ์ ์ ์ฅ
RGB_List.Add(RGB_row);
}
Console.WriteLine(cnt++ + "/" + length);
...
}
...
}
...
}
๊ธฐ๋กํ RGB ๊ฐ๋ค์ ํ๋์ ์ด๋ฏธ์ง๋ก ๋ณํํด์ฃผ๊ธฐ ์ํด ๊ฒ์์์ ๋น Mat์ ํ๋ ๋ง๋ค๊ณ ์์ ์ ํ ๋๊ฐ๋ค.
int total_y = RGB_List.Count / total_x;
Mat result = new Mat(50 * (total_y + 1), total_x, MatType.CV_8UC3, new Scalar(0, 0, 0)); // ์ฐ๋ ๊ธฐ๊ฐ์ด ๋ค์ด๊ฐ๋ ๊ฑธ ๋ฐฉ์งํ๊ธฐ ์ํด ๊ฒ์์์ผ๋ก ์ฑ์๋
for (int col = 0; col < RGB_List.Count; col++)
{
int y = col / total_x;
int x = col % total_x;
var newPixel = new Vec3b // ์์๋ RGB๊ฐ ์๋ BGR์ธ ๊ฒ์ ์ฃผ์
{
Item0 = (byte)RGB_List[col].B, // B
Item1 = (byte)RGB_List[col].G, // G
Item2 = (byte)RGB_List[col].R // R
};
for (int row = 0; row < 50; row++)
{
result.Set(50 * y + row, x, newPixel);
}
Console.WriteLine(col + "/" + RGB_List.Count);
}
๊ทธ ํ ํด๋น Mat์ ์ด๋ฏธ์ง๋ก ์ ์ฅํ์๋ค.
int lastIndex = openFile.FileName.LastIndexOf('.');
string filename = openFile.FileName.Substring(0, lastIndex) + ".png";
Cv2.ImWrite(filename, result);
MessageBox.Show("์ด๋ฏธ์ง ์ถ์ถ ์๋ฃ");
ํ๋ก๊ทธ๋จ์ ์๋์ํค๋ฉด ํ๋ ์์ ๋ฐ๋ฅธ ์งํ์ํฉ์ด ์ฝ์์ ์์ฑ๋์ด ๋์จ๋ค.
์๊ณ ํธ์์ ๋ฝ์๋ธ ํ๋ ์๋ค์ ๋ํ์์ ๊ธฐ๋กํ ํ ์ฅ์ ์ด๋ฏธ์ง(์์ผ๋ก ์ด๋ฌํ ์ด๋ฏธ์ง๋ฅผ ์ปฌ๋ฌ๋ฐ์ฝ๋๋ผ ์นญํ๊ฒ ๋ค).
์ํ ์๊ณ ํธ์ ๋ค์ด๋ฐ์ผ๋ฉฐ ๊ณ์ํด์ ํ๋ก๊ทธ๋จ์ ๋๋ ธ๋ค. ์๊ณ ํธ์ ๋ค์ด๋ฐ๋ ๊ฒ๋, ํ๋ก๊ทธ๋จ์ ๋๋ฆฌ๋๊ฒ๋ ๊ต์ฅํ ํผ๊ณคํ ์ผ์ด์์ผ๋ ๋๋๋ก ๋นจ๋ฆฌ ๋๋ด๊ธฐ ์ํด ์ด ํ ์์ด ์์ ํ๋ค.
์๊ณ ํธ๋ค์ ํ ๋ฒ์ ์ ํํด์ ๋ชจ๋ ์ปฌ๋ฌ๋ฐ์ฝ๋๊ฐ ์ถ์ถ๋ ๋๊น์ง ๋๋ฆฌ๋ ๋ฐฉ๋ฒ๋ ์๊ฐํ์ผ๋, ์์ ์ ๋น์ทํ ์์ ์ ํ๋ค๊ฐ ๋ฐ์ดํฐ๊ฐ ๊ผฌ์ฌ์ ์๋ง์ด ๋ ๊ฒฝํ์ด ์๊ธฐ์ ๋ด๊ฐ ์กฐ๊ธ ๊ณ ์ํ๋๋ผ๋ ํ๋์ฉ ์งํํ๋ ๋ฐฉ๋ฒ์ ํํ๋ค.
์ค๊ฐ์ค๊ฐ ํ๋ก๊ทธ๋จ์ด ๋ค์ด๋๋ ๊ฒฝ์ฐ๊ฐ ์์๋ค. ์๊ณ ํธ์ ์ฝ๋ฑ ์ฐจ์ด ๋ฑ์ผ๋ก Mat ๋ณํ์ด ๋์ง ์๋ ๋ฏ ํ๋ค. ๋ค๋ฅธ ์ฌ์ดํธ์์ ๋ค์ด๋ฐ์ ์ฌ์์์ ํ๊ธฐ๋ ํ๋ค.
์ํ ๋ฐ์ดํฐ๋ฅผ 100๊ฐ ์ ์ฅํ๊ธฐ๋ก ํ๊ธฐ ๋๋ฌธ์, ์๊ณ ํธ 100ํธ์์ ์ปฌ๋ฌ๋ฐ์ฝ๋ 100๊ฐ๋ฅผ ์ถ์ถํ๋ค. ๊ทธ ๋ค์ ์ปฌ๋ฌ๋ฐ์ฝ๋์์ ๋ํ์์ ๋ฝ์ ์ํ์ ์์ฑ ํ๋ ํธ๋ก ์ง์ ํ ํ, Jsonํ์ผ๋ก ๋ณํํ์ฌ DB ๋ฐ์ดํฐ ์์ ์ด ์์ํด์ง๋๋ก ํ๋ค.
var jsonData = System.IO.File.ReadAllText(filePath);
var colorList = JsonConvert.DeserializeObject<List<Color>>(jsonData) ?? new List<Color>();
๊ทธ ํ Kmeans๋ก ๋ํ์ 6๊ฐ๋ฅผ ๋ฝ์๋ค.
์ํ ์๊ณ ํธ์ ๊ธด๋ฐ๊ฐ์ด๋ ํ๋ฉด์ ํ ํจ๊ณผ๋ฅผ ์ฃผ๊ธฐ ์ํด ๊ฒ์ ํ๋ฉด์ ๋์ฐ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ค. ์ด๋ฅผ ์ํด 6๊ฐ์ ๋ํ์ ์ค, ๊ฐ์ฅ ์ด๋์ด ๋ํ์์ ์ ์ธํ๋ ๋ฐฉ๋ฒ์ ํํ๋ค.
int[,] total_color = new int[6, 3];
int darkest_index = 0;
int darkest_value = 255 * 3;
for (int i = 0; i < 6; i++)
{
total_color[i, 0] = (int)centers.At<float>(i, 0);
total_color[i, 1] = (int)centers.At<float>(i, 1);
total_color[i, 2] = (int)centers.At<float>(i, 2);
// ๊ฐ์ฅ ์ด๋์ด ์์ ์ง๋ ๋ํ์์ ์ฐพ์
if (darkest_value > total_color[i, 0] + total_color[i, 1] + total_color[i, 2])
{
darkest_value = total_color[i, 0] + total_color[i, 1] + total_color[i, 2];
darkest_index = i;
}
}
// ํด๋นํ๋ ๊ฐ์ 6๋ฒ์งธ ๋ํ์์ผ๋ก ๋ฐ๊ฟ์ค
total_color[darkest_index, 0] = total_color[5, 0];
total_color[darkest_index, 1] = total_color[5, 1];
total_color[darkest_index, 2] = total_color[5, 2];
colorList.Add(new Color()
{
movie = openFile.FileNames[idx],
color_1_B = total_color[0, 0],
color_1_G = total_color[0, 1],
color_1_R = total_color[0, 2],
color_2_B = total_color[1, 0],
color_2_G = total_color[1, 1],
color_2_R = total_color[1, 2],
color_3_B = total_color[2, 0],
color_3_G = total_color[2, 1],
color_3_R = total_color[2, 2],
color_4_B = total_color[3, 0],
color_4_G = total_color[3, 1],
color_4_R = total_color[3, 2],
color_5_B = total_color[4, 0],
color_5_G = total_color[4, 1],
color_5_R = total_color[4, 2],
});
// json ์
๋ฐ์ดํธ
jsonData = JsonConvert.SerializeObject(colorList);
System.IO.File.WriteAllText(filePath, jsonData);
์ด๋ ๊ฒ ํ์ฌ ์ํ์ ์์ฑ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ์๋ค.
์์ฑ ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ ๋ฐ ๊ธฐํ ์๊ณ ๋ฆฌ์ฆ๊ณผ ์นํ์ด์ง ๋ํ ์ผ ์์
๋ฐ์ดํฐ๋ฅผ ์์ฑํ๊ณ ์ค๋ ํ์ด์ด์ ๊ฑดํฌ๋์ด ๋๋ถ๋ถ์ ์น ๊ตฌ์กฐ๋ฅผ ์ก์๋์ผ์ ์ํ์๋ค. ๋จ๊ธฐ๊ฐ์ ๋๋ด์ค ์ค ๋ชฐ๋์ด์ ๊ต์ฅํ ๋๋ผ์ ๊ณ , ํด๋น ๋ฐฉํฅ์ผ๋ก๋ ๋์๋๋ฆฐ ๊ฒ ์์ด์ ์ฃ์กํ๋ค.
์ง์ฌ์ง ๊ตฌ์กฐ๋ฅผ ๋ฐํ์ผ๋ก ์๊ณ ๋ฆฌ์ฆ์ ์์ฑํ๊ธฐ๋ก ํ๋ค. ์ฐ์ ์ํ ์์ฑ์ ๋ํ RGB๋ Json ๋ฐ shell_plus๋ก ๋ค์ด๊ฐ ์๋ ์ํ์๋ค. ์ด์ ์ด ๊ฐ๋ค๊ณผ ์ ์ ๊ฐ ๊ณ ๋ฅธ ์์ ๋์กฐํ์ฌ ์ ๋ ฌํ๋ฉด ๋๋ ์ํ์๋๋ฐ, ์ด๋ค ๋ฐฉ์์ผ๋ก ๋์กฐํ๋ฉฐ ์งํํด์ผ ํ ์ง ์กฐ๊ธ ๋๊ฐํ๋ค.
์์ด๋์ด ํ์๋ฅผ ์งํํ๋ฉฐ ๋ฐฉ๋ฒ๋ก ์ ๊ณ ๋ฏผํ๋ค.
- ํ๊ท ์ ๊ตฌํ๋ค๋ฉด ๊ธฐ์กด ์์ฑ์ ์๋ฏธ๋ฅผ ์๊ฒ ๋ ์ ์๋ค.
- (40, 40, 40), (60, 60, 60)๊ณผ (0, 10, 20), (100, 90, 80)์ด ์ฃผ๋ ์์ฑ๋ ๋ค๋ฅด๋ค.
- ํ๊ท ์ ๋ด๋ฒ๋ฆฌ๋ฉด ์์ฑ ๊ตฌ์กฐ๊ฐ ๊นจ์ด์ง๊ณ , ์์ ๊ตฌํ ๋ฐ์ดํฐ๋ค์ด ๋ฌด์ฉ์ง๋ฌผ ๋ ์ ์๋ค.
- ์์ ๊ตฌํ ์์ฑ๋ค์ ์ฐ์ ์์๊ฐ ์ค์ ๋์ด ์์ง ์๋ค. ์ด๋ค ์์ด ์ํ์ ๋๋ถ๋ถ์ ์ฐจ์งํ๋์ง๋ ๋ชจ๋ฅธ๋ค. ๊ทธ์ ๋ง์ด ๋์จ ์ 5๊ฐ๋ฅผ ์์์์ด ๊ณจ๋์ ๋ฟ.
- ์ ์ ๊ฐ ์ฃผํฉ์์ ๊ณจ๋๋๋ฐ, ๋๋ถ๋ถ ๋ถ์ ์์ฑ๋ฅผ ์ง๋ ์ํ๋ณด๋ค ์ฃผํฉ ํ๋๋ง ์ผ์นํ๊ณ ๋๋จธ์ง๋ ์ ํ ๋ค๋ฅธ ์์ฑ๋ฅผ ์ง๋ ์ํ๊ฐ ๋งจ ์์ผ๋ก ์ค๋ฉด ์ด๋ ์ฐ๋ฆฌ์ ์๋์ ๋ง์ง ์๊ฒ ๋๋ค.
- ์ ์ ๊ฐ ๊ณ ๋ฅธ ์๊ณผ ์ํ์ ์์ ํ๋์ฉ ๋น๊ตํ๋ฉฐ ๊ทธ ์ฐจ์ด๋งํผ ์ ์๋ฅผ ๋๋ฆฐ๋ค.
- ์ ์๊ฐ ๊ฐ์ฅ ๋ฎ๊ฒ ๋์จ ์ํ๋ ์ ์ ์ ์๊ณผ distance๊ฐ ๊ฐ์ฅ ์งง๋ค.
- ํด๋น ๋ฐฉ์์ด ๋ฐ์ดํฐ๋ ์ ๋๋ก ์ด๋ฆด ์ ์์ผ๋ฉฐ, ์ฐ๋ฆฌ๊ฐ ์์ํ ๋ฐฉ์๊ณผ ๊ฐ์ฅ ๋ฎ์์๋ค.
๊ทธ๋ฆฌํ์ฌ 3๋ฒ์งธ ๋ฐฉ์์ ํํ๊ฒ ๋์๋ค.
last_color_rgb = last_color.color[4:-1].split(", ") # Users R, G, B list
for movie in movies:
movie_color = movie.color_set.all()[0]
score = math.sqrt(
math.pow(int(last_color_rgb[0]) - int(movie_color.color_1_R), 2) +\
math.pow(int(last_color_rgb[1]) - int(movie_color.color_1_G), 2) +\
math.pow(int(last_color_rgb[2]) - int(movie_color.color_1_B), 2)
) + \
math.sqrt(
math.pow(int(last_color_rgb[0]) - int(movie_color.color_2_R), 2) +\
math.pow(int(last_color_rgb[1]) - int(movie_color.color_2_G), 2) +\
math.pow(int(last_color_rgb[2]) - int(movie_color.color_2_B), 2)
) + \
math.sqrt(
math.pow(int(last_color_rgb[0]) - int(movie_color.color_3_R), 2) +\
math.pow(int(last_color_rgb[1]) - int(movie_color.color_3_G), 2) +\
math.pow(int(last_color_rgb[2]) - int(movie_color.color_3_B), 2)
) + \
math.sqrt(
math.pow(int(last_color_rgb[0]) - int(movie_color.color_4_R), 2) +\
math.pow(int(last_color_rgb[1]) - int(movie_color.color_4_G), 2) +\
math.pow(int(last_color_rgb[2]) - int(movie_color.color_4_B), 2)
) + \
math.sqrt(
math.pow(int(last_color_rgb[0]) - int(movie_color.color_5_R), 2) +\
math.pow(int(last_color_rgb[1]) - int(movie_color.color_5_G), 2) +\
math.pow(int(last_color_rgb[2]) - int(movie_color.color_5_B), 2)
)
res_movies.append([movie, int(score)])
์ฝ๋๋ก ๋ณด๋ฉด ์กฐ๊ธ ์ด์ง๋ฌ์ธ ์ ์์ผ๋, ์์์ผ๋ก ๋ณด๋ฉด ๋ณ ๊ฒ ์๋ ๋ชจ์์๋ค.
์ดํ ํด๋น ๊ฐ๋ค์ ์ฌ์ฉํ์ฌ ์ ์ ๊ฐ ๊ณ ๋ฅธ ์๊ณผ ์ํ์ ์์ฑ๊ฐ ๊ฐ์ฅ ๋น์ทํ ์ํ ์์ผ๋ก ์ ๋ ฌํ์ฌ ํ๋ฉด์ ๋์์ฃผ๋ ๋ฐฉ๋ฒ์ ํํ๋ค.
res_movies.sort(key= lambda x: x[1])
ํ๋์์ ๊ณจ๋์ ๋์ ํ๋ฉด. ๋๋ถ๋ถ ํ๋ ์์ฑ ์ํ์ธ ์ํ๊ฐ ๋งจ ์๋ก ๋์ค๊ฒ ๋๋ค.
๋ฐ๋๋ก ๋ถ์ ์์ ๊ณจ๋์ ๋์ ํ๋ฉด.
๋ฉ์ธํ๋ฉด์์๋ ์ปฌ๋ฌ๋ฐ์ฝ๋๋ฅผ ์ผ์ ํ ํฌ๊ธฐ์ ๋ง๊ฒ ๋ณด์ฌ์ฃผ๊ธฐ ๋๋ฌธ์, ์ ์ฒด์ ์ธ ์ปฌ๋ฌ๋ฐ์ฝ๋์ ๊ตฌ์กฐ๋ฅผ ์๊ธฐ ํ๋ค๋ค. detail์์๋ ์กฐ๊ธ ํฌ๊ฒ ๋ณด์ฌ์ฃผ๊ธด ํ์ง๋ง, ๋ญ๊ฐ ์ ์ ์๊ฒ ํ์คํ ๋ณด์ฌ์ฃผ๊ณ ์ถ์๋ค. ์ํ ์์ฑ๋ฅผ ๊ทธ๋ฅ ๋ฝ์๋ค๋ ๊ฒ์ด ์๋, ํ๋์ ์ฆ๊ฑฐํ์ด์ ๊ฒฐ๊ณผ๋๊น.
์ฐ์ base์ modal๊ณผ js ์ ์์น์ block์ ์ก์๋ค. modal์ ํด๋น ์น์ ์ ์ ๋นํ์ฑํํ๊ณ modal์ด ํ์ฑํ๋๋ ๊ตฌ์กฐ์ธ๋ฐ, modal์ด ์น ๊ตฌ์กฐ ์ค ์์ชฝ์ ์์นํ ๊ฒฝ์ฐ ์น๊ณผ modal ๋๋ค ๋นํ์ฑํ๊ฐ ๋์ด ์๋ก๊ณ ์นจ์ด ์๋๋ฉด modal์ ๋ ์ ์๊ฒ๋ ๋์ด๋ฒ๋ ธ๋ค. ์ด๋ ์์ pjt03์ ์งํํ ๋์ ๊ฐ์ ๋ฌธ์ ์๋ค. ๊ทธ๋ ๊ธฐ์ ์์ ์ฒ๋ผ modal์ ์์น๋ฅผ ์์ ํด์ผ ํ๋ค๋ ๊ฒ์ ๋ฐ๋ก ์์๋ค.
๋ํ ์ปฌ๋ฌ๋ฐ์ฝ๋๋ฅผ ํด๋ฆญํ์ ๋ ์ป์ด์ง๋ ๋ฐ์ดํฐ๋ ์น ์์ชฝ์ ์์นํ๋๋ฐ, ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊นฅ์ชฝ์ modal๊น์ง ์ ๋ฌํด์ฃผ๋ ค๋ฉด js๊ฐ ๊ฐ์ฅ ๊ฐํธํ ๋ฐฉ๋ฒ์ด๋ผ ์๊ฐํด ํด๋น ๊ตฌ๋ฌธ์ ์์ฑํ์๋ค.
{% block context %}
<div class="d-flex justify-content-center align-items-center" style="height:100px;" data-toggle="modal" data-target="#imagemodal">
<img src="{{color.color_url}}" alt="" class="h-75 w-100" id="color_url">
</div>
...
{% endblock context %}
{% block modal %}
<div class="modal" id="imagemodal">
<div class="modal-dialog" style="max-width: 80%; vertical-align: middle;">
<div class="modal-content" style="border-radius: 30px; background-color: rgba(255,255,192,0.1); backdrop-filter: blur(10px); box-shadow: 2px 7px 15px 8px rgba(0,0,0,0.3);">
<!-- Modal body -->
<div class="modal-body">
<img src="" alt="">
</div>
</div>
</div>
{% endblock modal %}'
{% block js %}
<script>
const color_url = document.querySelector('#color_url')
const src = color_url.getAttribute('src')
const imagemodal = document.querySelector('#imagemodal')
const color_barcode = imagemodal.querySelector('.modal-body img')
color_url.addEventListener('click', function(event) {
color_barcode.src = src
})
</script>
{% endblock js %}
๋ฐ์ฝ๋๋ฅผ ํด๋ฆญํ๋ฉด ํฌ๊ฒ ๋ณผ ์ ์๊ฒ๋ modal์ ์์ฑ
๋ฐ๋ก ์์ ์ด๋ฏธ์ง ์ผ์ชฝ์ ์์ธํ ๋ณด๋ฉด, ์ ์ ์ ์ ์ ํ ๊ธฐ๋ก์ด ์๋ค. ๋ง์ฝ ์ ์ ๊ฐ ์ ์์ ๋ํ ๊ฐ์ ๋ค์ ๋ณด๊ณ ์ถ์ดํ๋ค๋ฉด? ๊ณผ์ฐ ์์ ๊ณผ ๋๊ฐ์ RGB๋ฅผ ์๋ฒฝํ ๊ณ ๋ฅผ ์ ์์๊น? ํ๋ ์๊ฐ์ '๊ณผ๊ฑฐ ๊ธฐ๋ก์ ๋๋ฅด๋ฉด ๋ค์ ํ ๋ฒ ํ์'ํด์ฃผ๋ ์์คํ ์ ๊ตฌ์ฑํ์๋ค.
๋ก์ง ์์ฒด๋ ๊ฐ๋จํ ์ ์์ผ๋, ํ์ฌ์ ๋ค๋ฅธ ์๊ณ ๋ฆฌ์ฆ ๊ตฌ์กฐ๋ค์ ๊นจ๋จ๋ฆฌ์ง ์๋ ๋ฐฉํฅ์ผ๋ก ์์ฑํด์ผ ํ๋ค. ์ฐ์ ์ ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ์ ์ ์ ์ ์์ ๋ฐ์ ๊ฐ์ฅ ์ต๊ทผ์ ๊ณ ๋ฅธ ๊ฐ์ ๊ธฐ์ค์ผ๋ก ๋ณด์ฌ์ฃผ๋๋ฐ, ๊ทธ๋ ๋ค๋ฉด ์ฐ๋ฆฌ์ ๋ก์ง์ '์ ํํ ๊ฐ์ ๊ฐ์ฅ ์ต๊ทผ ๊ฐ์ผ๋ก ์ฎ๊ธด๋ค'๋ก ํ๋ฌ๊ฐ์ผ ํ๋ค.
์ฐ์ ํญ๋ชฉ์ ๋๋ ์ ๋ ํด๋นํ๋ ์ ๊ฐ์ ์์๋ณด๊ธฐ ์ํด js๋ฅผ ์ด์ฉํ์ฌ ์ฝ์์ฐฝ์ ์ฐ์ด ๋ณด์๋ค. ๋คํํ backgroundcolor๋ก ๋ค๊ณ ์๋ ๊ฒ์ด ํ์ธ๋์๋ค.
๋ค์์ ํด๋น ํญ๋ชฉ ๋ฐ์ aํ๊ทธ๋ฅผ ๋ง๋ค์ด ์ฐ๋ฆฌ๊ฐ ์ํ๋ ํจ์๋ก ์ด๋์์ผ ์ฃผ๊ธฐ๋ก ํ๋ค.
ํจ์๋ ๊ฐ๋จํ๊ฒ ์ธ์๋ก rgb ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๊ฐ์ฅ ์ต๊ทผ๊ฐ์ผ๋ก ์ฎ๊ฒจ์ฃผ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์๋ค.
def usercolor_update(request, rgb):
colors = request.user.usercolorrecord_set.all()
for i in range(len(colors)):
if colors[i].color == rgb:
colors[i].delete()
break
UserColorRecord.objects.create(user=request.user, color=rgb) # Color Push
return redirect('movies:index', 1)
ํ์ง๋ง ์ด๋ ์๋ฒ ๊ฐ์ ๋ฐ๊พธ๋ ํ์์ด๋ฏ๋ก, post๋ก ์ ๊ทผํ๋ ๊ฒ์ด ์ณ์ ์ ํ์ด์๋ค. aํ๊ทธ๋ฅผ ์์ ๊ณ form์ ๊ตฌ์ถํ ๋ค์, ๋ด๋ถ ๋ฒํผ์ ๋ง๋ค์ด์ ๊ฐ์ ๋ฐํํ๋ ๊ตฌ์กฐ๋ก ๋ฐ๊พธ์๋ค.
<form action="{% url 'movies:usercolor_update' color.color %}" method="POST">
{% csrf_token %}
<button style="width: 100%; height: 100%; background-color: transparent; background-repeat: no-repeat; border: none; cursor: pointer; overflow: hidden; outline: none;"/>
</form>
๋๋ถ๋ถ์ ์์ ์ ๋๋ด๊ณ ์ฟผ๋ฆฌ ์ต์ ํ๋ฅผ ์งํํ๋ค. ์์ฑ ์ถ์ฒ๋ง ํด๋ ์ฟผ๋ฆฌ๊ฐ 400๊ฐ๊ฐ๋์ด์๋ค. ํด๋น ์ถ์ฒ๋ค์ ๋ชจ๋ '์ ์ ๊ฐ ๋ณ์ ์ ๋จ๊ธด ์ํ๋ฅผ ์ถ์ฒํ์ง ์์'์ ๊ธฐ๋ฐ์ผ๋ก ๋์ด์ ธ ์์๊ธฐ ๋๋ฌธ์, ์ด ๋ถ๋ถ๋ถํฐ ํด๊ฒฐํด์ผ ํ๋ค.
movies = []
for movie in temp_movies:
for comment in movie.comments.all():
if request.user == comment.user:
break
else:
for review in movie.reviews.all():
if request.user == review.user:
break
else:
movies.append(movie)
๊ธฐ์กด ์ฝ๋. for๋ฌธ๊ณผ ์ฐ๊ณ๋์ด ์์ด ์ฟผ๋ฆฌ๋ฅผ ๊ณ์ ๋ฐ์์ค๋ ๊ตฌ์กฐ์๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด for๋ฌธ๊ณผ ์ญ์ฐธ์กฐ๋ฅผ ๊ฐ์ด ๋๋ ๊ฒ ์๋, ์ฟผ๋ฆฌ๋ฅผ ํํฐ๋ก ์ ํํ์ฌ ๋ฐ์์จ ํ for๋ฌธ์ผ๋ก ํด๋น ์ ์ ๊ฐ ๋ฆฌ๋ทฐ ํน์ ํ์คํ์ ๋จ๊ธด ์ํ์ pk๊ฐ์ set์ผ๋ก ์ ๋ฆฌํ์๋ค. ๊ทธ ํ ์ํ ๋ชฉ๋ก์ ๋ฐ์์ฌ ๋ ์ ์ธํ ๋ชฉ๋ก์ผ๋ก ๋ฃ์ด ์ฃผ์๋ค.
already_watched_movies = set()
users_moviecomments = MovieComment.objects.filter(user=request.user.pk).values('movie')
for users_moviecomment in users_moviecomments:
already_watched_movies.add(users_moviecomment['movie'])
users_reviews = Review.objects.filter(user=request.user.pk).values('movie')
for users_review in users_reviews:
already_watched_movies.add(users_review['movie'])
movies = Movie.objects.exclude(id__in=already_watched_movies)
์ ์ฝ๋ ์ดํ ์ฟผ๋ฆฌ๊ฐ ์ฝ 200๊ฐ๋ก, ์ ๋ฐ ๊ฐ๋์ด ๊ฐ์ํ๋ ํจ๊ณผ๋ฅผ ๋ณ์๋ค.
๊ทธ๋ฌ๋ ์์ง ์ ์ ์์ ์ฟผ๋ฆฌ๋ ์๋์๊ธฐ ๋๋ฌธ์, ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ฌ ๋ ํด๋นํ๋ ์ํ์ ์์ฑ ๋ฐ์ดํฐ๊น์ง ๋ค๊ณ ์ฌ ์ ์๋๋ก ํ๋ค.
movies = Movie.objects.exclude(id__in=already_watched_movies).prefetch_related('color_set', 'comments')
์ก์์ ์ฟผ๋ฆฌ 8๊ฐ, ํ์๋ฆฌ ์๋ก ์ค์ด๋๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
SSAFY 1ํ๊ธฐ์ ๋ง๋ฌด๋ฆฌ, SSAFY์์ ์ฒ์ํ๋ ํ๋ก์ ํธ,
์ํ๊ณ ์ถ์๋ค. ์ํ๋ ๋ชจ๋ ๊ฒ์ ๊ตฌํํ๊ธฐ ์ํด ์ง์คํ๋ค.
๊ธฐ์กด์๋ ๋ค์ด๋ฒ, ์นด์นด์ค ๋ ์ค ํ๋์ API๋ฅผ ์ฌ์ฉํด์ Axios, Promise ๋ฐฉ์์ผ๋ก API / Django View 2๊ฐ์ ํต์ ํ์ฌ ๋ก์ง์ ์ฒ๋ฆฌํ๋ ค๊ณ ๊ตฌ์
ํ์ง๋ง, IE์ Chrome์์ ๋์ํ๋ Web Speech API์ ์์ฑ์ธ์ ์ ๋ขฐ๋(ํ๊ตญ์ด, 92%)์ ํ์ธ ํ ํด๋น API ์ฌ์ฉ์ ์ ๊ทผ
CSR = Naver CLOVA Speech Recognition, K_TTS = Kakao Speech-to-Text system, WSA = Web Speech API
-
ํ๊ตญ์ด ์ ๋ขฐ๋ ์์ CSR > K_TTS > Web Speech API
-
์ฒ๋ฆฌํด์ผ ํ๋ ๋ก์ง์์๋
- CSR, K_TTS์ ๊ฒฝ์ฐ,
- Template(์ฌ์ฉ์ ์์ฑ ๋ น์) > Axios(JS, ํ์ผ๋ก ๋ณํ ํ ์ ์ก) > CSR, K_TTS(๋ฐ์ดํฐ ์ฒ๋ฆฌํ Response) > Axios(๋ฐ์ดํฐ ์์ ) > Django View(๋ฐ์ดํฐ ์ ์ก) > Django View(๋ฐ์ดํฐ ์ฒ๋ฆฌ ํ Response) > Axios(์์ ํ Template์ฒ๋ฆฌ)
- WSA์ ๊ฒฝ์ฐ,
- ์ฌ์ฉ์ ์์ฑ ๋ น์(Template) > WSA(๋ฐ๋ก ์ฒ๋ฆฌ ํ ํ ์คํธ ์ถ์ถ ํ ์ ์ก) > Django View(๋ฐ์ดํฐ ์ฒ๋ฆฌ ํ Response) > Axos(์์ ํ Template์ฒ๋ฆฌ)
์ ๊ทธ๋๋ ๋ง์ ๋ฐ์ดํฐ๊ฐ ์ ๋ก๋ ๋์ด์ผํ๊ณ , ํต์ ๋ ๋ง์ด ํ๋ ์ฌ์ดํธ์์ CSR, K_TTS ๋ณด๋ค๋ WSA๊ฐ ํจ์ฌ ๊ฐ๋ฒผ์ธ ๊ฒ์ด๋ผ ์์ธก
- CSR, K_TTS์ ๊ฒฝ์ฐ,
-
๊ตฌํ ์ฌ์ดํธ์์ ํ์ํ ๊ฒ์ ์ด๋์ ๋์ ์ธ์๋ฅ ์ด์
์์ฑ์ธ์ ์ฒ๋ฆฌ๊ฐ ๋ชฉ์ ์ธ ์ฌ์ดํธ๊ฐ ์๋๋ฉฐ, ์ฒ๋ฆฌํด์ผํ๋ ์์ฒญ์ด ๋ง์ง ์์๋ค. ๋ฐ๋ผ์ WSA์ ์ ๋ขฐ๋ 92%๋ ์ถฉ๋ถํ๋ค๊ณ ํ๋จ
๋ น์ ์์ด์ฝ / ์ ์ง ์์ด์ฝ ๊ธฐ์ค์ผ๋ก ๋ น์ ์์ / ์ ์ง ๋ฐ ๋ก์ง ์์์ Flag ์ฒ๋ฆฌ
์ฒ๋ฆฌ๋ง๋ค ์์ด์ฝ ๋ฐ Answer Text ๋ณ๊ฒฝ
<div>
<h4 id="voiceText"> ์์ฑ์ ๊ธฐ๋ค๋ฆฌ๊ณ ์์ด์...</h4>
<h4 class="d-none" id="voiceAnswer" > ์๊ฒ ์ด์, ์ง๊ธ ๋ฐ๋ก ์คํํ ๊ฒ์.</h4>
</div>
<div class="modal-footer">
<button id="voiceBtn" type="button" class="btn btn-danger">
<i class="fas fa-microphone-alt"></i>
</button>
</div>
-
CDN - Webkit ์์ ๋ด์ฅ
-
Instance - new webkitSpeechRecognition
-
ํ ์คํธ ์ถ์ถ - Instance 'result' ์ด๋ฒคํธ ๋ฆฌ์ค๋, event['results']์ index 0
-
ํ ์คํธ ์ถ์ถ ํ ๋ก์ง
- voice_process๋ก Axios ์์ฒญ
- vocie_process ์ฒ๋ฆฌ ํ Response
- Axios์์ js๋ก template ์กฐ์
{% comment %} ์์ฑ์ธ์ {% endcomment %}
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
// Required Definition
const voiceBtn = document.querySelector('#voiceBtn')
const voiceText = document.querySelector('#voiceText')
const processURL = '/movies/voice_process/'
const voiceAnswer = document.querySelector('#voiceAnswer')
let speech = new webkitSpeechRecognition // Voice Recording Tool
let flag = true // start|stop flag
voiceBtn.addEventListener('click', function () {
if (flag) {
speech.start()
voiceText.innerText = "๋ฃ๊ณ ์์ด์..."
voiceBtn.innerHTML = '<i class="fas fa-stop" style="font-size:35px;"></i>'
voiceAnswer.classList.add('d-none')
flag = false
} else {
speech.stop()
voiceBtn.innerHTML = '<i class="fas fa-microphone-alt" style="font-size:35px;"></i>'
flag = true
// Connecting to voice_process in View
axios({
method: 'GET',
url: processURL,
params: {
data: voiceText.textContent,
}
})
.then(function (response) {
const res = response.data.res // Number for Recognition
if (res != 0 && res <= 3) {
// href to Main by Sorting
voiceAnswer.classList.remove('d-none')
voiceAnswer.innerText = "์๊ฒ ์ด์, ๋ฐ๋ก ์คํํ ๊ฒ์."
setTimeout(function() {
window.location.href = `/movies/index/${res}`
}, 1000);
} else if (res >= 4) {
if (res == 4) {
// Search
let query = response.data.query
const searchInput = document.querySelector('#searchInput')
const searchButton = document.querySelector('#searchButton')
searchInput.value = query
voiceAnswer.classList.remove('d-none')
voiceAnswer.innerText = "์๊ฒ ์ด์, ๋ฐ๋ก ์คํํ ๊ฒ์."
setTimeout(function() {
searchButton.click()
}, 1000);
} else if (res == 5) {
// Click BackBtn
voiceAnswer.classList.remove('d-none')
voiceAnswer.innerText = "์๊ฒ ์ด์, ๋ฐ๋ก ์คํํ ๊ฒ์."
setTimeout(function() {
let backTag = document.querySelector('#backTag')
backTag.click()
}, 1000);
} else if (res == 6) {
// About UserInfo Update
voiceAnswer.classList.remove('d-none')
voiceAnswer.innerText = "์๊ฒ ์ด์, ๋ฐ๋ก ์คํํ ๊ฒ์."
setTimeout(function() {
window.location.href = '/accounts/logout'
}, 1000);
}
} else {
voiceAnswer.classList.remove('d-none')
voiceAnswer.innerText = "ํด๋น ์๋น์ค๋ ์ค๋น์ค์
๋๋ค."
}
})
}})
// Recording Response Tracking
speech.addEventListener("result", function (event) {
const { transcript } = event["results"][0][0]
voiceText.innerText = `"${transcript}"`
})
</script>
# movies/views.py
def voice_process(request):
data = request.GET['data']
responsable = {"๋ฉ์ธ": 1, "๋งค์ธ": 1, "์์ฒด": 1, "์์ฑ": 1, "ํ์ ": 2, "์ต์ ": 3, "๊ฒ์": 4, "๋ค๋ก":5, "๋ก๊ทธ": 6}
res, query = 0, ""
for key, value in responsable.items():
if key in data:
if key == "๊ฒ์":
query = str(data[1:data.index("๊ฒ์")])
res = value
break
context = {
'res' : res,
'query' : query,
}
return JsonResponse(context)
์ฌ์ฉ์ ๋ก์ปฌ์ ์ ์ฅํ์ง ์๊ณ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๋ก ์ ์กํ๊ธฐ ์ํด Blob ๋ฐ์ดํฐ ๋ณํ ํ, FormData ์์ฑ ํ Views.py ์ฒ๋ฆฌ, Json Response, Axios Promise ๋์
- https://github.com/shlee0882/painting-js ์ด์ฉ ๋ฐ ์์ฉ
- ์ฌ์ฉ์๊ฐ ๊ทธ๋ฆด ์ ์๋ ์ปฌ๋ฌ๋ ์ํ์ ์ปฌ๋ฌ ์์์ผ๋ก ํ์
์ค์ผ์น๋ถ ์ด๊ธฐ ๋ฒํผ์ ํตํด ๊ทธ๋ฆผํ display ํ ๊ธ
ํด๋น flag๋ก ๊ทธ๋ฆผ ๋ฐ์ดํฐ ์์ ๊ฒฝ์ฐ ๊ทธ๋ฆผ์ด ์๋ ๊ฒ์ผ๋ก ์ฒ๋ฆฌ
<div class="content-section mt-3">
<div class="app-card mt-5">
<button style="background: none; border:none;" id="drawBtn">
<h1 style="color:white;">์ค์ผ์น๋ถ ์ด๊ธฐ</h1>
</button>
</div>
</div>
<div class="content-section d-none" id="drawWindow">
<div class="app-card mt-3">
<div class="row d-flex justify-content-center align-items-center">
<canvas id="drawCanvas" class="canvas col-6" style="opacity: 0.8;"></canvas>
<div class="controls col-5">
<div class="controls__range">
<input type="range" id="jsRange" min="0.1" max="10.0" value="5.0" step="0.1" style="width:200"/>
</div>
<!-- div.controls__btns>button#jsMode+button#jsSave -->
<div class="controls__btns">
<button id="jsMode" style="opacity: 0.8;">ํ</button>
</div>
<div class="controls__colors" id="jsColors">
{% for color in movie.color_set.all %}
<div class="controls__color jsColor" style="background-color: rgb({{color.color_1_R}},{{color.color_1_G}},{{color.color_1_B}});"></div>
<div class="controls__color jsColor" style="background-color: rgb({{color.color_2_R}},{{color.color_2_G}},{{color.color_2_B}});"></div>
<div class="controls__color jsColor" style="background-color: rgb({{color.color_3_R}},{{color.color_3_G}},{{color.color_3_B}});"></div>
<div class="controls__color jsColor" style="background-color: rgb({{color.color_4_R}},{{color.color_4_G}},{{color.color_4_B}});"></div>
<div class="controls__color jsColor" style="background-color: rgb({{color.color_5_R}},{{color.color_5_G}},{{color.color_5_B}});"></div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
- Reference - https://github.com/shlee0882/painting-js)
- Reference ๊ทธ๋๋ก ์ด์ฉ, ๊ทธ๋ฆผํ์ ์์ฑํ๋ ๊ฒ์ด ๋ชฉํ๊ฐ ์๋์์ผ๋ฏ๋ก ์คํ์์ค CSS ํ์ฉ๊ณผ ๊ฐ๋ค๊ณ ํ๋จ
- Blob ๋ฐ์ดํฐ ๋ณํ ๋ฐ FormData ์์ฑ ์ด์
- ์ฌ์ฉ์ ๋ก์ปฌ์ ์ ์ฅํ์ง ์๊ณ ํ์ผ์ ๋ฏธ๋์ด ํ์ผ์ ์ ๋ก๋ >> Blob ๋ฐ์ดํฐ ๋ณํ ํ ๋ฐ์ดํฐ PUSH
- input์ ๋ฐ์ดํฐ ๊ฐ์ JS๋ก ๋ณ๊ฒฝํ ์ ์๋ ์ ์ฑ >> ์๋ก์ด FormData ์์ฑํ ํด๋น ๋ฐ์ดํฐ POST
function submitFunc(){
event.preventDefault()
let drawCanvas = document.querySelector('#drawCanvas')
data = new FormData() // FormData ์์ฑ
var imgDataUrl = drawCanvas.toDataURL('image/png');
var binaryData = atob(imgDataUrl.split(',')[1]); // Blob ๋ฐ์ดํฐ ๋ณํ
var array = [];
for (var i = 0; i < binaryData.length; i++) {
array.push(binaryData.charCodeAt(i));
}
var blob = new Blob([new Uint8Array(array)], {type: 'image/png'});
const title = document.querySelector('#title')
const content = document.querySelector('#content')
const moviePk = "{{movie.pk}}"
if (drawBtnFlag) {
} else {
data.set('draw', blob, 'my_draw.png')
}
data.set('title', title.value)
data.set('content', content.value)
axios({ // Axios
url: '',
method: 'POST',
data: data,
headers: {"X-CSRFToken": csrfToken, "Content-Type": "multipart/form-data" },
})
.then(response => {
window.location.href = `/movies/detail/${moviePk}/`
})
.catch(err => {
alert("์์ฑ ๊ฐ์ ์ด์์ด ์์ด์")
})
}
@login_required
@require_http_methods(['GET','POST'])
def review_create(request, movie_pk):
movie = Movie.objects.get(pk = movie_pk)
# Profile Color Create
colors = request.user.usercolorrecord_set.all() # Color List
last_color = colors[len(colors)-1] # Picked Color
colors = reversed(colors) # Color Sort(Recently)
if request.method == "POST":
form = ReviewForm(request.POST, request.FILES)
if form.is_valid():
review = form.save(commit=False)
review.movie = movie
review.user = request.user
review.save()
request.user.point += 30
request.user.save()
return redirect('movies:detail', movie_pk)
else:
form = ReviewForm()
context = {
'movie': movie,
'form': form,
'last_color': last_color,
'colors': colors,
}
return render(request, 'movies/review_create.html', context)
@login_required
@require_http_methods(['GET','POST'])
def quiz_create(request):
# Profile Color Create
colors = request.user.usercolorrecord_set.all() # Color List
last_color = colors[len(colors)-1] # Picked Color
colors = reversed(colors) # Color Sort(Recently)
movies = Movie.objects.all()
if request.method == "POST":
form = QuizForm(request.POST, request.FILES)
if form.is_valid():
quiz = form.save(commit=False)
quiz.user = request.user
quiz.save()
request.user.point += 30
request.user.save()
return redirect('movies:index', 2)
else:
form = QuizForm()
context = {
'form': form,
'last_color': last_color,
'colors': colors,
'movies': movies,
}
return render(request, 'movies/quiz_create.html', context)
- ์ฌ์ฉ์๋ณ๋ก ๋ง์ถ ๋ฌธ์ ๋ ๋ณด์ด์ง ์์
- Axios์ quiz_check(views.py) ์ฒ๋ฆฌ
๋ฌธ์ ์ pk์ ์ ์ถ๋ value pk ๋ฅผ ๋น๊ต
- JS Code
{% block option_js %}
<script>
const forms = document.querySelectorAll("#answerForm")
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value
for(const form of forms){
form.addEventListener("submit", function (event) {
event.preventDefault()
const questionPk = event.target.dataset.questionPk
const answer = event.submitter.value
axios({
method: 'post',
url: '/movies/quiz_check/',
headers: {'X-CSRFToken': csrfToken},
data: {
test: "Test",
question_pk: `${questionPk}`,
answer: `${answer}`,
}
})
.then(function (res) {
const correct = res.data.correct
const main = document.querySelector(`#main${questionPk}`)
const right = document.querySelector(`#right${questionPk}`)
const wrong = document.querySelector(`#wrong${questionPk}`)
if (correct) {
main.classList.add('d-none')
right.classList.remove('d-none')
} else {
main.classList.add('d-none')
wrong.classList.remove('d-none')
}
})
.catch(function (err) {
console.log(err)
})
})
}
</script>
{% endblock option_js %}
- quiz_check.py
@login_required
@require_http_methods(['GET','POST'])
def quiz_check(request):
post_body = json.loads(request.body.decode('utf-8'))
question_pk = post_body.get('question_pk')
answer = post_body.get('answer')
quiz = Quiz.objects.get(pk=question_pk)
correct = False
if int(quiz.movie.pk) == int(answer):
print("์ ๋ต")
correct = True
request.user.point += 20
quiz.correct_user.add(request.user)
request.user.save()
context = {
"correct": correct,
}
return JsonResponse(context)
- ์ํ ์ ํ ํ, ๋ฌธ์ ๋ฅผ ๊ทธ๋ฆผํ์ผ๋ก ๊ทธ๋ฆฌ๊ณ ๋ฐ๋ก ์ถ์ ๊ฐ๋ฅ
- ์ฝ๋ - ์๋จ ๋๋ก์ ์ฐธ์กฐ
- ๋ง์ถ๊ณ ํ๋ฆฐ ๊ฒ์ ๋ฐ๋ผ ํด๋น ๋ฌธ์ ๊ฐ ์ฒดํฌ๋๊ณ ์ฌ์ฉ์๊ฐ ์คํฌ๋กค์ ๋ด๋ฆด ์ ์๋๋ก UI ๊ตฌ์ฑ
- classList.add / remove d-none + promise ๋์๋ฐฉ์์ผ๋ก ๊ตฌ์ฑ
ColorData, TMDB API(์ํ ์ ๋ณด), Youtube Data(trailer) ๋ฑ์ด ๋ค์ด๊ฐ์ ๋ฐ๋ผ ์๋ก์ด DB๋ฅผ ๋ง๋ค ํ์
- ๊ธฐ๋ฅ๊ณผ ๋ค์ด๊ฐ ๋ฐ์ดํฐ๊ฐ ๋ง์ ๋งํผ ๊ท๋ชจ๊ฐ ์๋ DataBase ์์ฑ
- BASE ๋ฐ์ดํฐ์ธ ์ํ๋ฐ์ดํฐ๋ ์ง์ ์กฐํฉํด์ PUSH ํด์ผํ ํ์
- ํด๋ฌ์คํฐ๋ง ๋ฐ RGB ๋์ถ๋ ๋ฐ์ดํฐ
- TMDB API ์ํ์ ๋ณด
- Youtube Trailer URL ๋ฑ
-
๋ง๋ค์ด์ง json ๋ฐ์ดํฐ๋ฅผ Movie๋ฅผ ์ ์ฐธ์กฐ ํ๊ณ ์๋ Color ๋ชจ๋ธ์ PUSH
-
ํน์ ์ 5๊ฐ RGB ๋ฐ์ดํฐ PUSH
-
์์๊ฐ ๋ง์ผ๋ฏ๋ก ์์ ์ Python ์ด์ฉ
- Result
๊ธฐ์กด์ Naver ํ์ ์ API ํธ์ถํ๋ ค ํ์ผ๋, ์ํ์ ๋ชฉ ํฌํจ์ฌ๋ถ๋ก response๋ฅผ ์ฃผ์ด์ TMDB ํ์ ์ผ๋ก ๋ณ๊ฒฝ
- Naver
- TMDB
- Result
- Iframe ์๊ณ ํธ ์์ ์ link PUSH
๋ณต์กํ ๋ชจ๋ธ๊ตฌ์กฐ๋ก ์ญ์ฐธ์กฐ, ์ ์ฐธ์กฐ ๋ถ๋ถ์์ ์ค๋ณต Query ๋ฐ์
- 408 Query / Time 7000ms ~ 9000ms
- 9 Query / Time 250ms ~ 300ms (์ฝ Query 1/40, Time 1/30 ์ผ๋ก ๊ฒฝ๋ํ)
# index/views.py
# Exclude Already Watched Movies(Query Optimization)
already_watched_movies = set()
users_moviecomments = MovieComment.objects.filter(user=request.user.pk).values('movie')
for users_moviecomment in users_moviecomments:
already_watched_movies.add(users_moviecomment['movie'])
users_reviews = Review.objects.filter(user=request.user.pk).values('movie')
for users_review in users_reviews:
already_watched_movies.add(users_review['movie'])
# Query Optimization - movie.color_set + Exclude Already Watched Movies + movie.comments_set + movie.comments.all.count()
movies = Movie.objects.prefetch_related('color_set','comments')\
.annotate(comment_count=Count('comments'))\
.exclude(id__in=already_watched_movies)
res_movies = []
# Order by TMDB_Grade + User_Grade
if mode == 2:
for movie in movies:
naver_score = movie.naver_grade
user_score = 0
for comment in movie.comments.all():
user_score += comment.grade
if user_score:
user_score = round((user_score / movie.comment_count),1) * 2
score = (naver_score + user_score) / 2
else:
score = naver_score
res_movies.append([movie, score])
# Order by Release Date
elif mode == 3:
for movie in movies:
score = movie.release_date
res_movies.append([movie, score])
# Order by Search
elif mode == 4:
datas = request.GET['searchData'].split() # Input Data list
for movie in movies: # query in title : +10, query in overview +1
temp_cnt = 0
title = movie.title
overview = movie.overview
for data in datas:
if data in title:
temp_cnt += 10
if data in overview:
temp_cnt += 1
if temp_cnt >= 1: # If query in title, content, Can be Searched
res_movies.append([movie,temp_cnt])
AWS EC2(Ubuntu Server 18.04 LTS), Cloud9, Gunicorn, NGINX
Django contains STATIC / MEDIA
- static๊ณผ media files๊ฐ ํฌํจ๋์ด ์์ด NGINX์์ ์ ๊ทผํ์ง ๋ชปํ๋ ์ด์๊ฐ ๋ฐ์(404)
- static์ ๊ฒฝ๋ก๋ฅผ staticfiles/static์ผ๋ก ์ก์์ค์ staticfiles/static ๊ฒฝ๋ก๋ฅผ ๋ง๋ค์ด ์ค์ผ๋ก์จ ํด๊ฒฐ
- media ํ์ผ ์ ๊ทผ์ ๊ฒฝ์ฐ์๋ static ์ฒ๋ผ root๋ก ์ก์์ฃผ๋ฉด ์๋ฌ๊ฐ ๋ฐ์
- alias๋ก ๋ฐ๊ฟ mediaํ์ผ์ ์ ๊ทผํ ์ ์๋๋ก ์ฒ๋ฆฌ
server_name 18.214.88.219 prismfilmstudio.com www.prismfilmstudio.com;
location ^~ /static/ {
root /home/ubuntu/pjt10/staticfiles/;
}
location ^~ /media/ {
alias /home/ubuntu/pjt10/media/;
}
location / {
include proxy_params;
proxy_pass http://127.0.0.1:8000;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/prismfilmstudio.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/prismfilmstudio.com/privkey.pem; # managed by Certbot
- Django - STATIC ROOT, ALLOWED_HOSTS, collectstatic ๋ช ๋ น
- Gunicorn ์ค์
[Unit]
Description=gunicorn daemon
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/pjt10
ExecStart=/home/ubuntu/pjt10/venv/bin/gunicorn \
--workers 3 \
--bind 127.0.0.1:8000 \
pjt10.wsgi:application
[Install]
WantedBy=multi-user.target
-
www.~ ์ผ๋ก๋ ๋ค์ด์ฌ ์ ์๋๋ก A type ํ๋ ๋ ๊ฐ๋ฐฉ
-
ํด๋น ์ค์ ์ ๋ฐ๋ฅธ NGINX ์ค์
-
HTTPS ์ค์
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx
formdata - https://developer.mozilla.org/ko/docs/Web/API/FormData
๊ทธ๋ฆผํ - https://github.com/shlee0882/painting-js
blob Data ๋ณํ - https://codebb.tistory.com/22
๋ฐฐ๊ฒฝ ํ ๋ง - https://codepen.io/trending
Axios - https://github.com/axios/axios
Base - https://edu.ssafy.com
์ปฌ๋ฌ๋ฐ์ฝ๋ - https://happycoding.io/gallery/movie-colors/index
๋ํ ์์ฑ - https://airows.com/culture/color-palettes-from-famous-movie-scenes
ํฐ์ด ์์คํ - https://www.acmicpc.net/, https://solved.ac/
์์ด๋์ด ํ์์ ์๋นํ ๊ณต์ ๋ค์๋ค. ์์ด๋์ด๊ฐ ๋ ์ค๋ฅด์ง ์์ผ๋ฉด ์์
์ฐฉ์ํ๊ธฐ ํ๋ ํ์
์ธ๋ฐ, ๋คํํ ํ์ด์๋ ๊ฑดํฌ๋๊ป์ ์์ด๋์ด๋ฅผ ์์ฝ๊ฒ ๋์ถํ ์ ์๋๋ก ํธํ ๋ถ์๊ธฐ๋ฅผ ๋ง๋ค์ด ์ฃผ์
์ ์ข์ ์์ด๋์ด๋ค์ด ๋ง์ด ๋์ฌ ์ ์์๋ ๊ฒ ๊ฐ๋ค.
์ด๋ฐ ๋ฐ์ดํฐ์์
๊ณผ C# ์ฝ๋๋ฅผ ๋ณํํ๋๋ผ ์นํ์ด์ง ์ชฝ์๋ ์ ๊ฒฝ์ ๋ง์ด ๋ชป ์ผ์๋๋ฐ, ์์
์ข
๋ฃ ํ ์ดํด๋ณด๋ ๊ฑดํฌ๋๊ป์ ๋๋ถ๋ถ์ ๊ตฌ์กฐ์ ํ์ ์ก์ ๋์ ์ํ๋ผ ๋๋ผ์ ๊ณ , ๊ฐ์ฌํ๊ณ , ํํธ์ผ๋ก๋ ์ ๋ง ์ฃ์ก์ค๋ฌ์ ๋ค. ์ค๋๋ง์ C#์ ๋ค๋ค๋์ง๋ผ ์ฝ๋๊ฐ ์๊พธ ์ ๋ฉ๋๋ก ๊ตด๋ฌ๊ฐ์ ์์ ํ๋ ์๊ฐ๋ ๊ฝค ๊ฑธ๋ ธ๊ณ , ์ํ ์๊ณ ํธ ์ ์ฅ ๋ฐ ๋ฐ์ดํฐ ์ถ์ถ๊น์ง ํด์ ๋๋ต 2~3์ผ์ด ๊ฑธ๋ ธ๋๋ฐ ๊ทธ ์์ ์ด๋ป๊ฒ ๊ทธ๋ ๊ฒ ๋จ๋จํ ํ์ ๋ง๋ค์ด ๋์
จ์๊น?
๊ฐ๋งํ ์์ ์๋ง์ ์์ด์ ๋ด๊ฐ ํ ์ ์๋๊ฑธ ์ต๋ํ ํ๋ ค๊ณ ํ๋ค. ์์ ๋ง๋ ๋ฐ์ดํฐ๋ค์ ์ ๋ํ๊ณ , ์นํ์ด์ง์์ ์ฝ๊ฐ์ ๋ํ
์ผ๋ค์ ์ถ๊ฐ์ํค๋ฉฐ '์ด๋ป๊ฒ ํด์ผ ๋ ์ ์ ์๊ฒ ๊น๋ํ ์ธ์์ ์ค ์ ์์๊น?'๋ฅผ ๊ณ ๋ฏผํ๋ค.
์น์ ๊ต์ ์ผ๋ก ๋ณธ๊ฐ์ ์ ์ ๋ค๋
์จ ์ฌ์ด์, ํํ์ด์ง๋ฅผ ๋ ํธํ๊ณ ๊ธฐ๋ฅ์ ์ด๊ฒ ๊ตฌํํ ๊ฑดํฌ๋์ ๋ต๊ณ ์ ์ฌ๋ฌ ๋ง๊ฐ์ด ๊ต์ฐจํ๋ค. ๋ ์ ๋ง ์ด์ฌํ ํ๊ณ ์๋๊ฐ? ๋ ๋๋ฌธ์ ๊ฑดํฌ๋์ ์์ด๋์ด๊ฐ ๊ตฌํ๋์ง ๋ชป ํ๋ ๊ฒฝ์ฐ๊ฐ ์๊ธฐ์ง ์์๊น?
์์ทจ๋ฐฉ์ ์์ ์ปดํจํฐ๋ฅผ ์ผ๋ฉฐ ๊ณจ๋ํ ์๊ฐํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ '๋ด๊ฐ ๋ถ์กฑํจ์ด ์๋ค ํ๋ค, ๊ฑดํฌ๋์ ๋ฐ๋ชฉ์ ๋ถ์ก๋ ๋ชจ์์๋ ๋์ง ๋ง์์ผ๊ฒ ๋ค'๊ณ ๋ค์งํ๋ค.
์ฐ๋ฆฌ๋ ์ด์ ์ ์ผ๋ก ์ฌ๋ ์๊ฐ๋ ์ค์ฌ๊ฐ๋ฉฐ ์์
์ ๋ชฐ๋ํ๋ค. ๊ฑดํฌ๋์ ๊ณ์ํด์ ๋ ๊ฐํธํ๊ณ ์ง์์์๊ฒ ์นํ์ด์ง๋ฅผ ๊ตฌ์ถํด ๋์๊ฐ์ผ๋ฉฐ, ๋๋ ๊ทธ ํ์ด์ค์ ๋ง์ถฐ ์ด์ฌํ ๋จธ๋ฆฌ๋ฅผ ๊ตด๋ฆฌ๊ณ ์์ ์์ง์๋ค. ํ๋ค์ด์ ์ ์ ์ฌ๊ณ ์ถ์์ ๋๋ ์์์ง๋ง, ๋๋ณด๋ค ๋ ๊ณ ์ํ์๋ ๊ฑดํฌ๋์ ๋ณด๋ฉด์ ๋์ฑ ๋ ์ด์ฌํ ์ฐธ์ฌํ๋ค.
๊ณํํ ๋๋ถ๋ถ์ ๊ฒ๋ค์ ์ด๋ฃจ๊ณ ๋์๋, ์ฐ๋ฆฌ๋ ๋ ๋ง์ ๊ฒ๋ค์ ์๊ฐํ๋ค. ์ฃผ๋ณ ์ง์ธ๋ค์๊ฒ ์นํ์ด์ง์ ๋ํด ๋ณด์ฌ์คฌ๋๋ฐ, ๋ค๋ค ํฅ๋ฏธ๋ ๋๋์ง์ธ์ ํ์ด์ง์์ ๋ง์ ์๊ฐ์ ๋ณด๋ด์ง ์์๊ณ , ์ฐ๋ฆฌ๋ ๊ทธ ๋ '์ฌ์ฉ์์ ์
์ฅ์์ ์๊ฐํ๋ผ'๋ ๋ง์ ๋ ์ฌ๋ ธ๋ค.
๊ทธ๋, ํฅ๋ฏธ๋ ์ค์ํ์ง๋ง ์ฌ๋ฏธ๋ ์ค์ํ๊ตฌ๋.
์ฐ๋ฆฐ ์ฌ์ฉ์์๊ฒ ์ฌ๋ฏธ๋ฅผ ์ฃผ๊ธฐ ์ํด ํ๋ฃจ๋ฅผ ์จํต ํฌ์ํ์ฌ ์๋ก์ด ํ์ด์ง๋ฅผ ๊ตฌ์ถํ์๊ณ , ํผ๊ณคํ ์์ค์๋ ์๋ก ๋ํํ๊ณ ๋ฐฐ๋ คํ๋ฉฐ ๋ง์นจ๋ด ์์ฑํ ์ ์๊ฒ ๋์๋ค.
ํด๋น ํ๋ก์ ํธ๊ฐ ํ๋ค์ง ์์๋ค๋ฉด ๊ฑฐ์ง์ด๊ฒ ์ง๋ง ๊ทธ๋งํผ ๋ฟ๋ฏํ ๊ฐ์ ์ ๋๊ผ๊ณ , ์ด๋ก ์ธํด ๋ง์ ๊ฒ์ ๋ฐฐ์ ๋ค๊ณ ๋น๋นํ ๋งํ ์ ์๋ค. ํนํ๋ ๊ฑดํฌ๋์ ๊ณํ์ฑ๊ณผ ๊ณง์ ์์ง์ ์ํฅ์ ๋ง์ด ๋ฐ์, '์ด๋ ํ ํ๋ก๊ทธ๋๋จธ๊ฐ ๋์ด์ผ ํ๋๊ฐ'๋ฅผ ๊ณ ์ฐฐํ๊ฒ ๋ ์์คํ ์๊ฐ์ด์๋ ๊ฒ ๊ฐ๋ค.
์ ๋ง ๋ง์ ๊ฒ์ ๊นจ๋ซ๊ณ ๋ฐฐ์ธ ์ ์๊ฒ๋ ํด ์ฃผ์ ๋์ ํ์ด ๊ฑดํฌ๋, ์ฑ์ฅํ ๊ณ๊ธฐ๋ฅผ ์ค ์ธํผ์ ์กฐ์ธ์ฃผ์ ๊ต์๋, ์์ํด์ค ๋์ 2๋ฐ ๋๊ธฐ๋ถ๋ค๊ณผ ์ฃผ๋ณ ์ง์ธ๋ค์๊ฒ ์ง์ฌ์ผ๋ก ๊ฐ์ฌ ๋๋ฆฌ๋ฉฐ ๊ธ์ ๋ง์น๊ฒ ๋ค.
์ฃผํธ๋๊ณผ ํจ๊ป์ฌ์ ์ ๋ง์น ์ ์์๋ค. ์ํ๊ณ ์ถ์ ์์ฌ ๋๋ฌธ์ ์๋๋ฅด๊ณ ์กฐ๊ธํ๊ฒ ํ์ด๋ถ์ ๋ํ ๊ฒ์ ์๋์์ ์ง ํํ๊ฐ ๋๋ค. ๋ค๋ฅธ ์ฌ๋๊ณผ ํจ๊ปํ๋ ์ฒซ ํ๋ก์ ํธ์ฌ์ ์ํฐ ๋ถ๋ถ๋ ๋ง๊ณ , ํ๋ ๋ถ๋ถ๋ ์์์ ํ
๋ฐ ๋ด์ํ์ง ์๊ณ ๋ง์ง๋ง๊น์ง ๊ฐ์ด ์ ์์ฃผ์ ์ฃผํธ๋์๊ฒ ๊ฐ์ฌ์ ๋ป์ ์ ํ๋ค.
์ด๋ฐ์ ๊ฐ์ง๊ณ ์์๋ ์๊ฐ, '๊ฒฐ๊ณผ๊ฐ ์ข์ผ๋ฉด ์ข๋ค.'๋ผ๋ ์๊ฐ์ด ๊ณผ์ฐ ๋ง๋ ์๊ฐ์ผ๊น, ํ์
์ด๋ผ๋ ํจ๊ป ๊ฐ๋ ๊ฒ์์ ์ด ์๊ฐ์ ํ๋ฆด ์๋ ์๊ฒ ๋ค๊ณ ์๊ฐํ๋ค. ๊ทธ๋์ ์ด ํ๋ฆฐ ์๊ฐ์ ๊ธฐ์ ๋ก ํ ๊ฒ์ ๋ํด์ ์ ํจ๊ป ์์ฃผ์ ํ์ด๋ถ์ ๋ํ ๊ณ ๋ง์์ด ํฐ ๊ฒ ๊ฐ๋ค.
ํ๋ก์ ํธ์ ์์ฑ๋๋ ๋๋ค. ์ฝ 7์ผ, 8์ผ์ด๋ผ๋ ์๊ฐ๋์ ์ ๋ง ๋ง์ ๊ฒ์ ๊ตฌํํ๋ค. ๋ง๋ค๊ณ ์ถ์ ๊ฑด ๋๋ถ๋ถ ๋ง๋ ๊ฒ ๊ฐ๋ค. ํ์ง๋ง ์ด ์ฝ๋ ๊ทธ๋๋ก ๋ค๋ฅธ ์ฌ๋์ด ์ด์ํ๋ค๋ฉด?, ๊ธ์ ๋ถ์กฑํ ๋ถ๋ถ์ด ๊ต์ฅํ ๋ง์ ๊ฒ ๊ฐ๋ค. ์ ๋ง ๊น๋ํ๊ณ ์ ์ ๋๊ณ , ์ค๋ณต์์๋ฅผ ํผํ๊ณ , ์ปค๋ฉํธ๋ฅผ ํตํ ์ธ์
๊ตฌ๋ถ๊น์ง ์ ์ ๋ ์ฝ๋๋ก ๋ง๋ค๋ ค๊ณ ๋ง์ด ๊ณต๋ค์๋ค. ๋ค๋ง, ๋ง์ง๋ง ํด์ฆ๋ถ๋ถ์ ํ๋ฉฐ ์กฐ๊ธํด์ง๋ฉฐ ํด๋น ๋ถ๋ถ์ ๋ํ ์ฝ๋ ์ ์ ๋ฐ ๋ฏธ๋์ด ํ์ผ ์ฉ๋ ์ฒ๋ฆฌ๊ฐ ์ด๋ฃจ์ด ์ง์ง ์์๋ค. ๊ทธ๋์ ๋ ์์ฌ์์ด ํฌ๋ค. ๋, ์๋ฌด๋ฆฌ ๊ณต๋ค์๋ค ํ๋๋ผ๋ ํ์
์๊ฐ ๋ณด๋ฉด ๋ง์ด ๋ถ์กฑํ ์ฝ๋์ผ ๊ฒ์ด ๋ถ๋ช
ํ๋ค. ๋ด๊ฐ ๋ชจ๋ฅด๋ ๋ถ๋ถ, ๋ฏธ์ํ ๋ถ๋ถ์ ์ง๊ธ ๊ฐ๋ฐ ํ์
์์ ๋๋ถ๋ถ์ผ ํ
๋๊น. ๋จ, ์ต์ ์ ๋คํ์์๋ ํ๋ฆผ์ด ์๋ค. ํฐ ์ผ์ ์ฐ์ฐํ ์ด๋ฃจ์ด์ง์ง ์๋ฏ์ด, ์์ ์ผ ํ๋ํ๋์ ์ต์ ์ ๋คํ ๊ฒ ๊ฐ๋ค.
1ํ๊ธฐ๋ฅผ ๋ง์น๋ ํ๋ก์ ํธ์ด๊ธฐ์ 1ํ๊ธฐ์ ๋ํ ์๊ฐ๋ ๋ง๋ค.
์ค๋์๊ฐ์ ์ ๋ง ์น์ ํ๊ณ ์ ์ง๋ํด์ฃผ์ juan ๊ต์๋๊ณผ ํจ๊ป ๋ฌ๋ ค์จ ์์คํ ์ฐ๋ฆฌ๋ฐ ๋๊ธฐ๋ถ๋ค, ๋ถ์กฑํ ๋์ ํ์ด๋ก ํจ๊ป ํ๋ ์ง์๋, ์ง์ญ๋, ๊ทธ๋ฆฌ๊ณ ์ ๋ง ๊ฐ์ฌํ ์ฃผํธ๋๊น์ง. SSAFY์์ ๋ง์ ๊ฒ์ ๋ฐฐ์ฐ๊ณ ์ถ์๋ค. ๊ทธ ๊ฒ์ ํฅํด ๋ฌ๋ ค๊ฐ๋๋ฐ ์์ด ์ด ๋ถ๋ค์ด ์์๋ค๋ฉด ๋ถ๊ฐ๋ฅํ์ง ์์์๊น ์ถ๋ค.
ํ๋ก์ ํธ๋ ์ฌ๊ธฐ์ ๋ง์ณ ์์์ญ์ญํ ๋ง์์ด ํฌ์ง๋ง, ๊ฐ๋ฐ์ ๊ณ์ ํ ์ ์์ผ๋๊น, ๊ทธ๊ฑธ๋ก ์ข๋ค.