이전 글에서는 "저시력자를 위한 실내 근거리 물체 탐지 및 알림 시스템" 프로젝트에 대한 개요를 설명했습니다.
[인공지능 프로젝트] 저시력자를 위한 실내 근거리 물체 탐지 및 알림 시스템 - 1편 (문제 정의와
이번에는 제가 학교에서 진행했던 "저시력자를 위한 실내 근거리 물체 탐지 및 알림 시스템" 프로젝트에 대해서 설명하겠습니다. 본 글은 '왜 이 프로젝트를 했는가', '구체적으로 어떻게 문제를
whitecode2718.tistory.com
본 글에서는 프로젝트에서 진행한 YOLO의 데이터 전처리 과정에 대해 구체적으로 소개하겠습니다.
📌 깃허브 링크: https://github.com/lko9911/Artificial-Intelligence-Project
GitHub - lko9911/Artificial-Intelligence-Project: 강원대학교 인공지능 수업_프로젝트
강원대학교 인공지능 수업_프로젝트. Contribute to lko9911/Artificial-Intelligence-Project development by creating an account on GitHub.
github.com
Artificial-Intelligence-Project/Prototype/YOLO/yolo_seg(인공지능).ipynb at main · lko9911/Artificial-Intelligence-Project
강원대학교 인공지능 수업_프로젝트. Contribute to lko9911/Artificial-Intelligence-Project development by creating an account on GitHub.
github.com
1. 객체 탐지 기능에서 YOLO를 사용한 이유

YOLO는 Ultralyrics사의 실시간 객체 검출 모델입니다. "객체 검출"을 수행하는 여러 모델중 YOLO모델을 선택한 이유는 다음과 같습니다.
실시간 처리 성능 (Real-time Object Detection)
YOLO의 가장 큰 장점은 단 한 번의 Forward Pass로 객체를 검출한다는 점이기 때문에 실시간으로 객체 검출을 수행하게 됩니다. 실시간성은 중요한 본 프로젝트에 매우 중요하기 때문에 YOLO 모델을 사용하였습니다.
- 기존 방식: 슬라이딩 윈도우 → 후보 영역 생성 → 분류 → 매우 느림
- YOLO 방식: 이미지를 한 번에 보고 바운딩 박스 + 클래스를 동시에 예측
또한 기본에 GPU에 의존적인 CNN모델과는 달리 CPU에서도 동작할수 있고(이 경우 살짝 버벅이지만), GPU를 사용한다면 매우 빠르게 연산이 가능하여 프로젝트 목적에 맞게 객체 검출을 할 수 있었습니다.
경량화 및 모바일 환경 최적화
YOLO 계열은 모델 크기에 따라 다양한 버전이 제공되며 사용환경에 맞춰 경량화를 쉽게 적용할 수 있습니다.

또한 YOLO의 경우 코랩에서 간단하게 학습시키고 테스트해볼 수 있기 때문에 개발 생산성이 뛰어나고, 프로젝트 구현까지 걸리는 시간이 짧으며, 다양한 모델의 호환성까지 좋습니다.
정리하면 YOLO 모델은 빠르고, 경량화되어 있으며, 확장성이 좋기 때문에 프로젝트의 객체 검출 기능으로 선택하였습니다.
Ultralytics에서 소개하고 있는 YOLO에 대한 자세한 내용은 아래 사이트에서 확인 가능합니다.
YOLO12: Attention-Centric Object Detection
docs.ultralytics.com
YOLO 모델도 검출한 대상의 영역까지 검출이 가능한 seg와 바운딩박스 형태로 검출 가능한 dectect 모델로 나눌수 있는데요.
처음에는 구체적인 대상의 분석을 위해 seg역할을 할 수 있는 모델을 학습시켜 사용했습니다.
2. YOLOv11n-seg를 재학습할 데이터셋 만들기
YOLO를 어떻게 학습시키고 작동시켰는지와 검증한 과정에 대해서 설명하겠습니다. 결론부터 말하면 YOLOv11n-seg의 성능이 좋지 않아 seg가 아닌, dectect 기능으로 바꾸고 성능 향상을 위해 데이터셋을 NYU에서 직접 라벨링한 것으로 바꾸었습니다.
데이터 전처리 과정 개요
NYU Depth V2 데이터셋 출처
NYU Depth V2 « Nathan Silberman
NYU Depth Dataset V2 Nathan Silberman, Pushmeet Kohli, Derek Hoiem, Rob Fergus If you use the dataset, please cite the following work: Indoor Segmentation and Support Inference from RGBD Images ECCV 2012 [PDF][Bib] Samples of the RGB image, the raw depth i
cs.nyu.edu
저는 아래 NYU 데이터셋이 포함된 SUNRGBD _V1 데이터셋을 다운받아 전처리를 진행했습니다. (NYU에서 더욱 많은 이미지를 사용하기 위해)
SUN RGB-D: A RGB-D Scene Understanding Benchmark Suite
Although RGB-D sensors have enabled major breakthroughs for several vision tasks, such as 3D reconstruction, we haven not achieved a similar performance jump for high-level scene understanding. Perhaps one of the main reasons for this is the lack of a benc
rgbd.cs.princeton.edu

사용한 데이터셋은 위에서 다운 받았고, Visual Studio Code에서 전처리 작업을 진행하였습니다. 우선, 제가 원하는 데이터셋의 구조는 다음과 같습니다.
Dataset_NYU
ㄴimages
ㄴNYU0001.jpg
ㄴNYU0002.jpg...
ㄴlabels
ㄴNYU0001.txt (이미지에 해당하는 라벨링 정보 - 폴리곤)
ㄴNYU0002.txt...
가장 먼저 해야할 작업은 제가 다운 받은 데이터 셋을 위와 같이 바꾸기 위해 분석해야합니다. SUNRGBD 데이터셋을 받으면 다음과 같은 데이터 구조가 보입니다.

여기서 제가 원하는 데이터셋 구조에서 images에 들어갈 이미지는 NYU000*/image/NYU000*.jpg 형태란 것을 알 수 있습니다. 이는 간단히 폴더를 다루는 파이썬 코드로 간단히 추출이 가능합니다.
labels에 들어갈 NYU000*.txt는 class_id x_1 y_1 x_2 x_3 ... x_n y_n 형식으로 저장되어야 하기 때문에 SUNRGBD에서 각 클래스가 무엇인지, 몇개인제, 클래스의 아이디가 어떻게 되는지 알아야하고, 그에 해당하는 각 폴리곤 좌표( x_1 y_1 x_2 x_3 ... x_n y_n)를 추출하면 됩니다.
데이터셋 만들기
저는 따로 SUNRGBD/kv1/NYUdata/NYU000*/image/NYU000*.jpg 이는 모든 NYU 이미지를 따로 코딩하고 뽑았습니다. 이 과정은 쉽기 때문에 labels을 어떻게 추출할지 고민을 많이 했습니다.
대부분 이미지의 라벨링 정보는 annotation에 있으니 annotation/index.json 형태를 확인해야 합니다.
{"frames":[{"polygon":[{"x":[],"y":[],"XYZ":[],"object":0},{"x":[543,546,553,552,561,561,561,543],"y":[271,250,245,233,233,237,273,271],"XYZ":[[1.0618600845336914,0.25740399956703186,2.1279149055480957],[1.0610990524291992,0.16919663548469543,2.102034091949463],[1.3752764463424683,0.18801462650299072,2.653501272201538],[1.3812333345413208,0.12766849994659424,2.6749415397644043],[1.0486698150634766,0.09377912431955338,1.9648830890655518],[1.0494422912597656,0.10900712013244629,1.9663305282592773],[1.0349279642105103,0.24204279482364655,1.9391350746154785],[1.3518670797348022,0.32770416140556335,2.7090744972229004]],"object":1},{"x":[376,380,458,459,453,458,427,384,376,376],"y":[366,269,289,326,400,427,427,396,396,366],"XYZ":[[0.601142942905426,1.031779170036316,3.3933427333831787],[0.6297850608825684,0.39896926283836365,3.406769037246704],[0.8433074951171875,0.3916151523590088,2.5158863067626953],[0.8516175746917725,0.5733544230461121,2.526153087615967],[0.8395481109619141,0.9530978798866272,2.5788092613220215],[0.8487112522125244,1.0675594806671143,2.5320074558258057],[0.7709261775016785,1.1800563335418701,2.7988245487213135],[0.6306431889533997,1.1850969791412354,3.2748420238494873],[0.5982917547225952,1.2221555709838867,3.3772482872009277],[0.6006419062614441,1.0309191942214966,3.390514373779297]],"object":2},{"x":[425,428,561,561,561,492,494,427,425],"y":[125,86,28,132,189,192,125,140,125],"XYZ":[[0.9284843802452087,-0.5484304428100586,3.4186758995056152],[0.9475183486938477,-0.8047733902931213,3.4160349369049072],[1.2766239643096924,-0.8309109210968018,2.391998767852783],[1.2345001697540283,-0.33986130356788635,2.3130717277526855],[1.24245023727417,-0.08630719780921936,2.327967643737793],[1.1099785566329956,-0.08667761087417603,2.7699496746063232],[1.1253771781921387,-0.44623273611068726,2.7816200256347656],[0.9309375286102295,-0.4444773197174072,3.3797409534454346],[0.928629457950592,-0.5485161542892456,3.419210195541382]],"object":3},{"x":[370,501,561,561,561,429,370],"y":[81,1,1,13,26,85,81],"XYZ":[[0.5719027519226074,-0.8469365239143372,3.45373272895813],[0.9309800863265991,-0.8894289135932922,2.2268662452697754],[1.1385741233825684,-0.8520721793174744,2.133336067199707],[1.2093989849090576,-0.8526668548583984,2.2660398483276367],[1.324710488319397,-0.871776282787323,2.48209810256958],[0.9878401160240173,-0.8400475382804871,3.5368294715881348],[0.57499760389328,-0.8515197038650513,3.4724225997924805]],"object":4},{"x":[67,416,368,67],"y":[1,1,40,1],"XYZ":[[-1.2524727582931519,-1.1956639289855957,2.9935879707336426],[0.7197849750518799,-1.1307477951049805,2.831057071685791],[0.562345027923584,-1.1273776292800903,3.476950168609619],[-1.2542550563812256,-1.197365403175354,2.99784779548645]],"object":5},{"x":[1,55,55,66,62,1,1],"y":[360,361,395,405,427,427,360],"XYZ":[[-1.1262000799179077,0.6037691235542297,2.0641963481903076],[-0.9329454302787781,0.6221355199813843,2.113065004348755],[-0.9355276226997375,0.7627067565917969,2.118913412094116],[-0.8859021663665771,0.7993009686470032,2.107722759246826],[-0.7832913398742676,0.7715864777565002,1.8300271034240723],[-0.9539187550544739,0.7371807098388672,1.7484244108200073],[-1.3615185022354126,0.7299261093139648,2.4955084323883057]],"object":6},{"x":[136,138,142,151,151,254,260,285,283,281,274,273,153,145,145,136,136],"y":[393,343,318,312,290,293,317,344,358,427,424,409,410,414,427,427,393],"XYZ":[[-0.8133119940757751,1.0147768259048462,2.8497188091278076],[-0.8816164135932922,0.8133078813552856,3.1313388347625732],[-0.7978494167327881,0.616367518901825,2.913593292236328],[-0.7511467337608337,0.5856660008430481,2.928548574447632],[-0.7708430290222168,0.4735942482948303,3.005340099334717],[-0.1841297596693039,0.5188255906105042,3.175844669342041],[-0.14300918579101562,0.6458739638328552,3.081141948699951],[0.0054192193783819675,0.801845371723175,3.064467191696167],[-0.005582867655903101,0.7724259495735168,2.6760752201080322],[-0.015787050127983093,1.1204193830490112,2.657379150390625],[-0.05305885896086693,1.1354565620422363,2.7304883003234863],[-0.053706154227256775,0.9729125499725342,2.514413833618164],[-0.6001680493354797,0.9237865805625916,2.3756189346313477],[-0.6402871608734131,0.9472650289535522,2.388641119003296],[-0.6524975299835205,1.0263183116912842,2.4341928958892822],[-0.6619765758514404,0.9779452681541443,2.319463014602661],[-0.6717244982719421,0.8381168246269226,2.3536183834075928]],"object":7},{"x":[1,4,9,17,18,1,1],"y":[263,250,251,317,324,324,263],"XYZ":[[-1.4314916133880615,0.2769298255443573,2.6237611770629883],[-1.4536442756652832,0.21675674617290497,2.6929028034210205],[-1.500131368637085,0.23320777714252472,2.829533576965332],[-1.503922700881958,0.6124415993690491,2.9216527938842773],[-1.5159287452697754,0.6595313549041748,2.9560446739196777],[-1.3794382810592651,0.5641079545021057,2.528353452682495],[-1.4315828084945679,0.2769474387168884,2.6239283084869385]],"object":8},{"x":[379,389,441,429,421,426,488,537,545,544,551,379],"y":[265,260,258,261,269,272,270,280,282,298,308,265],"XYZ":[[0.626555323600769,0.3747008144855499,3.4250059127807617],[0.6540656089782715,0.32270050048828125,3.234607696533203],[0.7936713695526123,0.25169989466667175,2.6243250370025635],[0.774080216884613,0.28183916211128235,2.7714905738830566],[0.7528510093688965,0.33411431312561035,2.8529775142669678],[0.7664365172386169,0.34436145424842834,2.8021316528320312],[0.8946918249130249,0.27098989486694336,2.2764980792999268],[0.9931265711784363,0.2817936837673187,2.0373895168304443],[1.005886435508728,0.2843734920024872,2.0002951622009277],[1.0008333921432495,0.3456428647041321,1.9979040622711182],[1.0097854137420654,0.3774201571941376,1.9629100561141968],[0.5997480750083923,0.3586691915988922,3.2784669399261475]],"object":9},{"x":[454,460,546,546,545,459,454],"y":[400,290,310,327,427,427,400],"XYZ":[[0.845452070236206,0.9541517496109009,2.581660747528076],[0.8641161322593689,0.40162819623947144,2.548656940460205],[1.0079302787780762,0.3916150629520416,1.9967070817947388],[1.0124781131744385,0.45909786224365234,2.005716323852539],[0.9882773756980896,0.8286117315292358,1.9652780294418335],[0.8539488911628723,1.0680067539215088,2.5330684185028076],[0.8475157618522644,0.9564807415008545,2.5879623889923096]],"object":10},{"x":[271,271],"y":[520,520],"XYZ":[[0,0,0],[0,0,0]],"object":11},{"x":[],"y":[],"XYZ":[],"object":12},{"x":[282,345,352,353,375,385,426,282,282],"y":[414,410,405,396,396,398,427,425,414],"XYZ":[[-0.012374323792755604,1.2226893901824951,3.0831563472747803],[0.3714085519313812,1.2301349639892578,3.1634275913238525],[0.4248979389667511,1.230971097946167,3.246018886566162],[0.4510869085788727,1.2289729118347168,3.3960869312286377],[0.5972272157669067,1.2333996295928955,3.4083194732666016],[0.6568825840950012,1.235192060470581,3.3772988319396973],[0.8075428605079651,1.244815468788147,2.952418327331543],[-0.011757696978747845,1.2238683700561523,2.929518938064575],[-0.012290315702557564,1.2143886089324951,3.062224864959717]],"object":13},{"x":[51,146,136,135,135,66,67,56,51],"y":[286,286,354,412,422,425,402,395,286],"XYZ":[[-1.219637155532837,0.40691035985946655,2.7149980068206787],[-0.7610620856285095,0.4286070168018341,2.8597631454467773],[-0.8132294416427612,0.8004962205886841,2.8494298458099365],[-0.823481023311615,1.125522494316101,2.8659956455230713],[-0.8247917294502258,1.1826385259628296,2.8705573081970215],[-1.1719975471496582,1.1649112701416016,2.788395881652832],[-1.1600189208984375,1.0354117155075073,2.7726099491119385],[-1.2057688236236572,0.9873359799385071,2.742967128753662],[-1.229598045349121,0.41023361682891846,2.7371716499328613]],"object":14},{"x":[273,350,361,351,351,345,283,286,275,273],"y":[288,286,292,399,405,409,411,344,330,288],"XYZ":[[-0.06656389683485031,0.47908106446266174,3.116387367248535],[0.40648290514945984,0.47953343391418457,3.1995553970336914],[0.4783014953136444,0.5208742022514343,3.2264485359191895],[0.4110666811466217,1.1718398332595825,3.1872832775115967],[0.41332313418388367,1.21533203125,3.2047791481018066],[0.37508252263069153,1.236146330833435,3.1947202682495117],[-0.006435546558350325,1.2055035829544067,3.0847959518432617],[0.011451148428022861,0.8107485771179199,3.0984933376312256],[-0.053889594972133636,0.7224707007408142,3.0785794258117676],[-0.06568890064954758,0.4727834463119507,3.0754218101501465]],"object":15},{"x":[419,420,424,491,491,527,561,561,523,468,420,419],"y":[174,147,141,127,192,192,191,195,202,205,198,174],"XYZ":[[0.9132826328277588,-0.23175114393234253,3.5122480392456055],[0.9072650671005249,-0.40875834226608276,3.463435411453247],[0.8667092323303223,-0.4164896011352539,3.2140281200408936],[1.1187739372253418,-0.4392324686050415,2.805391311645508],[1.1104761362075806,-0.08713555335998535,2.7845840454101562],[1.3253278732299805,-0.08858249336481094,2.8308238983154297],[1.4765185117721558,-0.09190286695957184,2.7665393352508545],[1.4777051210403442,-0.07063167542219162,2.7687625885009766],[1.3875280618667603,-0.03621690720319748,3.013298511505127],[1.3272147178649902,-0.023353328928351402,3.7442638874053955],[0.9913575053215027,-0.07466070353984833,3.7844536304473877],[0.8904727101325989,-0.22596298158168793,3.4245269298553467]],"object":16},{"x":[291,296,344,348,346,336,291,291],"y":[181,147,150,173,203,208,205,181],"XYZ":[[0.041733089834451675,-0.16431383788585663,3.130232572555542],[0.07266036421060562,-0.37335205078125,3.163435697555542],[0.3702739179134369,-0.3598834276199341,3.2063984870910645],[0.4258774518966675,-0.23477570712566376,3.4571080207824707],[0.41259247064590454,-0.034891605377197266,3.4574503898620605],[0.3454018235206604,-0.0015711868181824684,3.4519052505493164],[0.04534536972641945,-0.021213453263044357,3.4011752605438232],[0.04128448665142059,-0.16254757344722748,3.0965847969055176]],"object":17},{"x":[1,27,22,1,1],"y":[326,327,345,353,326],"XYZ":[[-1.4784345626831055,0.6150367856025696,2.7098021507263184],[-1.4047199487686157,0.6489354968070984,2.835082769393921],[-1.2555688619613647,0.6551999449729919,2.485713243484497],[-1.2570730447769165,0.6428470015525818,2.3040716648101807],[-1.3259090185165405,0.55158531665802,2.4302401542663574]],"object":18},{"x":[1,13,10,1,1],"y":[90,91,166,167,90],"XYZ":[[-1.493167757987976,-0.6236572861671448,2.736806631088257],[-1.4411362409591675,-0.6232542395591736,2.7583670616149902],[-1.4534615278244019,-0.22397874295711517,2.7515077590942383],[-1.4953404664993286,-0.2178238481283188,2.7407889366149902],[-1.4980918169021606,-0.6257138848304749,2.7458317279815674]],"object":19},{"x":[441,449,466,486,491,483,467,451,441],"y":[255,241,247,265,268,268,260,264,255],"XYZ":[[0.9876331686973572,0.29432982206344604,3.265672206878662],[1.1312850713729858,0.2247501015663147,3.5592098236083984],[1.2265137434005737,0.26135122776031494,3.498213052749634],[1.248428225517273,0.35096293687820435,3.208026647567749],[1.259486198425293,0.36377641558647156,3.158235549926758],[1.2109233140945435,0.36381617188453674,3.1585805416107178],[1.1438666582107544,0.3237028121948242,3.244654417037964],[1.0059906244277954,0.33608144521713257,3.127089738845825],[0.9871900081634521,0.2941977381706238,3.264206886291504]],"object":20},{"x":[552,561,561,552,552],"y":[419,289,427,427,419],"XYZ":[[0.9959918260574341,0.7835211157798767,1.9288703203201294],[1.0370659828186035,0.3024633824825287,1.9431411027908325],[1.0013643503189087,0.7910740971565247,1.8762472867965698],[0.9958487749099731,0.8131445646286011,1.928593397140503],[0.9964542984962463,0.7838849425315857,1.929766058921814]],"object":21},{"x":[427,432,449,439,427],"y":[267,263,265,269,267],"XYZ":[[0.7943468689918518,0.3266139626502991,2.883852481842041],[0.9291771650314331,0.3440112769603729,3.259321928024292],[1.0402157306671143,0.3580372631549835,3.272690534591675],[0.8562591671943665,0.3358534276485443,2.8678276538848877],[0.8059400916099548,0.33138078451156616,2.9259414672851562]],"object":22},{"x":[1,10,17,24,27,42,41,41,1],"y":[355,355,346,346,333,333,339,357,355],"XYZ":[[-1.1842830181121826,0.6139904260635376,2.1706559658050537],[-1.2190319299697876,0.6527590155601501,2.307715654373169],[-1.2546751499176025,0.6471741795539856,2.4374425411224365],[-1.288314938545227,0.6824113130569458,2.570155620574951],[-1.30416738986969,0.6329211592674255,2.6321423053741455],[-1.4092515707015991,0.7262964248657227,3.0204639434814453],[-1.4083808660507202,0.7576247453689575,3.0061798095703125],[-1.4072065353393555,0.8611952662467957,3.0036733150482178],[-1.1800042390823364,0.6117721199989319,2.162813425064087]],"object":23},{"x":[146,147,152,272,272,272,146,146],"y":[423,413,412,410,415,427,427,423],"XYZ":[[-0.646028995513916,1.0047885179519653,2.4275152683258057],[-0.6418382525444031,0.9587315320968628,2.4293618202209473],[-0.6206005215644836,0.9574015140533447,2.4378974437713623],[-0.0601571686565876,1.0045596361160278,2.5833356380462646],[-0.05983287841081619,1.0239046812057495,2.5694098472595215],[-0.06016680970788002,1.089375376701355,2.583749771118164],[-0.6458024978637695,1.023144006729126,2.426664113998413],[-0.6464981436729431,1.0055181980133057,2.4292781352996826]],"object":24},{"x":[547,548,560,552,551,547,547],"y":[323,311,311,410,427,427,323],"XYZ":[[1.0212756395339966,0.44578808546066284,2.015449047088623],[1.0286662578582764,0.4005405902862549,2.0223422050476074],[1.0326616764068604,0.384608656167984,1.941901445388794],[0.9973552227020264,0.7510900497436523,1.9315106868743896],[0.9915371537208557,0.8126571774482727,1.927437424659729],[0.9883956909179688,0.8224070072174072,1.950561761856079],[1.0205132961273193,0.4454553425312042,2.013944625854492]],"object":25},{"x":[369,371,421,493,369,369],"y":[63,41,1,1,79,63],"XYZ":[[0.5478869080543518,-0.9370617866516113,3.3476641178131104],[0.5598227381706238,-1.0771427154541016,3.3418848514556885],[0.7274616360664368,-1.1010740995407104,2.756762981414795],[0.8708153963088989,-0.8638070225715637,2.1627166271209717],[0.5438032150268555,-0.8276150822639465,3.3227124214172363],[0.5481631755828857,-0.9375343322753906,3.3493523597717285]],"object":26},{"x":[465,541,546,561,561,554,548,550,544,544,533,521,509,518,518,506,495,495,502,515,503,475,465,465],"y":[208,205,199,198,231,227,234,245,251,265,257,246,247,251,260,249,249,255,255,269,271,256,244,208],"XYZ":[[1.2150969505310059,-0.0015861622523516417,3.484806537628174],[1.431001901626587,-0.018025081604719162,2.8899803161621094],[1.4375232458114624,-0.050692301243543625,2.847729444503784],[1.4630448818206787,-0.05408097058534622,2.7412939071655273],[1.4575250148773193,0.1198149248957634,2.7309513092041016],[1.4486074447631836,0.10070271044969559,2.7846333980560303],[1.4365746974945068,0.14023952186107635,2.824284076690674],[1.4393351078033447,0.1989920437335968,2.8084282875061035],[1.4295963048934937,0.23520927131175995,2.8538177013397217],[1.4250121116638184,0.3112108111381531,2.8446664810180664],[1.4041335582733154,0.2750747501850128,2.9268558025360107],[1.3764044046401978,0.21939407289028168,3.0143747329711914],[1.343131184577942,0.23148444294929504,3.098443031311035],[1.367998480796814,0.2500917911529541,3.034389019012451],[1.3684632778167725,0.30282852053642273,3.0354199409484863],[1.3359662294387817,0.24540241062641144,3.123577117919922],[1.3047409057617188,0.2521660327911377,3.209667205810547],[1.2995012998580933,0.28812044858932495,3.196777582168579],[1.3229650259017944,0.28390058875083923,3.149956703186035],[1.3541600704193115,0.35633477568626404,3.0427165031433105],[1.294377088546753,0.37109893560409546,3.0678114891052246],[1.2171123027801514,0.30449768900871277,3.3077542781829834],[1.211712121963501,0.23953159153461456,3.4750988483428955],[1.2186745405197144,-0.0015908322529867291,3.4950666427612305]],"object":27},{"x":[-1,67,368,368,428,425,427,421,417,466,464,447,439,378,375,353,361,349,269,270,259,254,150,145,143,53,50,51,18,21,1],"y":[2,1,42,84,85,129,136,142,199,209,245,239,257,268,399,399,292,285,285,328,315,289,286,312,284,277,219,204,197,243,245],"object":32}]}],"objects":[{"name":"book"},{"name":"bottle"},{"name":"cabinet"},{"name":"cabinet"},{"name":"ceiling"},{"name":"ceiling"},{"name":"chair"},{"name":"chair"},{"name":"cone"},{"name":"counter"},{"name":"dishwasher"},{"name":"faucet"},{"name":"fireextinguisher"},{"name":"floor"},{"name":"garbagebin"},{"name":"garbagebin"},{"name":"microwave"},{"name":"papertoweldispenser"},{"name":"paper"},{"name":"paper"},{"name":"pot"},{"name":"refridgerator"},{"name":"stoveburner"},{"name":"table"},{"name":"unknown"},{"name":"unknown"},{"name":"wall"},{"name":"wall"},null,null,null,null,{"name":"wall"}],"date":"Tue, 10 Mar 2015 20:41:52 GMT","conflictList":[null],"fileList":["NYU0001.jpg"],"extrinsics":"20150309151641.txt","name":"/SUNRGBD/kv1/NYUdata/NYU0001/"}
JSON 데이터는 3D 실내 장면 데이터셋(SUN RGB-D / NYU)에서 한 프레임의 객체 위치와 형태를 담은 정보입니다. 내용을 단계별로 해석하면 다음과 같습니다.
1. Frames 정보
- "frames" 배열 안에는 polygon 데이터가 있습니다.
- 각 polygon 객체는 화면상 2D 좌표(x, y)와 3D 좌표(XYZ), 그리고 해당 다각형이 어떤 객체(object)에 속하는지 정보를 가지고 있습니다.
- 예시:
- x, y: 이미지에서의 픽셀 좌표
- XYZ: 카메라 좌표계에서의 3D 위치 (미터 단위)
- object: 0부터 시작하는 인덱스로, 나중에 objects 배열과 매칭
- "x":[543,546,553,552,561,561,561,543], "y":[271,250,245,233,233,237,273,271], "XYZ":[[1.06,0.25,2.12], ...], "object":1
2. Objects 정보
- "objects" 배열에는 장면에서 인식된 객체들의 이름이 나열되어 있습니다.
- 예시: {"name":"book"}, {"name":"bottle"}, {"name":"cabinet"}, ...
- 프레임의 polygon.object 값과 매칭되어 어떤 객체인지 알 수 있습니다.
- polygon.object = 1 → "bottle"
- polygon.object = 3 → "cabinet"
3. 특징
- 객체마다 다각형 형태(polygon)를 가지고 있어 3D bounding area를 표현
- 일부 객체는 여러 polygon으로 나누어져 있음
예: cabinet 객체는 두 개의 polygon으로 나눠져 있을 수 있음 - 객체가 없는 polygon은 object:null 처리
4. 주요 객체와 위치 예시
| 0 | book | 작은 다각형 |
| 1 | bottle | 이미지 상 2D와 3D 좌표 있음 |
| 2 | cabinet | 크고 세로형 다각형 |
| 3 | cabinet | 다른 위치의 캐비닛 |
| 4~5 | ceiling | 천장 영역 |
| 6~7 | chair | 좌석 영역 |
| 8 | cone | 작은 물체 |
| 9 | counter | 조리대/테이블 |
| 12~13 | fireextinguisher, floor | 소화기/바닥 |
이 정보를 통해 위의 json에서 클래스 정보와 해당 위치를 뽑을 수 있을거란 확신이 들었습니다. 테스트로 2단계를 진행해 labels 데이터셋을 구성하였습니다.
1단계. 데이터를 각 class_name x_1 y_1 x_2 y_2 ... x_n y_n 으로 바꾸고 클래스 리스트 추출
- 굳이 클래스 이름으로 바꾼 이유는 정상적으로 클래스 이름과 해당 폴리곤 좌표과 매핑되는지 확인하기 위함입니다.
import os
import json
import glob
from PIL import Image
# --- 설정 (CONFIG) ---
SUNRGBD_ROOT = 'SUNRGBD'
# 경고: 이 폴더의 파일은 비표준 형식이므로 폴더 이름을 변경했습니다.
OUTPUT_DIR = os.path.join(SUNRGBD_ROOT, 'polygons_name_output')
CLASSES_FILE = os.path.join(SUNRGBD_ROOT, 'classes.txt')
# 출력 폴더 생성
os.makedirs(OUTPUT_DIR, exist_ok=True)
# ----------------------------------------------------
## 1. 클래스 이름 목록 수집 및 통합 ID 매핑 생성 (classes.txt 생성 목적)
# ----------------------------------------------------
def build_class_mapping():
"""
NYU 데이터셋 전체에서 고유 클래스 이름을 수집하고,
classes.txt를 생성하는 용도로만 사용합니다.
"""
# 💡 검색 패턴: SUNRGBD/kv1/NYUdata/NYU000*/.../index.json
search_pattern = os.path.join(SUNRGBD_ROOT, 'kv1', 'NYUdata', 'NYU000*', 'annotation', 'index.json')
json_files = glob.glob(search_pattern, recursive=True)
unique_classes = set()
for idx_file in json_files:
try:
with open(idx_file, 'r') as f:
data = json.load(f)
top_level_objects = data.get("objects", [])
for obj in top_level_objects:
name = None
if isinstance(obj, dict):
name = obj.get("name")
elif isinstance(obj, str):
name = obj
# 'name'이 유효하면 사용, 'null'이나 빈 문자열이면 건너뜀
if name and isinstance(name, str) and name.strip():
unique_classes.add(name.strip())
except Exception:
continue
# classes.txt 파일 생성을 위한 정렬
sorted_classes = sorted(list(unique_classes))
# classes.txt 파일 생성 (YOLOv8 학습용)
with open(CLASSES_FILE, 'w') as f:
for name in sorted_classes:
f.write(f"{name}\n")
print(f"총 {len(sorted_classes)}개의 고유 클래스 이름 발견. {CLASSES_FILE} 저장 완료.")
# 이 함수는 이름 -> 숫자 ID 매핑을 반환하지만,
# 아래 convert_json_to_name_seg 함수에서는 이 맵을 사용하지 않습니다.
return {name: i for i, name in enumerate(sorted_classes)}
# ----------------------------------------------------
## 2. JSON 파일을 클래스 이름 Segmentation TXT 파일로 변환 (비표준 형식)
# ----------------------------------------------------
def convert_json_to_name_seg(json_files):
total_files = len(json_files)
for file_index, idx_file in enumerate(json_files):
print(f"--- ({file_index + 1}/{total_files}) 처리 중: {os.path.basename(idx_file)} ---")
# 1. JSON 읽기
try:
with open(idx_file, 'r') as f:
data = json.load(f)
except Exception as e:
print(f"JSON 읽기 오류: {idx_file} - {e}")
continue
# 파일별 object ID (index) -> Class Name 매핑 생성
file_object_map = {}
top_level_objects = data.get("objects", [])
for i, obj in enumerate(top_level_objects):
name = None
if isinstance(obj, dict):
name = obj.get("name")
elif isinstance(obj, str):
name = obj
if name and isinstance(name, str) and name.strip():
# 인덱스(object ID)를 클래스 이름으로 매핑
file_object_map[i] = name.strip()
# 2. 이미지 파일 확인 및 크기 가져오기
img_dir = os.path.dirname(os.path.dirname(idx_file))
img_files = glob.glob(os.path.join(img_dir, 'image', '*.jpg'))
if len(img_files) == 0:
print(f"이미지 파일 없음: {img_dir}")
continue
img_path = img_files[0]
try:
with Image.open(img_path) as img:
img_width, img_height = img.size
print(f"이미지 크기: {img_width}x{img_height}")
except Exception as e:
print(f"이미지 열기 오류: {img_path} - {e}")
continue
# 3. 텍스트 출력 파일 경로 설정
base_name = os.path.splitext(os.path.basename(img_path))[0]
txt_path = os.path.join(OUTPUT_DIR, f"{base_name}.txt")
# 4. 비표준 레이블 생성 (클래스 이름 사용)
name_annotations = []
frames = data.get("frames", [])
for frame in frames:
polygons_to_process = frame.get("polygon", [frame])
flat_polygons = []
for p in polygons_to_process:
if isinstance(p, list):
flat_polygons.extend(p)
elif isinstance(p, dict):
flat_polygons.append(p)
for p in flat_polygons:
obj_id = p.get("object")
# 4-1. Object ID로 클래스 이름 찾기 (문자열)
class_name = file_object_map.get(obj_id)
if class_name is None:
continue
# --------------------------------------------------------
# 2D 픽셀 좌표 가져오기 및 타입 처리
# --------------------------------------------------------
x_data = p.get("x", [])
y_data = p.get("y", [])
x_list = [x_data] if not isinstance(x_data, list) else x_data
y_list = [y_data] if not isinstance(y_data, list) else y_data
if len(x_list) != len(y_list) or len(x_list) < 3:
continue
# --------------------------------------------------------
# 5. 좌표 정규화 및 포맷팅
coords_str = []
valid_coords = True
for x, y in zip(x_list, y_list):
try:
x_norm = max(0.0, min(1.0, float(x) / img_width))
y_norm = max(0.0, min(1.0, float(y) / img_height))
coords_str.append(f"{x_norm:.6f} {y_norm:.6f}")
except (ValueError, TypeError):
valid_coords = False
break
if not valid_coords:
continue
# ⭐⭐⭐ 핵심 수정 부분: class_name 변수를 사용하여 문자열로 출력합니다. ⭐⭐⭐
line = f"{class_name} {' '.join(coords_str)}"
name_annotations.append(line)
# 6. TXT 파일 저장
if name_annotations:
try:
with open(txt_path, 'w') as f_out:
f_out.write('\n'.join(name_annotations) + '\n')
print(f"{txt_path} 저장 완료 (주석 {len(name_annotations)}개) ✅")
except Exception as e:
print(f"TXT 저장 오류: {txt_path} - {e}")
else:
print(f"주석이 없어 {os.path.basename(txt_path)}를 생성하지 않음.")
# ----------------------------------------------------
## 메인 실행
# ----------------------------------------------------
# classes.txt 생성을 위해 매핑 함수는 그대로 실행
build_class_mapping()
# 변환 대상 파일 검색
search_pattern = os.path.join(SUNRGBD_ROOT, '*', 'NYUdata', '**', 'annotation', 'index.json')
json_files_for_conversion = glob.glob(search_pattern, recursive=True)
print(f"\n총 {len(json_files_for_conversion)}개의 JSON 파일을 변환합니다.")
print(f"⚠️ 경고: '{OUTPUT_DIR}'에 저장되는 파일은 클래스 이름이 포함된 비표준 형식입니다! ⚠️")
# 변환 함수 실행
convert_json_to_name_seg(json_files_for_conversion)
print("\n--- 클래스 이름 변환 작업 완료 ---")
구체적인 코드에 대한 설명은 글이 너무 길어지기에 다음에 소개하겠습니다. (이하 본 글에서 나올 모든 코드 동일)
위의 코드는 json파일을 보고 각 클래스 리스트와 폴리곤 좌표를 만드는 코드입니다.
결과를 보니 클래스틑 총 64개로 보입니다. (NYU000*을 보시면 알겠지만 1~9까지의 데이터에서의 클래스이기 때문에 정확하지 않을 수 있습니다.)
클리스 리스트
airduct
airvent
bag
ball
bar
basket
book
bottle
bowl
box
cabinet
camera
ceiling
chair
clock
computer
cone
corkboard
counter
cup
desk
dishwasher
door
doorknob
faucet
fireextinguisher
floor
garbagebin
greenscreen
holepuncher
keyboard
ladder
laptop
light
magnet
manillaenvelope
mantel
microwave
monitor
motioncamera
paper
papertoweldispenser
picture
pipe
pot
projectorscreen
refridgerator
scissor
shelves
sink
speaker
stackedchairs
stand
stoveburner
styrofoamobject
table
tapedispenser
telephone
telephonecord
tracklight
unknown
wall
whiteboard
window
코드를 실행하면 SUNRGBD 폴더내에 ploygens_name_output 폴더가 생겼으며 그안의 txt 파일은 아래처럼 되어있을 겁니다.
bottle 0.967914 0.634660 0.973262 0.585480 0.985740 0.573770 0.983957 0.545667 1.000000 0.545667 1.000000 0.555035 1.000000 0.639344 0.967914 0.634660
cabinet 0.670232 0.857143 0.677362 0.629977 0.816399 0.676815 0.818182 0.763466 0.807487 0.936768 0.816399 1.000000 0.761141 1.000000 0.684492 0.927400 0.670232 0.927400 0.670232 0.857143
cabinet 0.757576 0.292740 0.762923 0.201405 1.000000 0.065574 1.000000 0.309133 1.000000 0.442623 0.877005 0.449649 0.880570 0.292740 0.761141 0.327869 0.757576 0.292740
ceiling 0.659537 0.189696 0.893048 0.002342 1.000000 0.002342 1.000000 0.030445 1.000000 0.060890 0.764706 0.199063 0.659537 0.189696
ceiling 0.119430 0.002342 0.741533 0.002342 0.655971 0.093677 0.119430 0.002342
chair 0.001783 0.843091 0.098039 0.845433 0.098039 0.925059 0.117647 0.948478 0.110517 1.000000 0.001783 1.000000 0.001783 0.843091
chair 0.242424 0.920375 0.245989 0.803279 0.253119 0.744731 0.269162 0.730679 0.269162 0.6791
이제 리스트를 보고 각 클래스의 이름을 id로 바꾸고 마무리 합니다.
2단계. 클래스 이름 → id로 바꾸기
import os
import glob
# --- 1. 사용자 정의 클래스 목록 및 ID 매핑 설정 ---
# 클래스 순서가 ID (0, 1, 2, ...) 순서가 됩니다.
USER_DEFINED_CLASSES = [
"airduct", "airvent", "bag", "ball", "bar", "basket", "book", "bottle", "bowl", "box",
"cabinet", "camera", "ceiling", "chair", "clock", "computer", "cone", "corkboard", "counter",
"cup", "desk", "dishwasher", "door", "doorknob", "faucet", "fireextinguisher", "floor",
"garbagebin", "greenscreen", "holepuncher", "keyboard", "ladder", "laptop", "light",
"magnet", "manillaenvelope", "mantel", "microwave", "monitor", "motioncamera", "paper",
"papertoweldispenser", "picture", "pipe", "pot", "projectorscreen", "refridgerator",
"scissor", "shelves", "sink", "speaker", "stackedchairs", "stand", "stoveburner",
"styrofoamobject", "table", "tapedispenser", "telephone", "telephonecord", "tracklight",
"unknown", "wall", "whiteboard", "window"
]
# 클래스 이름 -> ID 매핑 생성
CLASS_NAME_TO_ID = {name: i for i, name in enumerate(USER_DEFINED_CLASSES)}
# --- 2. 설정 (CONFIG) ---
SUNRGBD_ROOT = 'SUNRGBD'
# 재라벨링 대상 폴더
TARGET_DIR = os.path.join(SUNRGBD_ROOT, 'polygons_name_output')
# 새로운 클래스 ID 순서에 맞춰 classes.txt 파일도 생성
CLASSES_FILE_PATH = os.path.join(SUNRGBD_ROOT, 'final_yolo_classes.txt')
# ----------------------------------------------------
## 3. 클래스 파일 생성
# ----------------------------------------------------
try:
with open(CLASSES_FILE_PATH, 'w') as f:
for name in USER_DEFINED_CLASSES:
f.write(f"{name}\n")
print(f"새 클래스 목록 파일 ({CLASSES_FILE_PATH}) 저장 완료. (총 {len(USER_DEFINED_CLASSES)}개 클래스)")
except Exception as e:
print(f"클래스 파일 저장 중 오류 발생: {e}")
# ----------------------------------------------------
## 4. 재라벨링 함수 실행
# ----------------------------------------------------
def relabel_txt_files(target_dir, class_map):
txt_files = glob.glob(os.path.join(target_dir, '*.txt'))
total_files = len(txt_files)
processed_count = 0
print(f"\n총 {total_files}개의 TXT 파일을 사용자 정의 순서로 재라벨링합니다. 🔄")
for file_index, txt_path in enumerate(txt_files):
print(f"--- ({file_index + 1}/{total_files}) 처리 중: {os.path.basename(txt_path)} ---")
new_lines = []
changed_count = 0
# 파일 읽기
try:
with open(txt_path, 'r') as f:
lines = f.readlines()
except Exception as e:
print(f"파일 읽기 오류: {txt_path} - {e}")
continue
for line in lines:
line = line.strip()
if not line:
continue
parts = line.split()
if not parts:
continue
old_class_name = parts[0] # 현재 파일의 클래스 이름(문자열)
coordinates = parts[1:]
# 사용자 정의 맵에서 새로운 ID 찾기
new_id = class_map.get(old_class_name)
if new_id is not None:
# 새로운 ID로 라인 구성 (YOLO 표준 형식: ID + 좌표)
new_line = f"{new_id} {' '.join(coordinates)}"
new_lines.append(new_line)
changed_count += 1
else:
# 맵에 없는 클래스는 오류를 표시하고 건너뜀
print(f"경고: 사용자 정의 목록에 없는 클래스 이름 '{old_class_name}'이 발견되어 해당 라인을 건너뜁니다.")
# 원본 파일 덮어쓰기
if changed_count > 0:
try:
with open(txt_path, 'w') as f_out:
f_out.write('\n'.join(new_lines) + '\n')
print(f"{os.path.basename(txt_path)} 재라벨링 완료. (클래스 ID {changed_count}개로 변경) ✅")
processed_count += 1
except Exception as e:
print(f"파일 쓰기 오류: {txt_path} - {e}")
else:
print(f"참고: {os.path.basename(txt_path)}에서 변환된 유효한 라인이 없습니다.")
print(f"\n--- 재라벨링 작업 완료. 총 {processed_count}개의 파일이 처리되었습니다. ---")
# --- 메인 실행 ---
relabel_txt_files(TARGET_DIR, CLASS_NAME_TO_ID)
print("\n처리 결과:")
print(f"'{TARGET_DIR}' 내의 모든 TXT 파일이 사용자 정의 순서에 따른 숫자 ID로 덮어쓰기되었습니다.")
print(f"새로운 클래스 ID 순서는 '{CLASSES_FILE_PATH}' 파일에서 확인할 수 있습니다.")
위 코드를 실행하면 ploygens_name_output안의 txt의 class 이름이 class_id로 바뀝니다.
이제 이미지 파일과 txt파일을 통해 최종적으로 제가 원하는 데이터셋을 구성할수 있습니다.
Dataset_NYU
ㄴimages
ㄴNYU0001.jpg
ㄴNYU0002.jpg...
ㄴlabels
ㄴNYU0001.txt (이미지에 해당하는 라벨링 정보 - 폴리곤)
ㄴNYU0002.txt...
3. 결론
여기까지 YOLO-seg 모델을 프로젝트에 선택한 이유와 모델을 재학습하기 위한 데이터셋 구성 과정에 대해 설명했습니다.다음 글에서는 위의 데이터셋을 YOLOv11n-seg에 재학습하는 과정과 결과, 결론에 대해 설명하겠습니다.