바 그래프
햄스터 기초 - 근접 센서 사용하기 -
애완 로봇에서 작성한 코드에 그래픽을 추가하여 바 그래프를 만들어 봅시다.
여기서는 검은색 사각형을 그린 후 근접 센서 값에 따라 검은색 사각형을 지우는 방법으로 해봅시다.
근접 센서 값 40을 기준으로 바퀴가 앞으로 이동하거나 뒤로 이동하는데,
기준 값 40에 해당하는 위치를 빨간색 선으로 표시합니다.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JFrame;
import org.roboid.hamster.Hamster;
import org.roboid.runtime.Runner;
@SuppressWarnings("serial")
public class Controller extends JFrame {
private Image image;
private final Hamster hamster = new Hamster();
private int leftProximity;
private int rightProximity;
@Override
public void paint(Graphics graphics) {
int width = getWidth();
int height = getHeight();
if(image == null) {
image = createImage(width, height);
}
Graphics g = image.getGraphics();
g.clearRect(0, 0, width, height);
// 검은색 사각형을 두 개 그린다.
g.setColor(Color.BLACK);
g.fillRect(38, 50, 40, 160);
g.fillRect(138, 50, 40, 160);
// 센서 값을 표시한다.
g.drawString("Left: " + leftProximity, 38, 225);
g.drawString("Right: " + rightProximity, 138, 225);
// 센서 값만큼 검은색 사각형을 지운다.
g.clearRect(38, 50, 40, leftProximity * 2);
g.clearRect(138, 50, 40, rightProximity * 2);
// 빨간색 선을 그린다.
g.setColor(Color.RED);
g.drawLine(18, 130, 198, 130);
graphics.drawImage(image, 0, 0, this);
}
private static int calcSpeed(int proximity) {
if(proximity > 15) {
return (40 - proximity) * 4; // 거리가 멀면 앞으로, 가까우면 뒤로 이동한다.
} else {
return 0; // 거리가 너무 멀면 정지한다.
}
}
public void run() {
setSize(216, 248);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
while(true) {
leftProximity = hamster.leftProximity(); // 왼쪽 근접 센서 값
rightProximity = hamster.rightProximity(); // 오른쪽 근접 센서 값
hamster.wheels(calcSpeed(leftProximity), calcSpeed(rightProximity));
repaint(); // 화면을 갱신한다.
Runner.wait(20); // 너무 빨리 반복하지 않도록 한다.
}
}
public static void main(String[] args) {
new Controller().run();
}
}
마우스 조이스틱
마우스 이벤트를 사용하여 조이스틱을 만들어 봅시다.
화면 중심에 대한 마우스 커서의 위치에 따라 이동 속도와 방향을 다르게 합시다.
마우스 버튼을 누르고 있는 동안 움직이고, 버튼을 떼면 정지하도록 합시다.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.swing.JFrame;
import org.roboid.hamster.Hamster;
import org.roboid.runtime.Runner;
@SuppressWarnings("serial")
public class Controller extends JFrame {
private Image image;
private final Hamster hamster = new Hamster();
private int mouseX, mouseY;
private boolean mousePressed;
@Override
public void paint(Graphics graphics) {
int width = getWidth();
int height = getHeight();
if(image == null) {
image = createImage(width, height);
}
Graphics g = image.getGraphics();
g.clearRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.fillOval(188, 210, 40, 40); // 화면 중심에 원을 그린다.
g.drawLine(208, 230, mouseX, mouseY); // 화면 중심에서 마우스 커서 위치까지 선을 그린다.
graphics.drawImage(image, 0, 0, this);
}
public void run() {
setSize(416, 438);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent event) {
mousePressed = true;
}
@Override
public void mouseReleased(MouseEvent event) {
mousePressed = false;
}
});
addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseMoved(MouseEvent event) {
mouseX = event.getX();
mouseY = event.getY();
}
@Override
public void mouseDragged(MouseEvent event) {
mouseX = event.getX();
mouseY = event.getY();
}
});
int dx, dy;
while(true) {
// 화면 중심과 마우스 커서 위치의 차이
dx = (200 - mouseX) / 2;
dy = (200 - mouseY) / 2;
// 마우스 버튼을 누르고 있고 차이가 너무 작지 않으면
if(mousePressed && (Math.abs(dx) > 10 || Math.abs(dy) > 10)) {
if(dy > 0) {
// 마우스 커서 위치가 화면 중심보다 위에 있으면 dy가 양수, 즉 앞으로 이동한다.
// 마우스 커서가 화면 중심보다 왼쪽에 있으면 dx가 양수, 즉 왼쪽 앞으로 이동한다.
hamster.wheels(dy - dx, dy + dx);
} else {
// 마우스 커서가 화면 중심보다 아래에 있으면 dy가 음수, 즉 뒤로 이동한다.
// 마우스 커서가 화면 중심보다 왼쪽에 있으면 dx가 양수, 즉 왼쪽 뒤로 이동한다.
hamster.wheels(dy + dx, dy - dx);
}
} else {
hamster.stop(); // 마우스 버튼을 누르지 않거나 차이가 너무 작으면 정지한다.
}
repaint(); // 화면을 갱신한다.
Runner.wait(20); // 너무 빨리 반복하지 않도록 한다.
}
}
public static void main(String[] args) {
new Controller().run();
}
}
햄스터 리모컨
햄스터 로봇의 가속도 센서를 사용하여 화면의 사각형을 움직여 봅시다.
햄스터 로봇의 앞을 위로 들거나 아래로 내리면서 X축 가속도 값을 관찰해 보면
-18000 ~ 18000까지 변함을 알 수 있습니다.
가속도 값을 100으로 나누어 사각형의 위치 값으로 사용해 봅시다.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JFrame;
import org.roboid.hamster.Hamster;
import org.roboid.runtime.Runner;
@SuppressWarnings("serial")
public class Controller extends JFrame {
private Image image;
private final Hamster hamster = new Hamster();
@Override
public void paint(Graphics graphics) {
int width = getWidth();
int height = getHeight();
if(image == null) {
image = createImage(width, height);
}
Graphics g = image.getGraphics();
g.clearRect(0, 0, width, height);
int y = hamster.accelerationX() / 100;
g.setColor(Color.RED);
g.fillRect(158, 180 + y, 100, 100);
graphics.drawImage(image, 0, 0, this);
}
public void run() {
setSize(416, 438);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
while(true) {
repaint(); // 화면을 갱신한다.
Runner.wait(20); // 너무 빨리 반복하지 않도록 한다.
}
}
public static void main(String[] args) {
new Controller().run();
}
}
코드를 실행해 보면 햄스터 로봇의 앞을 위로 들거나 아래로 내렸을 때
사각형이 위아래로 잘 움직이기는 하지만 발발 떨면서 움직이는 것을 볼 수 있습니다.
가속도 센서는 굉장히 민감하게 반응하기 때문에 햄스터 로봇을 가만히 두어도 값이 계속 바뀌는 것을 관찰할 수 있는데,
가속도 센서 값의 변화를 좀 둔감하게 조정할 필요가 있습니다.
햄스터 로봇을 가만히 두었을 때에도 가속도 값이 1500 정도까지 왔다갔다 하는데
100으로 나누면 15가 됩니다. 즉, 사각형이 15 픽셀만큼 움직인다는 뜻입니다.
15 정도의 변화는 무시하도록 합시다.
15로 나눈 다음 반올림하고 다시 15를 곱하면 됩니다.
자바의 Math.round() 메소드는 소수점 아래에서 반올림하여 정수 값으로 반환하는 메소드입니다.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JFrame;
import org.roboid.hamster.Hamster;
import org.roboid.runtime.Runner;
@SuppressWarnings("serial")
public class Controller extends JFrame {
private Image image;
private final Hamster hamster = new Hamster();
@Override
public void paint(Graphics graphics) {
int width = getWidth();
int height = getHeight();
if(image == null) {
image = createImage(width, height);
}
Graphics g = image.getGraphics();
g.clearRect(0, 0, width, height);
int y = Math.round(hamster.accelerationX() / 1500f) * 15;
g.setColor(Color.RED);
g.fillRect(158, 180 + y, 100, 100);
graphics.drawImage(image, 0, 0, this);
}
public void run() {
setSize(416, 438);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
while(true) {
repaint(); // 화면을 갱신한다.
Runner.wait(20); // 너무 빨리 반복하지 않도록 한다.
}
}
public static void main(String[] args) {
new Controller().run();
}
}
가로 방향의 움직임에 대해서도 똑같이 적용해 봅시다.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JFrame;
import org.roboid.hamster.Hamster;
import org.roboid.runtime.Runner;
@SuppressWarnings("serial")
public class Controller extends JFrame {
private Image image;
private final Hamster hamster = new Hamster();
@Override
public void paint(Graphics graphics) {
int width = getWidth();
int height = getHeight();
if(image == null) {
image = createImage(width, height);
}
Graphics g = image.getGraphics();
g.clearRect(0, 0, width, height);
int x = Math.round(hamster.accelerationY() / 1500f) * 15;
int y = Math.round(hamster.accelerationX() / 1500f) * 15;
g.setColor(Color.RED);
g.fillRect(158 - x, 180 + y, 100, 100);
graphics.drawImage(image, 0, 0, this);
}
public void run() {
setSize(416, 438);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
while(true) {
repaint(); // 화면을 갱신한다.
Runner.wait(20); // 너무 빨리 반복하지 않도록 한다.
}
}
public static void main(String[] args) {
new Controller().run();
}
}
코드를 실행해 보면 사각형이 15 픽셀 단위로 움직이기 때문에 움직임이 튀는 것을 볼 수 있습니다.
평균 값을 구하여 움직임을 좀 더 부드럽게 만들어야겠습니다.
가속도 센서 값을 어느 정도 모아서, 예를 들어 100개를 모아서 평균 값을 계산하면 100개가 모여졌을 때만 사각형을 움직일 수 있기 때문에 움직임이 뚝뚝 끊어져 보일 수 있습니다.
따라서 예전 값은 버리고 새로운 값을 넣어서 매번 평균 값을 구하는 방법을 사용합니다.
이를 이동 평균(Moving Average)이라고 합니다.
이동 평균을 계산하기 위한 클래스를 만들어 봅시다.
private static final class AverageCalculator {
private final int[] array; // 정수 값들을 저장하는 곳
private final int size; // 평균을 구할 값들의 최대 개수
private int sum; // 합의 현재 값
private int index; // 배열에서 값을 넣을 위치
private int count; // 저장된 값들의 현재 개수
AverageCalculator(int size) {
array = new int[size];
this.size = size;
}
int calculate(int value) {
if(count < size) {
++ count;
} else { // size 개수만큼 모았으면
index %= size; // 순환하는 인덱스
sum -= array[index]; // 가장 예전 값을 sum에서 뺀다.
}
sum += value; // value를 sum에 더한다.
array[index++] = value; // value를 배열에 추가
return Math.round((float)sum / count); // 평균 계산
}
}
전체 코드를 완성하면 다음과 같습니다.
package examples.hamster;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JFrame;
import org.roboid.hamster.Hamster;
import org.roboid.runtime.Runner;
@SuppressWarnings("serial")
public class Controller extends JFrame {
private Image image;
private final Hamster hamster = new Hamster();
private final AverageCalculator calculatorX = new AverageCalculator(SIZE); // 가로 방향에 대한 이동 평균 계산기
private final AverageCalculator calculatorY = new AverageCalculator(SIZE); // 세로 방향에 대한 이동 평균 계산기
private static final int SIZE = 20; // 값의 최대 개수는 20개
private static final class AverageCalculator {
private final int[] array; // 정수 값들을 저장하는 곳
private final int size; // 평균을 구할 값들의 최대 개수
private int sum; // 합의 현재 값
private int index; // 배열에서 값을 넣을 위치
private int count; // 저장된 값들의 현재 개수
AverageCalculator(int size) {
array = new int[size];
this.size = size;
}
int calculate(int value) {
if(count < size) {
++ count;
} else { // size 개수만큼 모았으면
index %= size; // 순환하는 인덱스
sum -= array[index]; // 가장 예전 값을 sum에서 뺀다.
}
sum += value; // value를 sum에 더한다.
array[index++] = value; // value를 배열에 추가
return Math.round((float)sum / count); // 평균 계산
}
}
@Override
public void paint(Graphics graphics) {
int width = getWidth();
int height = getHeight();
if(image == null) {
image = createImage(width, height);
}
Graphics g = image.getGraphics();
g.clearRect(0, 0, width, height);
int x = Math.round(hamster.accelerationY() / 1500f) * 15; // 가로 방향에 대한 현재 값
int y = Math.round(hamster.accelerationX() / 1500f) * 15; // 세로 방향에 대한 현재 값
x = calculatorX.calculate(x); // 가로 방향에 대한 평균 값
y = calculatorY.calculate(y); // 세로 방향에 대한 평균 값
g.setColor(Color.RED);
g.fillRect(158 - x, 180 + y, 100, 100);
graphics.drawImage(image, 0, 0, this);
}
public void run() {
setSize(416, 438);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
while(true) {
repaint(); // 화면을 갱신한다.
Runner.wait(20); // 너무 빨리 반복하지 않도록 한다.
}
}
public static void main(String[] args) {
new Controller().run();
}
}
햄스터 로봇을 손목에 부착하여 화면의 그래픽을 제어하는 웨어러블 인터페이스 장치로 사용해 봅시다.
목차
수업 자료 내려 받기
- 원본 그림 2017.01.25 버전 (34.4 MB)
고급
- 행위 기반의 로봇 제어
- 경로 탐색
- 자리 바꾸기
Copyright 로봇SW교육원 All rights reserved.
어려운 일이 있으면 광운대학교 로봇학부 박광현 교수(
akaii@kw.ac.kr)에게 연락하세요.