2020년 1월 18일 토요일

Vue, MongoDB, Express 기반 간단한 웹 어플리케이션 개발

이 글은 Vue기반 MongoDB 사용하는 방법을 간략히 공유한다.

개발 방법
Vue는 자바스크립트와 Node.JS를 사용하므로 이를 미리 설치해야 한다. 이후, 개발 순서는 다음과 같다.
Simple MEVN 예제는 To do list를 CRUD(create, read, update, delete)하는 웹 어플리케이션이다. 구조는 크게 두 부분으로 나뉘어져 있고, 다음과 같다.
  • CRUD API + Express + Mongo DB server
  • App Form UI + Vue server
참고로, 이 API 서버와 App 서버의 구조는 다음과 같다. 
API서버 구조
App서버 구조

우선 CRUD API + MongoDB 서버부터 개발해 보자.

CRUD API 서버 개발
다음처럼 폴더와 파일을 만든다. 
mkdir vurcrudappapi
cd vurcrudappapi
touch server.js

다음과 같이 API 기능을 제공할 폴더를 만든다.
mkdir api
mkdir api/controllers
mkdir api/models
mkdir api/routes

다음과 같이 파일을 만든다.
touch api/controllers/taskController.js
touch api/model/taskModel.js
touch api/routes/taskRoutes.js

다음과 같이 package.json 파일을 만든다.
npm init

그리고, 다음 패키지를 설치한다. 
npm i express cors body-parser mongoose
npm i nodemon --save-dev

package.json 안의 scripts 부분을 다음처럼 수정한다. 
"scripts": {
  "start": "nodemon server.js"
},

taskController.js 파일을 다음과 같이 코딩한다. 
const mongoose = require('mongoose');
const task = mongoose.model('task');

exports.list_all_tasks = (req, res) => {
  task.find({}, (err, tasks) => {
    if (err) res.send(err);
    res.json(tasks);
  });
};

exports.create_a_task = (req, res) => {
  const newTask = new task(req.body);
  newTask.save((err, task) => {
    if (err) res.send(err);
    res.json(task);
  });
};

exports.read_a_task = (req, res) => {
  task.findById(req.params.taskId, (err, task) => {
    if (err) res.send(err);
    res.json(task);
  });
};

exports.update_a_task = (req, res) => {
  task.findOneAndUpdate(
    { _id: req.params.taskId },
    req.body,
    { new: true },
    (err, task) => {
      if (err) res.send(err);
      res.json(task);
    }
  );
};

exports.delete_a_task = (req, res) => {
  task.deleteOne({ _id: req.params.taskId }, err => {
    if (err) res.send(err);
    res.json({
      message: 'task successfully deleted',
     _id: req.params.taskId
    });
  });
};

taskModel.js 파일은 다음과 같이 코딩한다. 
const mongoose = require('mongoose');

const { Schema } = mongoose;

const taskSchema = new Schema(
  {
    task1: {
      type: String,
      required: 'task1 cannot be blank'
    },
    task2: {
      type: String,
      required: 'task2  cannot be blank'
    }
  },
  { collection: 'task' }
);

module.exports = mongoose.model('task', taskSchema);

RoutesModel.js 파일을 다음과 같이 수정한다.
const taskBuilder = require('../controllers/taskController');

module.exports = app => {
  app
    .route('/tasks')
    .get(taskBuilder.list_all_tasks)
    .post(taskBuilder.create_a_task);

  app
    .route('/tasks/:taskId')
    .get(taskBuilder.read_a_task)
    .put(taskBuilder.update_a_task)
    .delete(taskBuilder.delete_a_task);
};

다음과 같이 server.js 파일을 코딩한다. 
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');

global.Task = require('./api/models/taskModel');
const routes = require('./api/routes/taskRoutes');

mongoose.Promise = global.Promise;
mongoose.set('useFindAndModify', false);
mongoose.connect(
  'mongodb://localhost/Vuecrudapp',
  { useNewUrlParser: true }
);

const port = process.env.PORT || 3000;
const app = express();

app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

routes(app);
app.listen(port);

app.use((req, res) => {
  res.status(404).send({ url: `${req.originalUrl} not found` });
});

console.log(`Server started on port ${port}`);

위의 mongoose.connect 함수는 Vuecrudapp 데이터베이스를 연결하는 역할을 한다. mongo Compass를 사용해 Vuecrudapp 데이터베이스가 생성된 후, Express app을 통해 bodyParsercors 미들웨어를 사용함을 설정한다. 

이제 taskRoutes.js 에 정의된 코드를 통해 라우팅을 하고, 3000 번 포트를 통해 connection을 기다린다. 이제 Mongo DB를 실행한 후, 다음과 같이 npm을 통해 실행한다. 
npm run start

서버 실행 
API 및 MongoDB 서버

생성된 DB는 Mongo compass 프로그램을 사용해 내용을 확인할 수 있다. 아울러, Postman을 이용해 API를 확인할 수 있다.

APP 서버 개발
다음과 같이 Vue CLI 환경을 설치한다. 
npm install -g @vue/cli

그리고, vuecrudapp 프로젝트를 다음처럼 생성한다. 
vue create vuecrudapp

다음과 같이 실행하면, http://localhost:8080/ 에서 기본 Vue 인터페이스를 확인할 수 있을 것이다.
cd vuecrudapp
npm run serve

서버를 종료하고, 다음과 같이 폴더와 파일을 만든다. 
cd src
touch components/TaskTest.vue
touch components/Taskform.vue
mkdir views
touch views/Edit.vue
touch views/New.vue
touch views/Show.vue
touch views/Test.vue
touch views/Tasks.vue
mkdir helpers
touch helpers/Helpers.js
touch Router.js

패키지를 설치한다. 
npm i axios semantic-ui-css vue-flash-message

router.js 를 코딩한다.
import Vue from 'vue';
import Router from 'vue-router';
import Tasks from './views/Tasks.vue';
import New from './views/New.vue';
import Show from './views/Show.vue';
import Edit from './views/Edit.vue';

Vue.use(Router);

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  linkActiveClass: 'active',
  routes: [
    {
      path: '/',
      redirect: '/tasks'
    },
    {
      path: '/tasks',
      name: 'tasks',
      component: Tasks
    },
    {
      path: '/tasks/new',
      name: 'new-task',
      component: New
    },
    {
      path: '/tasks/:id',
      name: 'show',
      component: Show
    },
    {
      path: '/tasks/:id/edit',
      name: 'edit',
      component: Edit
    }
   
  ]
});

등록된 라우트의 의미는 다음과 같다. 
/tasks - Mongo DB 데이터베이스의 모든 task를 표시한다.
/tasks/new - 새로운 task를 생성한다.
/tasks/:id - id에 해당하는 task를 표시한다.
/tasks/:id/edit - id에 해당하는 task를 수정한다. 
다음과 같이 main.js 를 수정한다. 
import Vue from 'vue'
import App from './App.vue'

import 'semantic-ui-css/semantic.css';
import router from './router'

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

app.vue를 수정한다. 
<template>
  <div id="app">
    <div class="ui inverted segment navbar">
      <div class="ui center aligned container">
        <div class="ui large secondary inverted pointing menu compact">
          <router-link to="/tasks" exact class="item">
           <i class="tasks icon"></i> Tasks
          </router-link>
          <router-link to="/tasks/new" class="item">
            <i class="plus circle icon"></i> New
          </router-link>
      
        </div>
      </div>
    </div>

    <div class="ui text container">
      <div class="ui one column grid">
        <div class="column">
          <router-view />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'app'
};
</script>

<style>
#app > div.navbar {
  margin-bottom: 1.5em;
}
.myFlash {
  width: 250px;
  margin: 10px;
  position: absolute;
  top: 50;
  right: 0;
}
input {
  width: 300px;
}
div.label {
  width: 120px;
}
div.input {
  margin-bottom: 10px;
}
button.ui.button {
  margin-top: 15px;
  display: block;
}
</style>

TaskForm.vue 를 수정한다. 
<template>
 <form action="#" @submit.prevent="onSubmit">
    <p v-if="errorsPresent" class="error">Please fill out both fields!</p>


    <div class="ui labeled input fluid">
      <div class="ui label">
        <i class="calendar plus icon"></i>task
      </div>
      <input type="text" placeholder="Enter task..." v-model="task.task1" />
    </div>

    <div class="ui labeled input fluid">
      <div class="ui label">
   <i class="info circle icon"></i> Details
      </div>
      <input type="text" placeholder="Enter Details" v-model="task.task2" />
    </div>

    

    <button class="positive ui button">Submit</button>
  </form>
</template>

<script>
export default {
  name: 'task-form',
  props: {
    task: {
      type: Object,
      required: false,
      default: () => {
        return {
          task1: '',
          task2: ''
        };
      }
    }
  },
  data() {
    return {
      errorsPresent: false
    };
  },
  methods: {
    onSubmit: function() {
      if (this.task.task1 === '' || this.task.task2 === '') {
        this.errorsPresent = true;
      } else {
        this.$emit('createOrUpdate', this.task);
      }
    }
  }
};
</script>

<style scoped>
.error {
  color: red;
}
</style>

New.vue를 수정한다. 
<template>
  <div>
    <h1>New task</h1>
    <task-form @createOrUpdate="createOrUpdate"></task-form>
  </div>
</template>

<script>
import taskForm from '../components/TaskForm.vue';
import { api } from '../helpers/helpers';
export default {
  name: 'new-task',
  components: {
    'task-form': taskForm
  },
  methods: {
    createOrUpdate: async function(task) {
      const res = await api.createtask(task);
      this.flash('task created', 'success');
      this.$router.push(`/tasks/${res._id}`);
    }
  }
};
</script>

Edit.vue 를 수정한다. 
<template>
  <div>
    <h1>Edit task</h1>
    <task-form @createOrUpdate="createOrUpdate" :task=this.task></task-form>
  </div>
</template>

<script>
import taskForm from '../components/TaskForm.vue';
import { api } from '../helpers/helpers';

export default {
  name: 'edit',
  components: {
    'task-form': taskForm
  },
  data: function() {
    return {
      task: {}
    };
  },
  methods: {
    createOrUpdate: async function(task) {
      await api.updatetask(task);
      this.flash('task updated sucessfully!', 'success');
      this.$router.push(`/tasks/${task._id}`);
    }
  },
  async mounted() {
    this.task = await api.gettask(this.$route.params.id);
  }
};
</script>

Show.vue를 수정한다. 
<template>
  <div>
    <h1>Show task</h1>

   
    <div class="ui labeled input fluid">
      <div class="ui label">
      <i class="tasks icon"></i>  Task
      </div>
      <input type="text" readonly  :value="task.task1"/>
    </div>

     <div class="ui labeled input fluid">
      <div class="ui label">
        <i class="info circle icon"></i> Details
      </div>
      <input type="text" readonly  :value="task.task2"/>
    </div>
    <div class="actions">
      <router-link :to="{ name: 'edit', params: { id: this.$route.params.id }}">
        Edit task
      </router-link>
    </div>
  </div>
</template>

<script>
import { api } from '../helpers/helpers';
export default {
  name: 'show',
  data() {
    return {
      task: ''
    };
  },
  async mounted() {
    this.task = await api.gettask(this.$route.params.id);
  }
};
</script>

<style scoped>
.actions a {
  display: block;
  text-decoration: underline;
  margin: 20px 10px;
}
</style>

Tasks.vue를 수정한다.
<template>
  <div>
    <h1>tasks</h1>
    <table id="tasks" class="ui celled compact table">
      <thead>
        <tr>
         <th>  <i class="calendar plus icon"></i>Task</th>
          <th> <i class="info circle icon"></i>Detail</th>
                    <th> <i class="lock open icon"></i></th>
                   <th> <i class="edit icon"></i></th>
                    <th> <i class="trash icon"></i></th>



          <th colspan="3"></th>
        </tr>
      </thead>
      <tr v-for="(task, i) in tasks" :key="i">
        <td>{{ task.task1 }}</td>
        <td>{{ task.task2 }}</td>
        <td width="75" class="center aligned">
          <router-link :to="{ name: 'show', params: { id: task._id }}">Show</router-link>
        </td>
        <td width="75" class="center aligned">
          <router-link :to="{ name: 'edit', params: { id: task._id }}">Edit</router-link>
        </td>
        <td width="75" class="center aligned" @click.prevent="onDestroy(task._id)">
          <a :href="`/tasks/${task._id}`">Delete</a>
        </td>
      </tr>
    </table>
  </div>
</template>

<script>
import { api } from '../helpers/helpers';
export default {
  name: 'tasks',
  data() {
    return {
      tasks: []
    };
  },
  methods: {
    async onDestroy(id) {
      const sure = window.confirm('Are you sure?');
      if (!sure) return;
      await api.deletetask(id);
      this.flash('task deleted sucessfully!', 'success');
      const newtasks = this.tasks.filter(task => task._id !== id);
      this.tasks = newtasks;
    }
  },
  async mounted() {
    this.tasks = await api.gettasks();
  }
};
</script>

API와 통신하기 위한 helpers.js 를 수정한다.
import axios from 'axios';
import Vue from 'vue';
import VueFlashMessage from 'vue-flash-message';
import 'vue-flash-message/dist/vue-flash-message.min.css';

Vue.use(VueFlashMessage, {
  messageOptions: {
    timeout: 3000,
    pauseOnInteract: true
  }
});

const vm = new Vue();
const baseURL = 'http://localhost:3000/tasks/';

const handleError = fn => (...params) =>
  fn(...params).catch(error => {
    vm.flash(`${error.response.status}: ${error.response.statusText}`, 'error');
  });

export const api = {
  gettask: handleError(async id => {
    const res = await axios.get(baseURL + id);
    return res.data;
  }),
  gettasks: handleError(async () => {
    const res = await axios.get(baseURL);
    return res.data;
  }),
  deletetask: handleError(async id => {
    const res = await axios.delete(baseURL + id);
    return res.data;
  }),
  createtask: handleError(async payload => {
    const res = await axios.post(baseURL, payload);
    return res.data;
  }),
  updatetask: handleError(async payload => {
    const res = await axios.put(baseURL + payload._id, payload);
    return res.data;
  })
};

이제 APP 서버를 다시 실행하고, 크롬 브라우저에 접속하면 다음과 같은 화면을 볼 수 있다. 
앱 서버 실행
실행 화면

MongoDB compass 를 통해 아래와 같이 생성된 database 내용을 확인할 수 있다.
생성된 DB 내용(MongoDB compass)

레퍼런스

2020년 1월 15일 수요일

OpenMVG 기반 SFM 테스트 결과 및 장단점 분석

2차원 사진에서 3차원 모델 생성을 지원하는 오픈소스인 OpenMVG는 다른 SFM(structure from motion) 보다 문서화가 잘 되어 있지만, 실제 소스를 다운로드 받아 빌드하고 사용하는 과정이 그리 순탄하지는 않다. 이 과정은 다음 링크를 참고하길 바란다.
이 글은 소스를 빌드하고 MVG 환경을 만든 후 테스트를 수행한 결과를 나눔한다. 앞의 글에서는 드론, 카메라, 스마트 폰을 이용해 사진을 촬영하고 MVG로 테스트해 보았다. 이 글은 그 중에 누구나 가지고 있는 스마트폰으로 대상물을 촬영한 이미지로 3차원 포인트 클라우드 생성 결과를 분석한 것이다.

이미지 촬영
다음과 같이 스마트폰(iPhone 8)으로 건물을 촬영하였다. 
한국건설기술연구원 일산 본관1동 사진

실외 SFM 처리 결과
14장 사진을 OpenMVG의 MvgMvs_Pipeline 파이썬 프로그램을 이용해 SFM 처리를 해 보았다. 처리 전에 카메라 특성값을 찾아 파이썬 소스코드 해당 옵션 및 수치를 수정한후 실행하였다. 다음은 그 결과이다.

결과를 보면 포인트 클라우드 품질이 그리 나쁜편이 아니라는 것을 알 수 있다. 결과물에서 문제가 있는 부분은 다음과 같다.
  • 사진 간 이미지 중첩이 낮은 경우 포인트 누락 발생
  • 반사 재질에서 포인트 누락 및 편차 발생
  • 촬영 각도에 따라 색상 재질이 바뀌는 대상에서 포인트 누락 및 편차 발생
메쉬 처리 결과
다음은 Dense mesh 결과이다.

일반적인 사진 측량 소프트웨어 결과물과 유사한 결과를 얻을 수 있다. 다만, 사람, 나무 같은 부분에서 튀는 포인트, 노이즈 포인트 등으로 인해 메쉬가 왜곡된 곳을 발견할 수 있다.

참고로, 메쉬 처리는 Trianglation 기법 중 하나인 Delaunay 삼각화 기법을 사용한다. 이런 이유로 매우 많은 메모리를 사용하는 데, GPU를 사용할 경우 속도는 개선되지만, 고가의 GPU가 필요하고, CPU를 사용할 경우 저가 컴퓨터에서 실행 가능하나 처리 속도가 매우 느리다. 이 데이터의 경우 한 시간 이상 메쉬처리가 진행되었다. 불필요할 경우, SFM 처리 파이프라인에서 제외하는 것이 낫다.

실내 SFM 처리 결과
실내 SFM 처리를 위해, 사무실 공간을 아이폰으로 사진촬영한 후 작업해 보았다. 결과는 다음과 같다.
오피스 SFM 결과

SFM은 사진 오버랩 최소 30~60%이어야 제대로 매칭되는 특징점을 계산할 수 있다. 이런 이유로 오버랩이 별로 없는 상부, 하부, 양끝 부분은 포인트 클라우드가 없으며, 오버랩이 많이 된 중간 부분은 점군이 많이 생성된 것을 확인할 수 있다.

실내 복도 SFM 처리 결과
실내 복도 부분은 촬영 시 대상물에 대한 카메라 각도가 제약되므로 누락이 상대적으로 많이 발생할 수 있다. 이를 확인하기 위해 다음과 같이 사진 촬영하여 처리해 보았다.

결과는 다음과 같다. 

오버랩이 안된 부분은 데이터 누락이 발생한다. 다양한 각도 촬영이 어려워 모서리 직각 부분에 오차가 확인된다.

치수 측정
SFM으로 생성된 점군은 스케일이 없다. MESHLAB을 이용해 다음과 같이 측정해 보았다.

그림과 같이 스케일이 없으므로, 각 사물별 상대적 크기만 비교할 수 있을 뿐이다. 스케일을 적용하려면 control point를 적용해야 한다. 이 부분을 적용하려면 다음 링크를 참고하길 바란다.

마무리
촬영된 사진을 SFM으로 처리한 포인트 클라우드 결과를 비교 분석해 보았다. SFM은 사진측량기술 중 하나이다. 처리 결과 다음과 같은 장점이 있다는 것을 확인할 수 있다.
  • 저가 카메라로 대상물 3차원 모델 생성 가능
  • 고가 LiDAR에 비해 편리함
  • 상대적 크기 비교 가능
  • Control point 적용 시 치수 획득 가능
  • 드론 등 공중 촬영을 통한 대상물 이미지 획득이 용이함
단점은 다음과 같다.
  • 오버랩 어려운 대상물 촬영 시 Missing data 발생
  • 재질이 각도에 따라 달라지는 반사 대상물(창호 등) Missing data, 왜곡 및 Noise 발생
  • SFM 처리 시간 걸림. 입력 이미지가 많을수록 많은 메모리 필요하고, 최소 몇십분에서 하루 이상 시간 필요
  • 주의 깊은 대상물 촬영 필요(오버랩 60% 권장)
  • LiDAR에 비해 낮은 정확도. Control point 미적용시 수십cm 이상 오차. 적용 시 수 cm 오차 가능. 
  • 높은 정확도 요구사항의 경우 세심한 Control point 설정 계획, 촬영 각도 및 간격 등 필요.
  • 카메라 기종 별 Focal length 등 사전 계산 및 설정 필요(Pix4D와 같은 상용 프로그램은 불필요)
이런 부분을 고려해 SFM을 적절히 활용하는 것이 좋다.

2020년 1월 9일 목요일

스테레오 이미지/IR/IMU 내장된 MYNT EYE 센서 사용기

이 글은 MYNT EYE 센서 사용 후기이다. 현재 3차원 역설계 비전 연구를 진행 중이라, 가성비 높은 센서가 필요한 부분이 있다. 지금까지 다음과 같은 종류의 장치를 구매해 환경을 설치하고 예제를 실행해 보았다. 
  1. 고정식 고가 LiDAR (Trimble, FARO. $30,000~120,000): 고정밀 엔지니어링용
  2. 실시간 LiDAR (Velodyne. $4,000~20,000): 중저가 무인 이동체 객체 충돌 회피용 3차원 맵 생성
  3. 스테레오 이미지 기반 센서 (ZED. $400): 저가용 실내외 3차원 맵 생성
  4. 적외선 기반 센서 (Kinect, Realsense, $200): 저가용 실내 3차원 맵 생성
  5. 저가 2D LiDAR (RpLiDAR, $99): 저가용 실내 2차원 맵 생성
  6. 복합 3D 센서 (MYNT EYE, $250): 스테레오 이미지+IR+IMU 활용 3차원 맵 생성 
오늘 소개할 MYNT EYE 센서는 TZBOT이란 회사에서 개발한 저가 3차원 DEPTH MAP 스캐너로 IMU, IR, 스테레오 카메라가 포함되어 있다. 
MYNT EYE 사용 예시

사용 방법
AMAZON 등에서 해당 센서를 구입한다. 직구하면 가격은 200~300달러 정도 된다.

빌드하기 전에 UBUNTU, NVIDIA DRIVER, CUDA 등 기본적인 개발환경을 설치해야 놓아야 한다. 환경 준비가 되어 있다면, 다음 같이 소스코드를 다운로드 받는다.
git clone https://github.com/slightech/MYNT-EYE-S-SDK

생성된 폴더 안에서 다음과 같은 명령을 입력해, 코드를 빌드한다.
make
make init
make sample

참고로, NVIDIA JETSON TK 보드에서는 호환성 문제로 빌드되지 않았다. 이 센서는 INTEL 계열 API를 사용한다.

센서를 컴퓨터 USB에 연결하고, 빌드된 예제를 실행해 본다. 그리고, 센서 번호인 0을 입력한다.
./samples/_output/bin/camera_with_junior_device_api

문제없다면, 센서 데이터를 출력할 것이다.

다음은 get_depth_and_points 예제를 실행해 본 결과이다. 가까운 0.8미터쯤에서 포인트가 인식되며, 거리가 멀어질수록 오차가 커지는 것을 알 수 있다. 2미터 정도까지는 어느 정도 정확하게 인식된다. 10미터까지 인식된다.


이 센서는 IMU가 포함되어 있다. 다음은 이와 관련된 예제이다.

개인적 의견으로 다양한 DEPTH MAP 센서를 사용해 스캔을 해보니 가격은 저렴하나 성능은 서로 비슷하다는 것을 느꼈다. 스캔 센서는 가격에 비례해 정확도, 정밀도, 스캔 거리가 높아진다. IR 적외선 센서와 스테레오 이미지 센서는 각각 실외, 실내에서 사용할 때 강하고, LiDAR 센서는 정확도는 높지만 RGB가 없다. 이런 이유로 스캔 센서는 응용 목적에 따라 적합한 것을 활용해야 한다.

추신 - 이 글을 올리고 있을 때 벨로다인에서 10만원대 LiDAR 센서가 CES에서 공개되었다는 뉴스를 SNS를 통해 들었다. 올해는 3D point cloud processing 기술이 보편화되는 시점이 될 것 같다.

우분투 18.04.3 설치 후 NVIDIA 드라이버 설정 문제 해결

우분투 18.04.3 에서 패키지 업데이트 후 벌어진 사건과 테크 트리를 기록한다.

최근 연구를 위해 몇몇 3D 센서를 구입하고, SDK를 빌드하기 위해 몇몇 패키지를 설치하였다. GITHUB에서 소스코드를 다운로드 받고 빌드하는 데, ANACONDA로 인한 디펜던시 에러 발생... 결국 아나콘다를 삭제하고 모든 환경을 새로 설치하기로 결정하고, 우분투 최신 버전을 설치하였다.

우분투 설치는 순조롭게 진행... 사건의 시작은 그 빌어먹을 우분투 업그레이드와 대책없이 만들어 놓은 NVIDIA드라이버 설치 후 였다. 우분투 부팅 시 컴퓨터가 벽돌되는 현상 발생. 아무리 공짜라고 이리 대충 개발해 놓다니.

설정을 이리 저리 변경한 후 다시 우분투 멀티부팅.. 헉. 갑자기 윈도우 부팅이 날라감. 하루 종일 무한 작업.. -.-;;
ㅆㅂ 또 밤새야 하나. 웬수같은 우분투 NVIDIA 삽질

겨우 NVIDIA 문제까지 해결하고 그 과정을 기록한다.

기본사항 체크 사항
부팅할 때 F2를 눌러, 다음 사항을 체크한다.
  • Fast Boot, Secure Booting 모두 Off 상태여야 한다. on상태이면 NVIDIA 드라이버 서명 불일치로 로딩되지 않는 문제가 발생할 수 있다.
  • UEFI 인지 아닌 지 체크. UEFI 라면 윈도우 설치시 UEFI로 되어 있어야 한다.
  • Booting 는 우분투, 윈도우, USB 순으로 되어 있어야 한다.
  • 3D 그래픽스 모드 ON이어야 한다.
우분투 GRUB 체크 사항
  • 우분투 파티션 설정 시 윈도우, 우분투 각각 Boot 정보 제대로 설정되어 있는 지 확인한다.
  • NVIDIA 드라이버 설치 후 스크린 먹통인 경우 GRUB 부팅 시 e 키 입력 후 편집상태에서 ...quiet splash 단어 뒤에 nomodeset 단어를 추가 설정한다.
NVIDIA / CUDA / KERAS / ROS 설치 테크트리
NVIDIA 설치는 다음 순서로 진행한다.
  • 컴퓨터 전원 켬
  • 3D 그래픽스 모드 ON. UEFI ON. SECURE BOOTING OFF. 저장 재부팅
  • GRUB 멀티부팅에서 우분투 선택. 부팅안되면 GRUB 파일 편집. 파일 내 quiet splash 뒤에 nomodeset 추가함 (nomodeset은 그래픽 드라이버 설정을 못하게 하는 옵션이므로, 이후 NVIDIA드라이버 제대로 설치되면, 다시 삭제해야 할 수도 있음에 주의)
  • 우분투 부팅 후 터미널 모드 진입. 아래 명령 실행(만약, nvidia 드라이버 제대로 설치 안되면, nvidia driver download 페이지에서 본인 GPU의 linux용 드라이버 선택해 다운받아 수동 설치해야 함)
  • sudo add-apt-repository ppa:graphics-drivers/ppa
  • sudo apt update
  • ubuntu-drivers devices
  • sudo ubuntu-drivers autoinstall
  • sudo gedit /etc/default/grub
  • GRUB 편집하여 quiet splash 뒤에 nomodeset 추가
  • sudo update-grub
  • 우분투 재부팅
  • nvidia-smi 명령 인식되면 성공
  • Install Tensorflow, Tensorflow-gpu, Keras 
  • Install ROS
이전 NVIDIA 설치 때 테크트리와 좀 달라졌다.
GPU test screen

아마, NVIDIA에서 그 사이 드라이버 수정이 있었던 듯하다(ㅅㅂ.. 욕이 절로 나옴). 이제 잠이나 자야 겠다.

Reference