'2014/03 글 목록 :: Horizontal Grays S2

라즈베리파이를 모니터 없이 x11vnc를 이용하여 원격접속으로 사용하기


요거 하려고 뒤적이다보니 모니터 없이 사용하는 것을 Headless Mode 라고들 하더군 ㅋ 그래서 제목을 저따위로 달아봄


라즈베리파이에 모니터 + 키보드 + 마우스 따로 할당에서 쓸 공간도 없고, 또 추후 내가 하보려고 하는것 역시 라즈베리파이가 본체만으로 동작해야 하므로


x11vnc 설정이 필요했다.


그 설정은 다음과 같음


sudo apt-get install xvfb x11vnc daemon                    //xvfb, x11vnc, daemon 설치

sudo x11vnc -storepasswd /etc/x11vnc.pass            //x11vnc의 비밀번호를 /etc/x11vnc.pass에 저장



lightdm.conf 파일은 아래와 같이 수정 (내려가다보면 SeatDefaults 가 있슴)

sudo nano /etc/lightdm/lightdm.conf                            //lightdm.conf 편집


[SeatDefaults]

xserver-command=/etc/X11/xinit/xserverrc


/etc/X11/xinit/xserverrc 편집

sudo nano /etc/X11/xinit/xserverrc


#!/bin/sh

#exec /usr/bin/X -nolisten tcp "$@"

exec Xvfb :0 -screen 0 1280x768x16


자동시작을 위한 스크립트 생성 및 등록


sudo nano /etc/init.d/vnc_x11


아래내용 복사해서 붙여넣어 자동시작을 위한 스크립트 작성 완료

 #!/bin/sh

#

# /etc/init.d/vnc

#

### BEGIN INIT INFO

# Provides:          x11vnc

# Required-Start:    lightdm

# Should-Start:

# Required-Stop:

# Should-Stop:

# Default-Start:     2 3 4 5

# Default-Stop:      0 1 6

# Short-Description: X11 VNC Server

# Description:       Start or stop vnc server

### END INIT INFO


#INIT SCRIPT VARIABLES

SERVICE=$(basename $0)

PIDFILE="/var/run/${SERVICE}.pid"

BIN="/usr/bin/x11vnc "

#AUTH=`ps wwaux | grep '/X.*-auth' | grep -v grep | sed -e 's/^.*-auth *//' -e 's/ .*$//' | head -n 1`

AUTH="/var/run/lightdm/root/:0"

OPT="-xkb -auth ${AUTH} -rfbauth /etc/x11vnc.pass -forever -rfbport 5900 -o /var/log/x11vnc.log -loop"

CMD=${BIN}${OPT}


. /lib/lsb/init-functions


case "$1" in

    start)

echo -n "Starting ${SERVICE}..."

#/sbin/startproc ${CMD}

daemon -X "${CMD}"

sleep 2s

;;

stop)

echo -n "Stopping ${SERVICE}..."

PID=`ps -ef | grep x11vnc | grep -v grep | awk '{print $2}'`

kill -9 ${PID}

sleep 2s

;;

restart|force-reload)

echo -n "Stopping ${SERVICE}..."

PID=`ps -ef | grep x11vnc | grep -v grep | awk '{print $2}'`

kill -9 ${PID}

sleep 2s

echo -n "Starting ${SERVICE}..."

daemon -X "${CMD}"

sleep 2s

;;

    *)

    echo -e "Usage: ${SERVICE} {start | stop}"

    exit 1

    ;;

esac



vnc_x11의 권한 변경

sudo chmod 755 vnc_x11


부팅할 때 자동으로 시작하도록 등록

sudo update-rc.d -f vnc_x11 defaults 92


reboot


하면 아래 그림과 같이 잘 된다. 다만 모니터를 연결해보면 모니터에 연결된 화면은 아무것도 안나옴 커서만 딸랑 하나 나옴





(참고 : http://www.mls-software.com/rasppi.html)



리눅스에서 윈도우와 파일을 공유하기 위해서는 Samba가 필요하다.


1. 삼바는 다음과 같이 설치한다.


sudo apt-get install samba smbfs


2. 유저 추가

smbpasswd -a 계정


계정은 실제로 리눅스상에 존재하는 USER  만 사용가능하다고 함


3. 공유

난 외장하드에 데이터를 집어넣고 있으므로 

/media/디스크이름 을 통째로 공유하도록 한다.

아래와 같이 노틸러스 (파일브라우저)를 실행시켜서 파일시스템에서 /media/ 에 마운트 된 두개의 하드디스크가 보인다.



공유를 원하는 하드디스크를 클릭 / 우측마우스 버튼을 눌러 메뉴를 보면 공유옵션이 있다.

공유옵션을 눌러 들어가면




원하는대로 옵션 클릭하고 공유만들기 누르면 자동으로 접근권한 더할것이냐 묻고 OK 하면 끝!!



4. 그래서 속도는 만족스럽게 나오는가?



테스트를 위해 홈서버에서 영화파일 하나를 복사해봤다. 위 그림처럼 무려 102MB/Sec 


 매우 만족 :)




1. XBMC 설치


우분투에서 XBMC설치가 어려울 것은 없다.


우분투 소프트웨어센터에서 XBMC로 검색하면 아래 그림과 같이 검색되고 설치하면 된다.


(난 이미 설치 되어 있으므로 설치버튼이 아니고 제거버튼으로 뜬다.




2. 한글 설정

 1) 우선 폰트를 바꿔야 한다. 폰트 먼저 안바꾸면 글씨가 깨져서 못알아보게된다.

 시스템/설정/모양새/스킨 에서 변경


 2) 시스템/설정/모양새/국제에서 언어를 Korean으로 변경




그 외에 자세한 사용방법은  참고 : http://xbmc.tistory.com/5


그리고 Apple Store 에서 XBMC Remote 다운 받은 후에 이를 이용해서 리모컨으로 사용가능

* 여기서 자동으로 XBMC Server를 찾지 못해서 수동으로 설정했다.

 



서버는 아니었지만 기존 Etrayz + Xtreamer 구성은 NAS + Divx Player로 별 무리 없이 잘 사용해왔다..


하지만 세월이 문제였을까? 제품의 안정성이 문제였을까?


Etrayz는 어느날 갑자기 모든 데이터를 초기화시켜버리고 복구 불가 상태가되어버리고

Xtreamer는 못돌리는 동영상 속출, 종종 다운되어 버리는 문제가 발생하곤했다.


그래서 


1. 현재 사용하는 데스크탑에 7개의 하드를 모두 설치해놓고 

   PC ->  모니터 + TV + 프로젝터 연결

   PC -> USB-DAC 광출력 -> 리시버 연결


하여 영화도 보고 토렌트도 쓰고 하였지만.. 전력소모가 크기에 쓸때만 켜고 안쓸 때는 바로 꺼야 했고

중국산 저렴한 USB-DAC는 광출력이 안나오고 

뭐 그런 문제를 안고 대충 쓰다가

최근에 저렴하게 Zotac ZBOX ID18 이라는 베어본 PC가 나와서 홈서버 구성을 목적으로 구매했다.


ZOTAC ZBOX ID18 사양

CPU : 1007U (1.5G)

Chipset : NM70

RAM : 4G

SSD : 64G

1Gbps LAN

HDMI

DVI

Card Reader

USB3.0


대충 이정도 CPU가 매우 저전력으로 켜두고 있어도 기존 i3 데스크탑에 비하면 전력소모량이 훨씬 적을 것으로 보인다.



 위 사진의 제품이다.



1. 우선 OS 선정으로 고민했다.

windows7 이 편할것 같긴 했다만 오래 켜두어도 안정적이고 USB로 외장하드를 연결해두고 다른 PC에서 접속했을 때도 빠른 파일 이동속도를 보장하고

여러 서버로서의 서비스를 사용하기에는 리눅스 계열이 좋을것 같았다. 게다가 최근에 Ubuntu 12.04 를 이용해서 이것저것 하기도 했고.

그래서 일단 Ubuntu 12.04 를 설치하기로 한다.


2. Ubuntu 12.04 설치

  이건 뭐 별로 어려울것도 없고.. SSD에 설치하니 빠르고 쾌적하게 잘 돌아간다.


다만 몇가지 문제점 발견


 1) 전원을 켤 때 가끔 콘솔모드로 부팅이 된다. 여러 문건을 검색해보았지만 딱히 해결책은 없어보인다.

   http://askubuntu.com/questions/228809/low-graphics-error 에보면 SSD와 우분투의 lightdm에서 발생하는 문제점 같다는 소리가 있다.


 2) 이것 저것 설치 후 전원이 안꺼지는 문제 발생, 아래의 조치로 해결

sudo nano /etc/default/grub    //grub 파일 편집


GRUB_CMDLINE_LINUX_DEFAULT를 아래와 같이 편집

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash acpi=force apm=power_off"


sudo update-grub




3. x11vnc 설치

 이전 포스팅 대로 설치하고 별 문제 없슴

 * 참고로 x11vnc를 이용 데스크탑에서 접속했을 때 한영전환은 ctrl + shift 키임




자 이제 기본은 설치가 되었고 아래와 같이 세팅하고 사용할 예정


1. XBMC 설치 HTPC로 활용

2. Samba 설치 파일 공유

3. Torrent Client

4. Air Play Server 설치

5. MPD 설치


지금 생각나는건 여기까지




현재 영상처리 PC는 앞으로는 키보드, 모니터, 마우스 없이 오직 전원(배터리)로 동작해야 한다. 블루투스와 무선인터넷은 동글을 이용해서 사용하기로 하고.


기본적으로는 전원이 켜지면 작성한 프로그램이 동작하고 그 결과값을 블루투스를 이용하여 받아볼 수 있고, 현재 찍고 있는 영상을 와이파이로 받을 수 있도록 되어있으나..


어떤 문제점이 생기면 다시 모니터와 키보드 등을 연결해서 해결해야 하는부분이 불편할 수 밖에 없겠다.


그래서 우분투 원격접속하기로 함


1. 우선 가장 많이 사용하는 XRDP의 경우 Server와 Desktop을 공유하는 방식이 아닌 또 다른 Desktop을 Client에 내주는 방식으로 내가 쓰려는 것과 맞지 않음


2. X11vnc가 사용해보니 속도도 빠르고 기본적으로 Desktop을 공유하는 방식으로 내가 사용하기에 가장 적합한 방식 


그래서 X11vnc로 환경을 설정하기로 함!  (참고 http://renderaid.blogspot.kr/2013/01/x11vnc.html)


x11vnc 설치

sudo apt-get install x11vnc


x11vnc Auto Start를 위한 설정 (재부팅 후 별도의 명령없이 자동으로 x11vnc가 실행되도록


sudo nano /etc/init/x11vnc.conf

위 명령으로 x11vnc.conf 파일 생성 후

아래의 내용을 쓰고 저장

#x11vnc.conf

start on login-session-start

script

x11vnc -xkb -noxrecord -noxfixes -noxdamage -display :0 -auth /var/run/lightdm/root/:0 -forever -bg -o /var/log/x11vnc.log

end script


하고 Rebooting 


원격에서 접속할 Client PC는 윈도우 7이 설치된 노트북 vnc client로는 ultra vnc viewer를 사용해서 접속




위와 같이 윈도우7 위에서 잘 동작하고 있슴 






결론적으로 나는 이게 안되었슴 다른 사람은 잘 되나봄 T.T

여튼 아래 포스팅을 따라하다 나와 같이 실패 할 수도 있으니 조심하길.. 


지금 책상에 

영상처리 클라이언트 데스크탑 (Ubuntu 12.04)

영상처리 서버 랩탑 (Windows 7)

영상처리 서버 데스크탑 (Ubuntu 12.04)

이렇게 기본 3대가 깔려 있다. 때에 따라서 XP가 깔린 다른 노트북을 한 대 더 꺼내서 써야할 때도 있고.


다행히도 골프 관련 개발을 하는 데스크탑은 시타실 옆에 있다. ㅋ


이덕에 도무지 내가 어떤 PC에서 어떤 프로젝트를 수행하였는지 헷갈림


각설하고 기본적으로 책상의 3대의 컴퓨터가 굴러다니다 보니 키보드 + 마우스가 3set 다.

정신없다. 여기다 RaspberryPI까지 추가하니 한세트 더 추가 (트랙볼달린 미니 키보드라 다행 ㅋ)


그래서 RaspberryPI를 원격접속해서 사용하기로 결심!!!


1. 우분투에서 RaspberryPi 접속  (VNC이용)

(참고: http://blog.naver.com/PostView.nhn?blogId=ekstnsantlr0&logNo=80175896297)


 1) 우선 RaspberryPi 터미널에서 tightvncserver 설치

 sudo apt-get install tightvncserver


 2) 설치가 완료되면 tightvncserver 실행

/usr/bin/tightvncserver


 3) 실행하면 Password 입력화면 나오는데 (원격에서 접속할 때 쓸 Password) 알아서 입력

     verify에서 한번 더 입력

     그러고 나면 Would you like to enter a view-only password (y/n)? 이라고 묻는데 난 No 했슴


 4) New 'X' desktop is jsleepi: 1 이라고 뜸.. 이게 나중에 쓰인다고 함


 5) 접속할 우분투 PC의 터미널에 tightvncclient 설치


sudo apt-get install xtightvncviewer


 6) RaspberryPi의 IP 기억해둘 것

 ifconfig 치면 알수 있씀


 7) 우분투에서 접속 시도

xvncviewer 192.168.0.41:1      //뒤에 1이 아까 new X 어쩌구의 숫자임


 8) 성공




음.. 근데 1600*1200 해상도를 압축해서 보여주는 것 같기도하고 1280*1024 같기도 하다.


그래서 뒤적여보니 다음과 같은 옵션으로 풀스크린을 쓸 수 있긴하다만 절대 하지 말것 (우분투와 같은 X windows 하에서는)

xvncviewer -fullscreen 192.168.0.41:1


이렇게 옵션으로 -fullscreen을 붙이면 전체화면이 되는데 완전 엉망이 되고 끝내 Reset 버튼을 눌렀다는 T.T

xvncviewer 관련 문건을 보면 x windows 하에서 절대 사용하지 말라고 되어있슴..



 8) 큰문제 발생!!!

위의 -fullscreen 옵션이야 클라이언트 쪽에서 리셋하고 별 문제 없었다. 문제는 라즈베리파이의 tightvncserver가 문제

라즈베리파이를 재부팅 시키면

기존의 화면이 나오는 것이 아니고 로그인 창이 나오고 어떤 걸로도 접속할 수 없었다.

즉 부팅이 안됨

이와 관련하여 열심히 검색되었으나 해결한 사람이 안보임 T.T

http://www.raspberrypi.org/forum/viewtopic.php?f=28&t=27905


이 포스팅에서만 나와 같은 문제를 일으켰고 질문한 글 작성자도 결국엔 Raspbian을 지우고 다시 깔고 tightvncserver를 사용하지 않고 x11vnc를 사용하고 문제가 없었다고 한다. T.T


일단 나는 실패






'Study 외 > Raspberry' 카테고리의 다른 글

RaspberryPi 해상도 변경 (Rasbian)  (1) 2014.03.28

RaspberryPi 카테고리를 만든 기념으로

RaspberryPi가 무엇인지 그리고 설치 방법등 처음부터 쓰고 싶지만....... 귀찮다. ㅋ 아래 링크를 참고


1. 





두개의 SD카드에

하나는 Raspbmc 설치 또 하나는 Rasbian을 설치해두었드랬다.

Raspbmc는 원격에 있는 1080P동영상을 아무 문제 없이 재생한다. Raspberry Pi가 700Mhz ARM 계열로 알고 있는데 정말 대단한 동영상 재생능력이다.


여튼 작은 베어본 컴퓨터로 우분투 HTPC를 구성하고 XBMC를 설치해서 사용하는 바람에 Raspbmc는 별로 사용할 일이 없을 듯해서 Rasbian 을 사용하기로


원래 RaspberryPi가 HDMI연결을 해야하는데 (또는 컴포넌트 - 컴포지트던가? ㅋ-) 회사에 HDMI 입력되는 모니터가 없다.

그래서 HDMItoDVI로 연결을 했건만 이놈의 해상도가 800*600 쯤 되나보다. 해상도 변경 전에는 무언가를 하기가 쉽지 않아보여서 해상도 변경을 시도


1. 기본설정 / Monitor Settings ->"Unable to get monitor information"으로 암것도 할 수 없슴


2. 리눅스의 Xconfig 같은 무언가가 있을 것이라 생각.. 검색 역시 Rpiconfig라는게 있는 듯함 (참고 http://elinux.org/RPi_config.txt)

 - 그래서 링크에 나온걸 보고 해보기로 한다.


sudo nano /boot/config.txt



여기서 위의 링크를 참고하여


hdmi_force_hotplug = 1


hdmi_group = 2

hdmi_mode = 51        //해상도 1600*1200 60Hz로 변경을 위함


hdmi_drive = 1


로 /boot/config.txt 를 변경하고 reboot!!


해상도 변경 OK


캡춰를 첨부하고 싶지만 아직 설정중이라 캡춰프로그램을 안깔았슴


HDMI to VGA 또는 HDMI to DVI 어댑터를 사용하여 Raspbian을 사용하는 경우의 해상도 변경에 대해 적어두기 위해 포스팅!

이전에 사두었던 벨킨 공유기에 DD-WRT, TomatoUSB 핵펌 얹어서 무지 잘 쓰고 있었다.


 - 이거 셋팅하던 것도 기록해두면 좋았을 것을... ^^; 뒤늦게나마 참고했던 사이트 링크하자면

   핵펌하는 방법 : http://www.ppomppu.co.kr/zboard/view.php?id=oversea&no=157652

   외장하드 연결 및 파티션 : http://www.clien.net/cs2/bbs/board.php?bo_table=lecture&wr_id=173414

   Transmission : http://yjpark.tistory.com/146

 -


여튼 회사에서 토렌트 하나 추가할 게 있어서 외부접속을 시도했는데

공유기 접속 포트는 잘 접속이 되는데 Transmission 접속이 안된다. (내부망 즉 집에서는 잘 되는데 T.T)


Telnet 접속을 바로 시도해보았으나 접속 불가.. (공유기 설정을 보니 Telnet 접속은 외부 접속이 안되는듯)

그래서 회사에서 putty를 이용해서 공유기 SSH 접속을 해서 이것저것 함 보기로!!!

(공유기에 기본적으로 ssh를 지원, ssh 관련 메뉴를 보면 remote access enable 있는 걸로 보아서 외부접속이 될 듯!)




1. putty 설치

sudo apt-get install putty


2. putty 실행

 PuTTY SSH Client를 실행하면 됨


실행하면 다음과 같은 화면이 뜸



3. 접속 Host Name (or IP address)에 공유기 Host Name으로 접속



오옷!! 접속 성공!!


자 일단 회사에서 Putty로 집에 있는 공유기 까지는 접속했고.. 이제 왜 Transmission이 안되는지 함 보자.


1. 우선 외장하드가 잘 붙어 있나 확인

ls /mnt/


2. Optware 파티션을 opt로 마운트 시켜놓은게 잘 되어있는지 확인

ls /opt/


3. 외부에서 접속이 가능하도록 포트를 열어두었는지 확인

iptables -L INPUT


자 여기까지 한 결과가


아무것도 문제없이 다 잘 설정되어 있었다.. 근데 왜 접속이 안되는거지??


혹시나 해서 공유기 설정창 torrent에 접속



우씨.. 위에 참고한 링크들을 보면 공유기에서 torrent를 쓰기 위해서 이런저런 것들을 할게 많았는데


그냥 설정창에서 Enable 시키면 끝이었슴 --;;;


외부에서 접속 잘 됨!! 동작 잘 됨!! 

내부망에서는 아직 모르겠지만 안될이유가 없슴!!


여튼 오늘도 삽질



블루투스를 이용해서 Main PC와 통신을 해야해서 시리얼 프로그래밍이 필요하다.


그래서 검색해봤더니 (http://blog.naver.com/PostView.nhn?blogId=buniel1&logNo=60065576523&redirect=Dlog&widgetTypeCall=true)


오.. 뭐 이리 복잡해 --;


여기서 비동기 입력 방식을 처음에 채택..

예제를 보면 signal_handler_IO는 흔히 펌웨어에서의 Interrupt와 같은 개념

프로그래밍 하고 동작은 잘 하는데... 

문제는 signal_handler_IO가 Receive 할 때 뿐 아니고 Send 할 때도 발생한다.


그래서 다시 검색해서 가장 원론적인 문서(http://www.cmrr.umn.edu/~strupp/serial.html) 에 의해 다시 프로그래밍


소스는 캠으로 받은 영상을 모니터에 출력해주면서 블루투스를 통해 날아온 데이터를 읽어 콘솔에 뿌려주고 Echo하는 것..  설명은 생략


* 예제는 별거 없슴 UART Setting 하고 블루투스를 통해 날아오는 수신데이터 ReadSerialPort로 읽고 Write로 Sending 

   수신만 signal_handler_IO를 이용할 수 있었다면 참 좋았을텐데~ 


//============================================================================

// Name        : 140320_UARTTEST.cpp

// Author      : 

// Version     :

// Copyright   : Your copyright notice

// Description : Hello World in C++, Ansi-style

//============================================================================


#include <iostream>

#include <stdio.h>

#include <sys/time.h>


#include <termios.h>

#include <unistd.h>

#include <fcntl.h>

#include <sys/signal.h>

#include <sys/types.h>


#include <stdlib.h>




#include "opencv2/opencv.hpp"



#define BAUDRATE B115200


#define MODEMDEVICE "/dev/rfcomm0"

#define _POSIX_SOURCE 1

#define FALSE 0

#define TRUE 1


volatile int STOP=FALSE;



using namespace std;


int fd,c, res;

struct termios oldtio,newtio;

struct termios oldkey,newkey;

char rbuff[255];


int ReadSeralPort(void)

{

int retval;

res = read(fd,rbuff,255);

retval = res;

return retval;

}

int main() {


struct timeval start_time;

struct timeval end_time;

double elapsed_time;

float fps;

double total_fps = 0.0,average_fps = 0.0;

int cnt=1;

int key;

int i;

int ReadPort;

char buffer[25];

IplImage* img;

CvFont font;

cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX,0.5,0.5);

string Sstring;

pid_t pid;


long DATABITS, STOPBITS, PARITYON, PARITY;

DATABITS = CS8;

STOPBITS = 0;

PARITYON = 0;

PARITY = 0;


//Shell command

//Change permission


int ret = system("sudo chmod a+rw /dev/rfcomm0");

if(ret == -1) printf("chmod Fail %d\n",ret);

else printf("chmod OK %d\n",ret);


fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NDELAY);

if (fd <0) { perror(MODEMDEVICE); exit(-1); }


fcntl(fd, F_SETFL, 0);


tcgetattr(fd,&oldtio); // save current port settings


// canonical 입력처리를 위한 포트 세팅 */

newtio.c_cflag = BAUDRATE | CRTSCTS | DATABITS | STOPBITS | PARITYON | PARITY |  CLOCAL | CREAD;

newtio.c_iflag = IGNPAR | IGNCR ; //IGNPAR | ICRNL;

newtio.c_iflag &= ~(IXON | IXOFF | IXANY);

newtio.c_oflag &= ~OPOST;

newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //ICANON;

newtio.c_cc[VMIN]=0;

newtio.c_cc[VTIME]=0;

tcflush(fd, TCIFLUSH);

tcsetattr(fd,TCSANOW,&newtio);


write( fd, "Test String from jslee", 22);



CvCapture* capture = cvCaptureFromCAM(0);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH, 640);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT, 480);

cvSetCaptureProperty( capture, CV_CAP_PROP_FPS, 65);


while(1)

{

gettimeofday(&start_time,NULL);

sprintf(rbuff,"");

ReadPort = ReadSeralPort();

if(ReadPort)

{


printf("Receive String = ");


for(i=0;i<ReadPort;i++)

{

printf("%c",rbuff[i]);

//printf("%c",buffer[i]);


}

printf(" [res = %d]\n",ReadPort);

write( fd, rbuff, ReadPort);


}




cvGrabFrame(capture);

img = cvRetrieveFrame(capture);

cvPutText(img,rbuff,cvPoint(20,20),&font,CV_RGB(255,255,255));



cvNamedWindow( "PreviewImage", 1 );

cvShowImage( "PreviewImage", img );

cvMoveWindow("PreviewImage",200,400);




key = cvWaitKey(1);

if(key > 10) break;


gettimeofday(&end_time, NULL);



elapsed_time = (double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0 - (double)(start_time.tv_sec) - (double)(start_time.tv_usec)/1000000.0;

fps = 1.0 / elapsed_time;

total_fps += fps;

average_fps = total_fps / (double)(cnt);

cnt++;

if(cnt == 100)

{

cnt = 1;

total_fps = 0.0;

}

elapsed_time *= 1000.0;


//printf("Elapsed T = %2.3f ms, Frame = %2.3f (fps), Avrg Frame Rate = %2.3f\n",elapsed_time,fps,average_fps);


}

cvReleaseImage(&img);

cvReleaseCapture( &capture );

// restore old port settings

tcsetattr(fd,TCSANOW,&oldtio);

printf("Close and Quit\n");

close(fd);

// cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!



return 0;

}



특정색을 지정한 뒤 화면에서 그 색의 원만 검출한다.


일정환경에서 그냥 원을 찾는 것은 쉬운데 이건 생각보다 쉽지 않다.


이유는 기본적으로 물체추적을 할 때 영상을 흑백으로 전환하고 이진화하고 엣지 디텍트를 하는데


흑백으로 변환하면 색정보를 잃어버리고 또한 색에 따라서 이진화할 때 옵셋을 다르게 해주어야 하기 때문이다.


그래서 폭풍검색 결과 색공간을 변환해서 사용한다는 것을 알아냄

(색추출 알고리즘 참고 링크 : http://newstyle.egloos.com/2707246)


1. 우선 특정 포인트의 색을 지정하기 위해서 마우스 핸들러 프로그래밍

2. 원 영상의 Smoothing 

3. 색공간 변환 RGB -> HSV

4. 지정한 포인트의 H,S,V 값 추출

5. 검출할 H,S,V 값의 범위 지정

6. HSV 이미지 마스킹

7. 모폴로지 

8. 이미지에서 원 검출


하기로 한다.


1. 마우스 핸들링

 OpenCV의 HighGui는 마우스 이벤트에 대한 핸들러도 제공한다. cvSetMouseCallback() 이라는 함수를 이용하는데 OpenCV에서는 아래와 같이 설명한다


Sets mouse handler for the specified window


C++: void setMouseCallback(const string& winname, MouseCallback onMouse, void* userdata=0 )

C: void cvSetMouseCallback(const char* window_name, CvMouseCallback on_mouse, void* param=NULL )

Python: cv.SetMouseCallback(windowName, onMouse, param=None) → None

Parameters:

winname – Window name

onMouse – Mouse callback. See OpenCV samples, such as https://github.com/Itseez/opencv/tree/master/samples/cpp/ffilldemo.cpp, on how to specify and use the callback.

userdata – The optional parameter passed to the callback.


link : http://docs.opencv.org/modules/highgui/doc/user_interface.html?highlight=cvsetmousecallback#void cvSetMouseCallback(const char* window_name, CvMouseCallback on_mouse, void* param)


cvSetMouseCallback("PreviewImage",on_eventhandle,(void*)img); 

위와 같은 식으로 사용하였는데 img 영상에서 마우스 이벤트가 발생하면 on_eventhandle 라는 함수를 호출하도록 Setting 하는 것이다.

(http://yonghello.tistory.com/entry/%EB%A7%88%EC%9A%B0%EC%8A%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8  참조하였슴)


작성한 on_eventhandle는 아래와 같다.

void on_eventhandle(int event, int x ,int y, int flag, void* param)

{

switch(event)

{

case CV_EVENT_LBUTTONDOWN:

if(getFlag == 0)

{

getx = x;

gety = y;

getFlag = 1;

}else if(getFlag == 1)

{

getFlag = 2;

}

break;


case CV_EVENT_RBUTTONDOWN:

getFlag = 0;

break;

}

}


별다른건 없고 마우스 왼쪽 버튼 누르면 좌표를 얻어오고 한번 더 누르면 그 좌표로 셋팅, 우측 버튼 누르면 다시 처음부터 좌표 얻어올 수 있도록 초기화 하는거임.


3. 색공간 변환

색공간을 기존의 RGB(Red,Green,Blue) color space 에서 HSV(Hue, Saturation, Value - Brightness) 로 바꾼다 (http://ko.wikipedia.org/wiki/HSV_%EC%83%89_%EA%B3%B5%EA%B0%84)

OpenCV에서는  cvCvtColor(src,dest,CV_BGR2HSV); 로 간단하게 바꿀 수 있다.


4. 지정한 포인트의 H,S,V 추출

cvGet2D()함수를 이용하여 cvScalar로 저장하는 방법을 사용

cvGet2D()는 아래와 같이 설명하고 있다.

C: CvScalar cvGet1D(const CvArr* arr, int idx0)

C: CvScalar cvGet2D(const CvArr* arr, int idx0, int idx1)

C: CvScalar cvGet3D(const CvArr* arr, int idx0, int idx1, int idx2)

C: CvScalar cvGetND(const CvArr* arr, const int* idx)

Python: cv.Get1D(arr, idx) → scalar

Python: cv.Get2D(arr, idx0, idx1) → scalar

Python: cv.Get3D(arr, idx0, idx1, idx2) → scalar

Python: cv.GetND(arr, indices) → scalar

Return a specific array element.


Parameters:

arr – Input array

idx0 – The first zero-based component of the element index

idx1 – The second zero-based component of the element index

idx2 – The third zero-based component of the element index

idx – Array of the element indices

The functions return a specific array element. In the case of a sparse array the functions return 0 if the requested node does not exist (no new node is created by the functions).


link : http://docs.opencv.org/modules/core/doc/old_basic_structures.html?highlight=cvget2d#CvScalar cvGet2D(const CvArr* arr, int idx0, int idx1)


뭐 별건 없다. image에 지정한 x,y 좌표의 색정보를 가져오는 것임

CvScalar s = cvGet2D(HSVImg,gety,getx);

H = (int)s.val[0];

S = (int)s.val[1];

V = (int)s.val[2];


위와 같이 사용하였는데 여기서 중요한 것 하나!!!

cvGet2D의  첫번째 파라미터는 image, 두번째 파라미터는 y좌표, 세번째 파리미터는 x좌표 라는 것!! 

의례적으로 x,y 로 썼다가 한참 헤맸다. T.T

해당 영상이 HSV color space 이므로 자동으로 H,S,V 값으로 저장된다.

만약 RGB Color Space 라면

CvScalar v = cvGet2D(img,gety,getx);

getBlue = (int)v.val[0];

getGreen = (int)v.val[1];

getRed = (int)v.val[2];

역시 자동으로 Blue,Green,Red 값으로 받아들인다. 여기서 (Red,Green,Blue의 순이 아닌 Blue,Green,Red 순이라는 것 주의)


5. 검출할 H,S,V의 범위 지정

이건 같은 H,S,V 값만 가지는 픽셀을 선택하면 원의 색상 자체가 완전히 일정하지 않으므로 일정 범위를 줄 필요가 있겠다.

나는 그냥 원시적으로 다음과 같이 사용


//calculate HSV Boundary

lowH = H * LOW_BOUND;

highH = H * HIGH_BOUND;

if(highH >= 180) highH = 179;


lowS = S * LOW_BOUND;

highS = S * HIGH_BOUND;

if(highS >= 256) highS = 255;


lowV = V * LOW_BOUND;

highV = V * HIGH_BOUND;

if(highV >= 256) highV = 255;


위 소스를 보면 OpenCV의 HSV Color Space에서 각 H,S,V 값의 범위는 H = 0~179, S = 0~255, V = 0~255 라는 것을 알 수 있다.


6. HSV 이미지 마스킹

 지정한 범위의 색 외에 나머지는 모두 마스킹 시켜 원을 검출하기 용이하도록 한다.

 마스킹은 IplImage 가 아닌 Mat를 이용한다.


cvCreateMat(int rows, int cols, int type);

Creates a matrix header and allocates the matrix data.


C: CvMat* cvCreateMat(int rows, int cols, int type)

Python: cv.CreateMat(rows, cols, type) → mat

Parameters:

rows – Number of rows in the matrix

cols – Number of columns in the matrix

type – The type of the matrix elements in the form CV_<bit depth><S|U|F>C<number of channels> , where S=signed, U=unsigned, F=float. For example, CV _ 8UC1 means the elements are 8-bit unsigned and the there is 1 channel, and CV _ 32SC2 means the elements are 32-bit signed and there are 2 channels.

The function call is equivalent to the following code:


link : http://docs.opencv.org/modules/core/doc/old_basic_structures.html?highlight=cvcreatemat#CvMat* cvCreateMat(int rows, int cols, int type)


cvInRange(const CvArr* src, const CvArr* lower, const CvArr* upper, CvArr* dst)

Checks if array elements lie between the elements of two other arrays.


C++: void inRange(InputArray src, InputArray lowerb, InputArray upperb, OutputArray dst)

Python: cv2.inRange(src, lowerb, upperb[, dst]) → dst

C: void cvInRange(const CvArr* src, const CvArr* lower, const CvArr* upper, CvArr* dst)

C: void cvInRangeS(const CvArr* src, CvScalar lower, CvScalar upper, CvArr* dst)

Python: cv.InRange(src, lower, upper, dst) → None

Python: cv.InRangeS(src, lower, upper, dst) → None

Parameters:

src – first input array.

lowerb – inclusive lower boundary array or a scalar.

upperb – inclusive upper boundary array or a scalar.

dst – output array of the same size as src and CV_8U type.

The function checks the range as follows:


For every element of a single-channel input array:


\texttt{dst} (I)= \texttt{lowerb} (I)_0  \leq \texttt{src} (I)_0 \leq  \texttt{upperb} (I)_0


For two-channel arrays:


\texttt{dst} (I)= \texttt{lowerb} (I)_0  \leq \texttt{src} (I)_0 \leq  \texttt{upperb} (I)_0  \land \texttt{lowerb} (I)_1  \leq \texttt{src} (I)_1 \leq  \texttt{upperb} (I)_1


and so forth.


That is, dst (I) is set to 255 (all 1 -bits) if src (I) is within the specified 1D, 2D, 3D, ... box and 0 otherwise.


When the lower and/or upper boundary parameters are scalars, the indexes (I) at lowerb and upperb in the above formulas should be omitted.


link : http://docs.opencv.org/modules/core/doc/operations_on_arrays.html?highlight=cvinranges#void cvInRangeS(const CvArr* src, CvScalar lower, CvScalar upper, CvArr* dst)


mask = cvCreateMat(size.height, size.width,CV_8UC1);

cvInRangeS(HSVImg, cvScalar(lowH,lowS,lowV),cvScalar(highH,highS,highV),mask);


이와 같이 mask Mat를 만들고 cvInRanges 하면 원래 image에 정하진 범위 안의 H,S,V 값만을 남기는 Masking 이 완료된다.


7. 모폴로지

Masking 한 이미지는 도형 내부의 그림자나 외부와의 간섭으로 인한 불필요한 것들을 보정해 줄 필요가 있다. 

내가 사용한 cvErode()와 cvDilate()외에  OpenCV에서는 그 외에 다른 모폴로지 함수(cvMorphologyEx와 같은)를 많이 제공한다.

cvErode는 침식, cvDelate는 팽창  http://bench87.tistory.com/47 여기에 예제와 이미지까지 잘 설명되어 있다.


Erodes an image by using a specific structuring element.


C++: void erode(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )

Python: cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) → dst

C: void cvErode(const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1)

Python: cv.Erode(src, dst, element=None, iterations=1) → None

Parameters:

src – input image; the number of channels can be arbitrary, but the depth should be one of CV_8U, CV_16U, CV_16S, CV_32F` or ``CV_64F.

dst – output image of the same size and type as src.

element – structuring element used for erosion; if element=Mat() , a 3 x 3 rectangular structuring element is used.

anchor – position of the anchor within the element; default value (-1, -1) means that the anchor is at the element center.

iterations – number of times erosion is applied.

borderType – pixel extrapolation method (see borderInterpolate() for details).

borderValue – border value in case of a constant border (see createMorphologyFilter() for details).

The function erodes the source image using the specified structuring element that determines the shape of a pixel neighborhood over which the minimum is taken:


\texttt{dst} (x,y) =  \min _{(x',y'):  \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')


The function supports the in-place mode. Erosion can be applied several ( iterations ) times. In case of multi-channel images, each channel is processed independently.


link : http://docs.opencv.org/modules/imgproc/doc/filtering.html?highlight=cverode#void cvErode(const CvArr* src, CvArr* dst, IplConvKernel* element, int iterations)


Dilates an image by using a specific structuring element.


C++: void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )

Python: cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) → dst

C: void cvDilate(const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 )

Python: cv.Dilate(src, dst, element=None, iterations=1) → None

Parameters:

src – input image; the number of channels can be arbitrary, but the depth should be one of CV_8U, CV_16U, CV_16S, CV_32F` or ``CV_64F.

dst – output image of the same size and type as src.

element – structuring element used for dilation; if element=Mat() , a 3 x 3 rectangular structuring element is used.

anchor – position of the anchor within the element; default value (-1, -1) means that the anchor is at the element center.

iterations – number of times dilation is applied.

borderType – pixel extrapolation method (see borderInterpolate() for details).

borderValue – border value in case of a constant border (see createMorphologyFilter() for details).

The function dilates the source image using the specified structuring element that determines the shape of a pixel neighborhood over which the maximum is taken:


\texttt{dst} (x,y) =  \max _{(x',y'):  \, \texttt{element} (x',y') \ne0 } \texttt{src} (x+x',y+y')


The function supports the in-place mode. Dilation can be applied several ( iterations ) times. In case of multi-channel images, each channel is processed independently.



cvErode(mask,mask,NULL);

cvDilate(mask,mask,NULL);


위와 같이 사용하였는데 3번째 파라미터에 따라 그 침식 또는 팽창의 정도를 수정할 수 있다.


이렇게 완성한 영상을 마지막으로 기존에 해왔던 것 처럼 cvHoughCircle 함수를 이용하여 원을 찾아내면 끝


이렇게 완성한 소스는 다음과 같다.

(역시 대충 삽질해가며 작성하였기에 주석처리해버린 것들이라던지 좀 무식한 방법이라던지 그대로다 ㅋ)

//============================================================================

// Name        : GetColor.cpp

// Author      : 

// Version     :

// Copyright   : Your copyright notice

// Description : Hello World in C++, Ansi-style

//============================================================================


#include <iostream>

#include <stdio.h>

#include <sys/time.h>



#include "opencv2/opencv.hpp"


#define LOW_BOUND 0.7

#define HIGH_BOUND 1.3


struct timeval start_time;

struct timeval end_time;

double elapsed_time;

float fps;

double total_fps = 0.0,average_fps = 0.0;

long cnt=1;

char buffer[25];

CvFont font;

char getFlag = 0;

long getx,gety;

int getRed,getGreen,getBlue;

int Bval,Rval,Gval;


using namespace std;


void on_eventhandle(int event, int x ,int y, int flag, void* param)

{

switch(event)

{

case CV_EVENT_LBUTTONDOWN:


if(getFlag == 0)

{

getx = x;

gety = y;

getFlag = 1;

}else if(getFlag == 1)

{

getFlag = 2;

}

break;


case CV_EVENT_RBUTTONDOWN:

getFlag = 0;

break;

}




}

int main() {


IplImage* img;

IplImage* GrayImg=0;

IplImage* SmoothImg=0;

IplImage* Edge=0;

IplImage* HSVImg;

IplImage* HImg = 0;

IplImage* SImg = 0;

IplImage* VImg = 0;

CvMat* mask;

int key;

int k;

double dp = 1;

double min_dist = 300;

int H,S,V;

int lowH,lowS,lowV,highH,highS,highV;

CvSize size;

int cx,cy,radius,ccnt;

cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX,0.5,0.5);



CvCapture* capture = cvCaptureFromCAM(0);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH, 640);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT, 480);

cvSetCaptureProperty( capture, CV_CAP_PROP_FPS, 65);



while(1)

{

gettimeofday(&start_time,NULL);


cvGrabFrame(capture);

img = cvRetrieveFrame(capture);


cvSetMouseCallback("PreviewImage",on_eventhandle,(void*)img);


if(getFlag == 0)

{

sprintf(buffer,"Picking up color by left click");

cvPutText(img,buffer,cvPoint(200,20),&font,CV_RGB(255,255,255));

}else if(getFlag == 1)

{

sprintf(buffer,"If below color is right you chosen, start detect by left click again");

cvPutText(img,buffer,cvPoint(20,20),&font,CV_RGB(255,255,255));

size = cvGetSize(img);

CvScalar v = cvGet2D(img,gety,getx);

getBlue = (int)v.val[0];

getGreen = (int)v.val[1];

getRed = (int)v.val[2];

//Convert get RGB to HSV

SmoothImg = cvCreateImage(size,IPL_DEPTH_8U,3);

cvSmooth(img,SmoothImg,CV_GAUSSIAN,9,9,2,2);

HSVImg = cvCreateImage(size,IPL_DEPTH_8U,3);

cvCvtColor(SmoothImg,HSVImg,CV_BGR2HSV);

CvScalar s = cvGet2D(HSVImg,gety,getx);

H = (int)s.val[0];

S = (int)s.val[1];

V = (int)s.val[2];


sprintf(buffer,"X= %d,Y= %d,R= %d G= %d, B= %d, H= %d, S= %d, V= %d",getx,gety,getRed,getGreen,getBlue,H,S,V);

cvPutText(img,buffer,cvPoint(getx,gety),&font,CV_RGB(255,255,255));

//printf("X= %d,Y= %d,Red = %d, Green = %d, Blue = %d\n",getx,gety,getRed,getGreen,getBlue);


}else{

sprintf(buffer,"You can picking up again by right click");

cvPutText(img,buffer,cvPoint(100,20),&font,CV_RGB(255,255,255));

//SmoothImg = cvCreateImage(size,IPL_DEPTH_8U,3);

cvSmooth(img,SmoothImg,CV_GAUSSIAN,9,9,2,2);

//Change Color Space

cvCvtColor(SmoothImg,HSVImg,CV_BGR2HSV);


//calculate HSV Boundary

lowH = H * LOW_BOUND;

highH = H * HIGH_BOUND;

if(highH >= 180) highH = 179;


lowS = S * LOW_BOUND;

highS = S * HIGH_BOUND;

if(highS >= 256) highS = 255;


lowV = V * LOW_BOUND;

highV = V * HIGH_BOUND;

if(highV >= 256) highV = 255;

//Masking

mask = cvCreateMat(size.height, size.width,CV_8UC1);

cvInRangeS(HSVImg, cvScalar(lowH,lowS,lowV),cvScalar(highH,highS,highV),mask);

//CvMat* tempMat;

//tempMat = cvCreateMat(size.height, size.width,CV_8UC1);



//cvMorphologyEx(mask,mask,tempMat,NULL,CV_MOP_OPEN);

//IplConvKernel *se21 = cvCreateStructuringElementEx(21,21,10,10,CV_SHAPE_RECT,NULL);

//IplConvKernel *se11 = cvCreateStructuringElementEx(11,11,5,5,CV_SHAPE_RECT,NULL);

cvErode(mask,mask,NULL);

cvDilate(mask,mask,NULL);


//cvErode(mask,mask,se21);

//cvDilate(mask,mask,se11);

//cvErode(mask,mask,se21);

//cvReleaseStructuringElement(&se21);

//cvReleaseStructuringElement(&se11);

//cvReleaseMat(&tempMat);


//Hough Tranform

GrayImg = cvCreateImage(size,8,1);

cvCopy(mask,GrayImg,NULL);

cvSmooth(GrayImg,GrayImg,CV_GAUSSIAN,15,15,2,2);


CvMemStorage* storage = cvCreateMemStorage(0);


CvSeq* Circles = cvHoughCircles(GrayImg,storage,CV_HOUGH_GRADIENT,2,size.height/10,100,40,5,60);

//CvSeq* Circles = cvHoughCircles(GrayImg,storage,CV_HOUGH_GRADIENT,1,100,100,50,0,0);


for(k=0;k<Circles->total;k++)

{

float* circle;

circle = (float*)cvGetSeqElem(Circles,k);


ccnt = Circles->total;


cx = cvRound(circle[0]);

cy = cvRound(circle[1]);

radius = cvRound(circle[2]);

cvCircle(img,cvPoint(cx,cy),radius,CV_RGB(0,255,0),3,8,0);


sprintf(buffer,"X=%d, Y=%d, R=%d",cx,cy,radius);

cvPutText(img,buffer,cvPoint(cx-80,cy+radius+15),&font,CV_RGB(255,255,255));

}


cvReleaseMemStorage(&storage);


cvNamedWindow( "HSVImage", 1 );

cvShowImage( "HSVImage", HSVImg);

cvMoveWindow("HSVImage",850,200);


cvNamedWindow( "Mask", 1 );

cvShowImage( "Mask", mask);

cvMoveWindow("Mask",850,700);



}


cvNamedWindow( "PreviewImage", 1 );

cvShowImage( "PreviewImage", img );

cvMoveWindow("PreviewImage",200,200);


//cvNamedWindow( "GrayImage", 1 );

//cvShowImage( "GrayImage", GrayImg );

//cvMoveWindow("GrayImage",850,400);



key = cvWaitKey(1);

if(key > 10) break;


gettimeofday(&end_time, NULL);



elapsed_time = (double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0 - (double)(start_time.tv_sec) - (double)(start_time.tv_usec)/1000000.0;

fps = 1.0 / elapsed_time;

total_fps += fps;

average_fps = total_fps / (double)(cnt);

cnt++;

if(cnt == 100)

{

cnt = 1;

total_fps = 0.0;

}

elapsed_time *= 1000.0;


printf("Elapsed T = %2.3f ms, Frame = %2.3f (fps), Avrg Frame Rate = %2.3f\n",elapsed_time,fps,average_fps);



//cvReleaseMemStorage(&storage);

} //end of while(1)

cvReleaseImage(&img);

cvReleaseCapture( &capture );

cvReleaseMat(&mask);

cvReleaseImage(&SmoothImg);

cvReleaseImage(&GrayImg);

cvReleaseImage(&HSVImg);

return 0;

}


그래서 잘 찾느냐.. 그 결과는 다음과 같았다.




왼쪽 위의 영상이 원래의 이미지, 녹색의 원이 파란색의 물체를 잘 찾았슴을 보이고 아래에 마우스로 찍은 X,Y 좌표가 표시되고 있다.

우측 위의 영상은 원래의 이미지를 HSV Color Space로 변환한 이미지

우측 아래의 영상은 마스킹하고 모폴로지 까지 완료한 영상 :)


끝! ㅋ


전에 C 프로그램에서 쉘 명령어를 사용하여 블루투스 연결하는 것은 일단 실패한것이고


다음 방법으로 생각한 것이 우분투가 시작될 때 터미널을 실행하고 블루투스를 연결하는 것이다.


어차피 부팅이 되면 내가 짠 프로그램도 자동으로 시작되어야 하니 


우분투 시작프로그램 등록 방법을 알아둘 필요가 있다.


터미널을 열고 



gnome-session-properties 를 실행하면



이와 같이 시작프로그램 관리 창이 뜬다.



일단 나는 xterminal 을 실행 시켜보고자 한다.




이와같이 추가가 완료되고.. 그럼 리부팅!



위와같이 xterminal 이 일단 실행되는 것은 성공!!!


어.. 그럼 자동으로 여기다 명령어를 쓰는건 어떻게 하지??? 


자동으로 쓰는 건 -e 옵션이다. (http://kldp.org/node/112797)


여러 명령 쓰는건 대충 감과 삽질로 알아내고 ^^;;;


그래서 위의 gnome-session-properties 다시 실행 해서 명령을 다음과 같이 변경


xterm -e "sudo rfcomm release rfcomm0 ; sudo rfcomm connect 0; read"


전에 연결한 기억 때문인지 rfcomm0 가 기본적으로 mount 되어 있어서 

rfcomm release rfcomm0 로 release 

그리고 

rfcomm connect 0 로 connect


자 다시 부팅해보자!




오오 원하던 대로 부팅을 하고 xterm이 실행되고 블루투스로 지정한 PC연결 완료!!


그리고 C프로그램에서는 Shell명령으로 퍼미션 수정하고 실행하니 잘 된다!!!






이전 포스팅에서 Bluetooth Serial Port를 생성하고 연결하기 위해 몇가지 쉘 명령어를 이용해야만 했다.


기본적으로 내가 하고 있는 Linux Client PC는 Monitor도 없이 Stand Alone 으로 동작해야 해서 


전원을 켜면 제작한 프로그램이 실행되고 알아서 블루투스 연결하고 와이파이 연결하고 해야한다.


와이파이야 자동으로 연결되니 별 문제 없다면 블루투스는 프로그램 시작 전에 쉘 명령어를 입력해야 해서


C에서 입력 하는 방법을 검색 ㅋ (왠지 프로그래밍을 구글이 하는 것 같다. ^^;)


- 물론 윈도우 컴퓨터 등에서 원격접속으로 이래저래 실행 할 수도 있다. -


http://www.digipine.com/programming/25045


위 링크를 참고해서 다음과 같이 프로그램을 실행해보았다.


//Shell command

//Connect Bluetooth Seria Port

int ret = system("sudo su");

if(ret == -1) printf("sudo su Failed\n");

else printf("sudo su OK\n");


ret = system("password");

if(ret == -1) printf("Password input Failed\n");

else printf("Password input OK\n");

 즉 나는 장치를 컨트롤 해야하므로 Root 권한을 획득해야 한다.

그래서 루트권한 획득 sudo su 명령을 입력



그랬더니 다음과 같은 Error T.T

sudo: no tty present and no askpass program specified

sudo: no tty present and no askpass program specified

Sorry, try again.

sudo: no tty present and no askpass program specified

sudo: no tty present and no askpass program specified

Sorry, try again.

sudo: no tty present and no askpass program specified

sudo: no tty present and no askpass program specified

Sorry, try again.


http://stackoverflow.com/questions/21659637/sudo-no-tty-present-and-no-askpass-program-specified-netbeans


위 링크를 보니 문제는 sudo 명령 자체가 터미널에 붙여서 사용하는 것인데 쉘 명령으로 실행하니  sudo 명령을 attach 할 수 없는데 있던 것 같다.

상기 링크에서는 askpass를 이용하라는 데 난 좀 더 무식하게 해결하기로 했다.


방법은 sudo configuration을 수정하는 것

(이 방법은 추천하고 싶지 않다. sudo 명령을 비번없이 실행할 수 있기에 위험하다.)


위와 같이 sudo select-editor를 실행하면 Editor를 선택할 수 있다. nano가 편하니 2번 선택

(한번 선택하면 앞으로 계속 nano로 선택된다.)


그리고 visudo 실행한다.


# This file MUST be edited with the 'visudo' command as root.

#

# Please consider adding local content in /etc/sudoers.d/ instead of

# directly modifying this file.

#

# See the man page for details on how to write a sudoers file.

#

Defaults        env_reset

Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:$


# Host alias specification


# User alias specification


# Cmnd alias specification


# User privilege specification

root    ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges

%admin ALL=(ALL) ALL


# Allow members of group sudo to execute any command

#%sudo  ALL=(ALL:ALL) ALL  

%sudo ALL=NOPASSWD: ALL

# See sudoers(5) for more information on "#include" directives:


#includedir /etc/sudoers.d



아래쪽 %sudo 항목에서 

%sudo  ALL=(ALL:ALL) ALL  -> %sudo ALL=NOPASSWD: ALL 이렇게 바꿔줌


그리고 ctrl + o 로 저장 ctrl + x 로 나오면 앞으로 sudo 명령을 입력해도 비번을 묻지 않는다.


* 원래는 Ubuntu 에서 sudo configuration은 /etc/sudoers 에서 관리하는데 위험하기 때문에  visudo (/etc/sudoers.tmp)로 관리한다.

* visudo의 자세한 사용법은 검색 ㅋ (위험하니까 책임회피 ㅋ)

* 사실 한번 잘못써서 터미널에서 sudo를 사용못하게 되어서 복구모드로 진입해서 겨우 해결 (식겁했다는 T.T) http://www.psychocats.net/ubuntu/fixsudo  <= 이 사이트가 도움이 되었슴




여튼 포스팅이 삼천포로 가고 있다. ㅋㅋㅋ

다시 원래의 주제로 돌아와서 C에서 Shell 명령이 잘 되는지 다시 해보기로 한다.


결과는 다음과 같았다.


혹시 몰라 터미널을 실행하는 명령어를 입력했더니 Terminal 창이 하나 딱 뜬다.

그리고 기다린다. T.T

Terminal에서 exit 입력해서 끝내고 rfcomm connect 명령을 실행한다. 그리고 성공한다.

그런데 문제는 위와같이 실행중이 되어 버린다는 것이다.


사실 터미널에서 rfcomm connect를 수행할 때도 딱 저 메시지가 뜨고 터미널 작업은 새로운 터미널을 띄우고 했는데 T.T

여튼 어쩔수 없이 Ctrl+C로 연결을 끊고 넘어갔슴..

연결을 끊었으니 통신은 당연 안됨 T.T


다른 방법을 강구해보자!!





현재 하고 있는 작업 중

Server PC(Windows 7) 에서 명령을 보내면 Client PC(Linux) 에서 명령을 받아 해석하고 수행 그리고 결과를 넘겨주어야 하는데 이를 블루투스를 통해서 한다.

이를 위하여 Server PC와 Client PC는 블루투스 시리얼포트를 이용하여 통신을 하게 된다.


일단 Server는 윈도우 환경에서 BlueSoleil을 이용하니까 별문제가 없다. 

리눅스 PC에 NEXT-BT104라는 블루투스 동글을 설치하였는데 

이게 윈도우 상에서는 Serial Port Service가 보이는 걸로보아  (아마 SPP Protocol 아니면 RFCOMM protocol 이겠지) 시리얼 포트를 지원하는 것 같은데

리눅스에 꽂아두면 Server PC에서 사용가능한 서비스에 Serial Port가 나타나지 않는다.


 우분투/설정/블루투스 에서도 Serial Port 해당 서비스 안보임 T.T

이를 해결하기 위해 역시 검색 

http://robotbaram.tistory.com/entry/%EB%B8%94%EB%A3%A8%ED%88%AC%EC%8A%A4-%EC%8B%9C%EB%A6%AC%EC%96%BC-%ED%8F%AC%ED%8A%B8-%EC%84%A4%EC%A0%95 

위 링크에 가장 쉬운 방법이 있었다.


* Blueman 설치

$sudo apt-get install blueman



으로 설치하고 프로그램(이름이 "블루투스 관라지", 참고로 우분투/설정/블루투스는 이름이 "블루투스")을 실행하면



메뉴의 설정을 누르면 서비스에 Serial Port가 보임 이를 이용하여 연결하면 나의 경우 /dev/rfcomm0 로 연결됨 


자.. 이건 GUI환경에서이고 Terminal 환경에서 접속을 시도해 보자.

(이유는 C 프로그램 안에서 위의 작업들이 수행되어야 해서 T.T)


참고한 사이트는 아래와 같다.

http://stackoverflow.com/questions/15464475/how-to-setup-serial-communication-in-processing-to-dev-rfcomm0


위와 같이 터미널에서 hcitool scan 하면 주변의 블루투스 장치들이 검색된다.


연결은 rfcomm 을 이용한다.


$sudo nano /etc/bluetooth/rfcomm.conf

우선 위 명령어를 이용하여 rfcomm.conf 파일을 수정해주자.


# RFCOMM configuration file.

#


rfcomm0 {

#       # Automatically bind the device at startup

        bind yes;

#

#       # Bluetooth address of the device

        device 00:15:83:6B:FA:C1;

#

#       # RFCOMM channel for the connection

       channel 1;

#

#       # Description of the connection

        comment "Jslee-laptop-bluetooth";

}



기본적으로는 모두 #으로 주석처리 되어있는데 필요한 부분은 #을 지워 활성화 시켜준다.

bind no -> bind yes

device XX:XX:XX:XX:XX:XX: 연결하고자 하는 PC의 Bluetooth Address 

channel 1

그리고 혹시 몰라 comment도 ^^;



위 그림과 같이

$sudo rfcomm connect 0


를 입력하면


접속 성공!!!


Server PC의 BlueSoreil 에서도 연결이 되었슴이 나타난다.



그리고 작성한 시리얼통신 프로그램 예제를 실행하니

/dev/rfcomm0: Permission denied


메시지가 뜨고 실행이 종료된다.


즉 /dev/rfcomm0에 대한 권한이 없다는 것인데 이는 권한을 부여하면 된다.




위 그림과 같이


$ls -al /dev/rfcomm0 

하면 현재 /dev/rfcomm0 의 권한이 root 에게만 read ,write 가능한 것을 볼 수 있다.

이를

$chmod a+rw /dev/rfcomm0 

해주고 다시

$ls -al /dev/rfcomm0 

확인하면

read, write 권한이 모두에게 주어진 것을 볼 수 있다.



그리고 다시 실행하니 성공!!! 


자 이제 위의 쉘 명령들을 C프로그램 안에서 구현해봐야겠다.



이번에 할 것은 영상내의 사각형을 찾아내고 그 사각형의 기울기를 계산하는 것


아래 OpenCV에서 친절히 샘플 소스를 줬는데... 뭔 소린지 잘 모르겠어서 참고하고 기존에 쓰던 알고리즘을 이용해서 하기로 했다.


1. 영상취득

2. Gray Scale로 변환 - cvCvtColor()

3. Smooth - cvSmooth()

4. 이진화 - cvThreshold()

5. Edge Detect - cvCanny()

6. 코너검출 - cvFindContours()

7. 다각형 추측 - cvApproxPoly()

8. cvApproxPoly의 결과값이 사각형이라면 각 코너포인트를 이용하여 사각형의 긴 부분과 짧은 부분 구분

9. 사각형의 짧은 두 부분의 중간점 찾아내고 그 중간점을 atan() 이용하여 두 중간점을 있는 선의 기울기를 구한다.



OpenCV Squares.c Sample Code

// 
// The full "Square Detector" program. 
// It loads several images subsequentally and tries to find squares in 
// each image 
// 
#ifdef _CH_ 
#pragma package <opencv> 
#endif 

#include "cv.h" 
#include "highgui.h" 
#include <stdio.h> 
#include <math.h> 
#include <string.h> 

int thresh = 50; 
IplImage* img = 0; 
IplImage* img0 = 0; 
CvMemStorage* storage = 0; 
const char* wndname = "Square Detection Demo"; 

// helper function: 
// finds a cosine of angle between vectors 
// from pt0->pt1 and from pt0->pt2 
double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* pt0 ) 

double dx1 = pt1->x - pt0->x; 
double dy1 = pt1->y - pt0->y; 
double dx2 = pt2->x - pt0->x; 
double dy2 = pt2->y - pt0->y; 
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); 


// returns sequence of squares detected on the image. 
// the sequence is stored in the specified memory storage 
CvSeq* findSquares4( IplImage* img, CvMemStorage* storage ) 

CvSeq* contours; 
int i, c, l, N = 11; 
CvSize sz = cvSize( img->width & -2, img->height & -2 ); 
IplImage* timg = cvCloneImage( img ); // make a copy of input image 
IplImage* gray = cvCreateImage( sz, 8, 1 ); 
IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 ); 
IplImage* tgray; 
CvSeq* result; 
double s, t; 
// create empty sequence that will contain points - 
// 4 points per square (the square's vertices) 
CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvPoint), storage ); 

// select the maximum ROI in the image 
// with the width and height divisible by 2 
cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height )); 

// down-scale and upscale the image to filter out the noise 
cvPyrDown( timg, pyr, 7 ); 
cvPyrUp( pyr, timg, 7 ); 
tgray = cvCreateImage( sz, 8, 1 ); 

// find squares in every color plane of the image 
for( c = 0; c < 3; c++ ) 

// extract the c-th color plane 
cvSetImageCOI( timg, c+1 ); 
cvCopy( timg, tgray, 0 ); 

// try several threshold levels 
for( l = 0; l < N; l++ ) 

// hack: use Canny instead of zero threshold level. 
// Canny helps to catch squares with gradient shading 
if( l == 0 ) 

// apply Canny. Take the upper threshold from slider 
// and set the lower to 0 (which forces edges merging) 
cvCanny( tgray, gray, 0, thresh, 5 ); 
// dilate canny output to remove potential 
// holes between edge segments 
cvDilate( gray, gray, 0, 1 ); 

else 

// apply threshold if l!=0: 
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0 
cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY ); 


// find contours and store them all as a list 
cvFindContours( gray, storage, &contours, sizeof(CvContour), 
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) ); 

// test each contour 
while( contours ) 

// approximate contour with accuracy proportional 
// to the contour perimeter 
result = cvApproxPoly( contours, sizeof(CvContour), storage, 
CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 ); 
// square contours should have 4 vertices after approximation 
// relatively large area (to filter out noisy contours) 
// and be convex. 
// Note: absolute value of an area is used because 
// area may be positive or negative - in accordance with the 
// contour orientation 
if( result->total == 4 && 
fabs(cvContourArea(result,CV_WHOLE_SEQ)) > 1000 && 
cvCheckContourConvexity(result) ) 

s = 0; 

for( i = 0; i < 5; i++ ) 

// find minimum angle between joint 
// edges (maximum of cosine) 
if( i >= 2 ) 

t = fabs(angle( 
(CvPoint*)cvGetSeqElem( result, i ), 
(CvPoint*)cvGetSeqElem( result, i-2 ), 
(CvPoint*)cvGetSeqElem( result, i-1 ))); 
s = s > t ? s : t; 



// if cosines of all angles are small 
// (all angles are ~90 degree) then write quandrange 
// vertices to resultant sequence 
if( s < 0.3 ) 
for( i = 0; i < 4; i++ ) 
cvSeqPush( squares, 
(CvPoint*)cvGetSeqElem( result, i )); 


// take the next contour 
contours = contours->h_next; 




// release all the temporary images 
cvReleaseImage( &gray ); 
cvReleaseImage( &pyr ); 
cvReleaseImage( &tgray ); 
cvReleaseImage( &timg ); 

return squares; 



// the function draws all the squares in the image 
void drawSquares( IplImage* img, CvSeq* squares ) 

CvSeqReader reader; 
IplImage* cpy = cvCloneImage( img ); 
int i; 

// initialize reader of the sequence 
cvStartReadSeq( squares, &reader, 0 ); 

// read 4 sequence elements at a time (all vertices of a square) 
for( i = 0; i < squares->total; i += 4 ) 

CvPoint pt[4], *rect = pt; 
int count = 4; 

// read 4 vertices 
CV_READ_SEQ_ELEM( pt[0], reader ); 
CV_READ_SEQ_ELEM( pt[1], reader ); 
CV_READ_SEQ_ELEM( pt[2], reader ); 
CV_READ_SEQ_ELEM( pt[3], reader ); 

// draw the square as a closed polyline 
cvPolyLine( cpy, &rect, &count, 1, 1, CV_RGB(0,255,0), 3, CV_AA, 0 ); 


// show the resultant image 
cvShowImage( wndname, cpy ); 
cvReleaseImage( &cpy ); 



char* names[] = { "pic1.png", "pic2.png", "pic3.png", 
"pic4.png", "pic5.png", "pic6.png", 0 }; 

int main(int argc, char** argv) 

int i, c; 
// create memory storage that will contain all the dynamic data 
storage = cvCreateMemStorage(0); 

for( i = 0; names[i] != 0; i++ ) 

// load i-th image 
img0 = cvLoadImage( names[i], 1 ); 
if( !img0 ) 

printf("Couldn't load %s\n", names[i] ); 
continue; 

img = cvCloneImage( img0 ); 

// create window and a trackbar (slider) with parent "image" and set callback 
// (the slider regulates upper threshold, passed to Canny edge detector) 
cvNamedWindow( wndname, 1 ); 

// find and draw the squares 
drawSquares( img, findSquares4( img, storage ) ); 

// wait for key. 
// Also the function cvWaitKey takes care of event processing 
c = cvWaitKey(0); 
// release both images 
cvReleaseImage( &img ); 
cvReleaseImage( &img0 ); 
// clear memory storage - reset free space position 
cvClearMemStorage( storage ); 
if( (char)c == 27 ) 
break; 


cvDestroyWindow( wndname ); 

return 0; 
}




1~5 까지는 전에 설명했었으니 패스

6. cvFindCountours 는 영상에서 도형의 윤곽을 찾아내는 역할

   여기서 Mode를 잘 써야한다는데 난 그냥 예제대로 CV_RETR_LIST를 사용, 요놈은 도형의 외곽을 찾아 hierarchy구조 없이 List에 저장하는 모드.

   여튼 필요에 따라 이 모드를 잘 설정해야함


* cvFindContours


Finds contours in a binary image.


C++: void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())

C++: void findContours(InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset=Point())

Python: cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) → contours, hierarchy

C: int cvFindContours(CvArr* image, CvMemStorage* storage, CvSeq** first_contour, int header_size=sizeof(CvContour), int mode=CV_RETR_LIST, int method=CV_CHAIN_APPROX_SIMPLE, CvPoint offset=cvPoint(0,0) )

Python: cv.FindContours(image, storage, mode=CV_RETR_LIST, method=CV_CHAIN_APPROX_SIMPLE, offset=(0, 0)) → contours

Parameters:

image – Source, an 8-bit single-channel image. Non-zero pixels are treated as 1’s. Zero pixels remain 0’s, so the image is treated as binary . You can use compare() , inRange() , threshold() , adaptiveThreshold() , Canny() , and others to create a binary image out of a grayscale or color one. The function modifies the image while extracting the contours.

contours – Detected contours. Each contour is stored as a vector of points.

hierarchy – Optional output vector, containing information about the image topology. It has as many elements as the number of contours. For each i-th contour contours[i] , the elements hierarchy[i][0] , hiearchy[i][1] , hiearchy[i][2] , and hiearchy[i][3] are set to 0-based indices in contours of the next and previous contours at the same hierarchical level, the first child contour and the parent contour, respectively. If for the contour i there are no next, previous, parent, or nested contours, the corresponding elements of hierarchy[i] will be negative.

mode –

Contour retrieval mode (if you use Python see also a note below).


CV_RETR_EXTERNAL retrieves only the extreme outer contours. It sets hierarchy[i][2]=hierarchy[i][3]=-1 for all the contours.

CV_RETR_LIST retrieves all of the contours without establishing any hierarchical relationships.

CV_RETR_CCOMP retrieves all of the contours and organizes them into a two-level hierarchy. At the top level, there are external boundaries of the components. At the second level, there are boundaries of the holes. If there is another contour inside a hole of a connected component, it is still put at the top level.

CV_RETR_TREE retrieves all of the contours and reconstructs a full hierarchy of nested contours. This full hierarchy is built and shown in the OpenCV contours.c demo.

method –

Contour approximation method (if you use Python see also a note below).


CV_CHAIN_APPROX_NONE stores absolutely all the contour points. That is, any 2 subsequent points (x1,y1) and (x2,y2) of the contour will be either horizontal, vertical or diagonal neighbors, that is, max(abs(x1-x2),abs(y2-y1))==1.

CV_CHAIN_APPROX_SIMPLE compresses horizontal, vertical, and diagonal segments and leaves only their end points. For example, an up-right rectangular contour is encoded with 4 points.

CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS applies one of the flavors of the Teh-Chin chain approximation algorithm. See [TehChin89] for details.

offset – Optional offset by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context.



7. cvApproxPoly는 위에  cvFindContours로 찾아낸 도형의 윤곽을 근사화 시키는데 결과값반환이 4이면 사각형 요놈을 이용해서 사각형을 판별한다.

여기서 4번째 파라미터는 오직 CV_POLY_APPROX_DP만 서포트 한다니 예제대로 CV_POLY_APPROX_DP 를 사용하고

5번째 파라미터 epsilon은 근사화 정밀도.. 역시 어떤값이 좋은지 잘 모르므로 예제대로 ^^;


cvApproxPoly

Approximates a polygonal curve(s) with the specified precision.


C++: void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)

Python: cv2.approxPolyDP(curve, epsilon, closed[, approxCurve]) → approxCurve

C: CvSeq* cvApproxPoly(const void* src_seq, int header_size, CvMemStorage* storage, int method, double eps, int recursive=0 )

Parameters:

curve –

Input vector of a 2D point stored in:


std::vector or Mat (C++ interface)

Nx2 numpy array (Python interface)

CvSeq or `` CvMat (C interface)

approxCurve – Result of the approximation. The type should match the type of the input curve. In case of C interface the approximated curve is stored in the memory storage and pointer to it is returned.

epsilon – Parameter specifying the approximation accuracy. This is the maximum distance between the original curve and its approximation.

closed – If true, the approximated curve is closed (its first and last vertices are connected). Otherwise, it is not closed.

header_size – Header size of the approximated curve. Normally, sizeof(CvContour) is used.

storage – Memory storage where the approximated curve is stored.

method – Contour approximation algorithm. Only CV_POLY_APPROX_DP is supported.

recursive – Recursion flag. If it is non-zero and curve is CvSeq*, the function cvApproxPoly approximates all the contours accessible from curve by h_next and v_next links.

The functions approxPolyDP approximate a curve or a polygon with another curve/polygon with less vertices so that the distance between them is less or equal to the specified precision. It uses the Douglas-Peucker algorithm http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm


See https://github.com/Itseez/opencv/tree/master/samples/cpp/contours2.cpp for the function usage model.



http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=cvapproxpoly#CvSeq* cvApproxPoly(const void* src_seq, int header_size, CvMemStorage* storage, int method, double eps, int recursive)



8. 사실 OpenCV에 Moments 함수가 있어서 이를 이용하여 기울어진 이미지는 쉽게 판독이 가능하다는데 난 이미지 내의 도형의 기울기를 찾아내야 해서 안맞고

    뭔가 쉬운 방법이 있을 것 같은데  찾을수가 없었다. 그래서 그냥 무식한 방법으로 해결하기로 결정

   cvGetSeqElem을 이용해서 4개의 포인트를 찾고 이를 각각의 좌표로 변환

   x1 = pt1.x

   y1 = pt1.y

   이런식으로 4 포인트 각각의 x,y를 구하고


  4포인트간의 길이를 계산 pt1-pt2, pt2-pt3, pt3-pt4, pt4-pt1

  length = sqrt(pow(x1-x2,2) + pow(y1-y2,2)); <- 뭐 이거는 그냥 피타고라스 정리로 ^^;


  그 중 짧은 두 선분을 찾고 그에 해당하는 좌표를 기억


 

9. 사각형의 짧은 선분을 구성하는 두 포인트의 중간점을 계산

   그 두 중간점을 이용하여 각도를 계산

abase = px2 - px1;

aheight = py2 - py1;

angle = atan(aheight/abase);

angle_degree = angle*180.0/3.141592;


실행결과는 다음과 같이 잘 되었슴 ㅋ




왼쪽 위가 실제 촬영되고 있는 영상이고

빨간색 포인트는 검출한 사각형으 코너 포인트, 파란색 라인은 각 코너포인트간의 연결선으로 검출된 사각형을 그리고,

녹색선은 짧은 선의 중간점끼리 연결한 선

녹색선의 각도를 도형의 각도로 계산한다.


그 우측 Edge는 그 아래 이진화 한 영상의 Edge를 detect한 화면

속도도 초당 41fps 정도로 꽤 빠르다.


소스는 다음과 같다.

(좀 멍청하게 짜서 쓸데없이 중복되는거 많음 ㅋㅋㅋ 수정하기 귀찮아서 냅뒀슴)




//============================================================================

// Name        : 20140317_RECTANGLE2.cpp

// Author      : jslee

// Version     :

// Copyright   : Your copyright notice

// Description : Hello World in C++, Ansi-style

//============================================================================



#include <iostream>

#include <stdio.h>

#include <sys/time.h>

#include <math.h>

#include "opencv2/opencv.hpp"



struct timeval start_time;

struct timeval end_time;

double elapsed_time;

float fps;

double total_fps = 0.0,average_fps = 0.0;

long cnt=1;

int cx,cy,radius,ccnt;



using namespace std;



int main() {


IplImage* img;

IplImage* GrayImg=0;

IplImage* Edge=0;

IplImage* HSVImg;

IplImage* HImg = 0;

IplImage* SImg = 0;

IplImage* VImg = 0;


int key;

int i,k;

double dp = 1;

double min_dist = 300;

double t,s;

double angle12,angle23,angle34,angle41;

int px1,py1,px2,py2,sx1,sy1,sx2,sy2,sx3,sy3,sx4,sy4,spx1,spy1,spx2,spy2;

double len12,len23,len34,len41;

double longlen;

double abase, aheight;

char buffer[25];

CvFont font;

cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX,0.5,0.5);

CvSeq* result;

CvPoint* pt1;

CvPoint* pt2;

CvPoint* pt3;

CvPoint* pt4;



CvCapture* capture = cvCaptureFromCAM(0);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH, 640);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT, 480);

cvSetCaptureProperty( capture, CV_CAP_PROP_FPS, 65);



while(1)

{

gettimeofday(&start_time,NULL);


cvGrabFrame(capture);

img = cvRetrieveFrame(capture);

GrayImg = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);

Edge = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);

cvCvtColor(img,GrayImg,CV_BGR2GRAY);


cvSmooth(GrayImg,GrayImg,CV_GAUSSIAN,9,9,2,2);

cvThreshold(GrayImg,GrayImg,200,255,CV_THRESH_BINARY);


cvCanny(GrayImg,Edge,25,200,3);


CvMemStorage* storage = cvCreateMemStorage(0);



CvSeq* Contours;

cvFindContours(Edge,storage,&Contours, sizeof(CvContour),CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);

while(Contours)

{

result = cvApproxPoly(Contours,sizeof(CvContour),storage,CV_POLY_APPROX_DP,cvContourPerimeter(Contours)*0.02,0);


if(result->total == 4 && fabs(cvContourArea(result,CV_WHOLE_SEQ)) > 1000 && cvCheckContourConvexity(result))

{


pt1 = (CvPoint*)cvGetSeqElem(result,0);

pt2 = (CvPoint*)cvGetSeqElem(result,1);

pt3 = (CvPoint*)cvGetSeqElem(result,2);

pt4 = (CvPoint*)cvGetSeqElem(result,3);


int x1 = pt1->x;

int y1 = pt1->y;

int x2 = pt2->x;

int y2 = pt2->y;

int x3 = pt3->x;

int y3 = pt3->y;

int x4 = pt4->x;

int y4 = pt4->y;



cvCircle(img,cvPoint(x1,y1),3,CV_RGB(255,0,0),3,8,0);

cvCircle(img,cvPoint(x2,y2),3,CV_RGB(255,0,0),3,8,0);

cvCircle(img,cvPoint(x3,y3),3,CV_RGB(255,0,0),3,8,0);

cvCircle(img,cvPoint(x4,y4),3,CV_RGB(255,0,0),3,8,0);


cvLine(img,cvPoint(x1,y1),cvPoint(x2,y2),CV_RGB(0,0,255),2,8,0);

cvLine(img,cvPoint(x2,y2),cvPoint(x3,y3),CV_RGB(0,0,255),2,8,0);

cvLine(img,cvPoint(x3,y3),cvPoint(x4,y4),CV_RGB(0,0,255),2,8,0);

cvLine(img,cvPoint(x4,y4),cvPoint(x1,y1),CV_RGB(0,0,255),2,8,0);


//calculate angle

//calculate length of two point

len12 = sqrt(pow(x1-x2,2) + pow(y1-y2,2));

len23 = sqrt(pow(x2-x3,2) + pow(y2-y3,2));

len34 = sqrt(pow(x3-x4,2) + pow(y3-y4,2));

len41 = sqrt(pow(x4-x1,2) + pow(y4-y1,2));


//sort len

longlen = len12;

px1 = x1;

py1 = y1;

px2 = x2;

py2 = y2;


sx1 = x2;

sy1 = y2;

sx2 = x3;

sy2 = y3;

sx3 = x4;

sy3 = y4;

sx4 = x1;

sy4 = y1;



if(len23 > longlen)

{

longlen = len23;

px1 = x2;

py1 = y2;

px2 = x3;

py2 = y3;


sx1 = x3;

sy1 = y3;

sx2 = x4;

sy2 = y4;

sx3 = x1;

sy3 = y1;

sx4 = x2;

sy4 = y2;

}


if(len34 > longlen)

{

longlen = len34;

px1 = x3;

py1 = y3;

px2 = x4;

py2 = y4;


sx1 = x4;

sy1 = y4;

sx2 = x1;

sy2 = y1;

sx3 = x2;

sy3 = y2;

sx4 = x3;

sy4 = y3;

}


if(len41 > longlen)

{

longlen = len41;

px1 = x1;

py1 = y1;

px2 = x4;

py2 = y4;


sx1 = x1;

sy1 = y1;

sx2 = x2;

sy2 = y2;

sx3 = x3;

sy3 = y3;

sx4 = x4;

sy4 = y4;

}


spx1 = (sx1 + sx2) / 2;

spy1 = (sy1 + sy2) / 2;

spx2 = (sx3 + sx4) / 2;

spy2 = (sy3 + sy4) / 2;

cvLine(img,cvPoint(spx1,spy1),cvPoint(spx2,spy2),CV_RGB(0,255,0),2,8,0);

px1 = spx1;

py1 = spy1;

px2 = spx2;

py2 = spy2;

//find min x -> point2

//point 1 - point2

abase = px2 - px1;

aheight = py2 - py1;

angle23 = atan(aheight/abase);

angle12 = angle23*180.0/3.141592;

sprintf(buffer,"ang= %02.2f",angle12);

cvPutText(img,buffer,cvPoint(abs((px2+px1)/2),abs((py2+py1)/2)),&font,CV_RGB(255,0,0));





}

Contours = Contours->h_next;

}



cvNamedWindow( "PreviewImage", 1 );

cvShowImage( "PreviewImage", img );

cvMoveWindow("PreviewImage",200,100);


cvNamedWindow( "GrayImage", 1 );

cvShowImage( "GrayImage", GrayImg );

cvMoveWindow("GrayImage",850,600);


cvNamedWindow( "Edge", 1 );

cvShowImage( "Edge", Edge );

cvMoveWindow("Edge",850,100);


key = cvWaitKey(1);

if(key > 10) break;


gettimeofday(&end_time, NULL);



elapsed_time = (double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0 - (double)(start_time.tv_sec) - (double)(start_time.tv_usec)/1000000.0;

fps = 1.0 / elapsed_time;

total_fps += fps;

average_fps = total_fps / (double)(cnt);

cnt++;

if(cnt == 100)

{

cnt = 1;

total_fps = 0.0;

}

elapsed_time *= 1000.0;


printf("Elapsed T = %2.3f ms, Frame = %2.3f (fps), Avrg Frame Rate = %2.3f count = %d\n",elapsed_time,fps,average_fps,result->total);

//Circles->total = 0;

cvReleaseImage(&GrayImg);

cvReleaseImage(&Edge);

cvReleaseMemStorage(&storage);




} //end of while(1)

cvReleaseImage(&img);

cvReleaseCapture( &capture );


return 0;

}





http://blog.naver.com/PostView.nhn?blogId=budlbaram&logNo=50169171521 를 참고. (감사합니다)


1. OpenCV를 위한 라이브러리 설치

 터미널 열고 (원본에는 한꺼번에 타이핑 하는 것으로 나와있는데 혹시 모를 오류를 대비하여 난 하나씩 설치하였다.)


sudo apt-get install cmake

sudo apt-get install libtbb2

sudo apt-get install libtbb-dev

sudo apt-get install libgtk2.0-dev

sudo apt-get install libjpeg62-dev

sudo apt-get install libjasper-dev

sudo apt-get install libtiff4-dev

sudo apt-get install ffmpeg

sudo apt-get install libavformat-dev

sudo apt-get install libswscale-dev

sudo apt-get install libgstreamer0.10-dev

sudo apt-get install libgstreamermm-0.10-dev

sudo apt-get install libdc1394-22-dev

sudo apt-get install libv4l-dev

sudo apt-get install libopenexr-dev

sudo apt-get install libxine-dev

sudo apt-get install libunicap2-dev

sudo apt-get install libucil2-dev


별문제 없이 설치 되는 걸로 보아 그냥 이렇게 해도 된다. (나도 참 무식하다 ㅋ)


sudo apt-get install cmake libtbb2 libtbb-dev  libgtk2.0-dev libjpeg62-dev libjasper-dev libtiff4-dev ffmpeg libavformat-dev libswscale-dev libgstreamer0.10-dev 

libgstreamermm-0.10-dev libdc1394-22-dev libv4l-dev libopenexr-dev libxine-dev libunicap2-dev libucil2-dev


2. OpenCV 2.4.8 (현재) 설치


 http://opencv.org/downloads.html 에 가서 OpenCV2.4.8 for Linux/Mac 클릭하면 OpenCV 다운이 시작된다.


3. 압축을 풀고 폴더 이동

 opencv-2.4.8.zip 을 압축을 풀고 다운로드 폴더에 받았으므로 /home으로 이동


4. 압축을 풀어 놓은 opencv-2.4.8 폴더에 make를 위한 release 폴더 생성

mkdir release


5. cmake


sudo cmake -D BUILD_EXAMPLES=ON -D INSTALL_C_EXAMPLES=ON -D BUILD_TESTS=ON -D WITH_TBB=ON -D WITH_UNICAP=ON -D WITH_XINE=ON ../

(맨끝에 ../ 는 압축을 풀어놓은 디렉토리. 현재 opencv-2.4.8 폴더 안에 release 폴더를 만들었으므로 ../ 으로 씀


 시간 많이 걸린다. ^^;;;


6. OpenCV 설치

sudo make install


7. 라이브러리 링크 및 패키지 확인


라이브러리 링크

sudo ldconfig


패키지 확인

pkg-config --cflags opencv

pkg-config --libs opencv


다음과 같은 결과가 나온다




8. 이클립스 설치

http://www.eclipse.org/downloads/packages/eclipse-ide-cc-developers/junosr2

위에서 리눅스32bit용 다운로드 (현재 Juno SR2 버전)


다운받으면 압축풀면 됨


9. java 설치

sudo apt-get purge openjdk

sudo add-apt-repository ppa:webupd8team/java

sudo apt-get update

sudo apt get install oracle-java7-installer


여기서 openjdk는 안깔려있어서 없는 package라고 나오는 것 같고

마지막 oracle-java7-installer 실행하면 무슨 license 어쩌구 binary 어쩌구 code 어쩌구 에서 accept 해줘야 되는듯하다.

(귀찮아서 자세히 안읽어봄... 봐도 모름 ㅋ)


10. 이클립스 바로가기

 텍스트 편집기를 이용해서 /usr/share/applications/eclipse.desktop 파일 생성

sudo gedit /usr/share/applications/eclipse.desktop

그리고 다음의 내용 복사해서 붙여넣기


[Desktop Entry]

Encoding=UTF-8

Name=Eclipse

Comment=Eclipse IDE

Exec=/home/jslee/eclipse/eclipse

Icon=/home/jslee/eclipse/icon.xpm

Terminal=false

Type=Application

Categories=GNOME;Application;Development;

StartupNotify=true


난 /home/jslee/ 에 eclipse 압축을 풀어놓았으므로 위의 경로로 썼다.


11. OpenCV 테스트


 1) 프로젝트 만들기

    File / New / C++ Project 하면



일단 Project Name을 작성하고 (여기서는 OpenCV_Test)

Project Type: Executable / Hello World C++ Project

Toolchains : Linux GCC

그리고 Finish하면 다음과 같이 기본코드가 생성된다.


 2) OpenCV Header 및 Library 



 위 그림에서와 같이 Project Explorer 에서 프로젝트를 선택하고 우측 마우스 버튼 클릭 / Properties 를 선택한다.




  위 그림과 같이 C/C++ Builder 에서 Settings 를 선택하고

  GCC C++ Compiler에서 Includes를 선택한 뒤 OpenCV Header가 있는 /usr/local/include 의 경로를 넣어준다.



  마찬가지로   위 그림과 같이 C/C++ Builder 에서 Settings 를 선택하고

  GCC C++ Linker에서 Libraries를 선택한 뒤 

  Library 에는 사용할 Library 이름 opencv_core, opencv_highgui 를 입력

  그리고 Library search path에는 OpenCV library가 있는 /usr/local/lib 의 경로를 넣어준다.


  3) 소스 작성

   OpenCV_Test.cpp를 아래와 같이 작성하고 build 후 실행하면 아래의 화면과 같이 test.png를 opencv를 이용하여 화면에 출력한다.


 * test.png가 프로젝트 폴더 안에 있어야 한다.

 ** jpg는 안되더라. 처음에 test.jpg로 했는데 안되서 png로 했더니 된다. 이유는 모르겠지만 opencv의 cvLoadImage()에서 jpg는 load 하지 못하는것 같다.





  







요새 리눅스 환경에서 또 이미지프로세싱 하느라 이것저것 캡춰해서 블로그에 올려두는데

캡쳐프로그램으로 정확히 캡쳐하기 힘든 경우가 있다.

이때 Print Screen을 이용하려고 했는데...

이놈이 도무지 동작을 안한다. --;;;


여튼 그래서 뒤적이니

http://ioriy2k.pe.kr/archives/1221 에 좋은 정보!!


즉 Linux Kernel Option에서 Sys Rq. 가 활성화 되어 있어서 Print Screen을 누르면 Sys Rq. 로 인식한다는 것이다.


이 때의 해결책


sudo sysctl -w kernel.sysrq=0 하면 커널의 Sys Rq 가 비활성화 된다.

근데 그러면 부탕할 때마다 해줘야 하니까.

nano /etc/sysctl.d/10-disable-sysrq.conf 를 만든뒤


kernel.sysrq=0


을 쓰고 저장한뒤

start procps 하면 OK!!



꽤 오래전에 그 당시 기준 나름 싸게, 현 시점 기준 비싸게(ㅋㅋ)주고 산 넷북이 하나 있다.


이름하야 Acer Aspire-one AOA150 (응? AOA?? AOA 좋음 ㅋㅋㅋ)


사양이 

CPU : Intel Atom N270 1.6Ghz (1세대 아톰)

RAM : 1G

HDD : 160G

Monitor : 8.9인치 (1024 * 600) <- 이 세로 600 해상도가 아주 사람 미치게 한다 


여튼 이놈의 넷북이 아는 사람은 알겠지만 그 성능이 참 안타깝고도 안타까워 딱히 활용하기가 지랄같다.

그래서 해본게


1. 윈도우XP에 외장하드 붙여서 네트워크 하드로 써보자

 - 속도 1Mbps 나옴.. 너무 느림


2. Xpenology 설치해서 NAS로 써보자.

 - 다운받을 수 있는 모든 버전별 Xpenology 이미지들 다 적용안됨 T.T

  (일반PC에 설치할 수 있는 무료 NAS 운영체제가 몇가지 있는데 Xpenology , Free4Nas, OpenMediaVault 등이 있다.)

 - 그 와중에 예전에 해외구매 해두었던 Belkin 공유기에 DD-WRT에 Tomato 핵펌 올리는데 성공, 여기에 외장하드 (Ext3 포맷하고) 붙이니 속도 10MBps 가까이 나옴 

 


3. 리눅스 중에 가볍다는 Mint Linux 설치해보자.

  - Linux Mint 16 Petra Mate 32Bit 설치 성공

 - Interface도 괜찮고 나름 나쁘지 않게 돌아가나 저놈의 세로 해상도 600은 웹서핑하기도 지랄맞음 T.T



그렇다면 요놈을 홈시어터용 PC로 만들어보자고 결정하고 XBMC 설치

- XBMC 설치는 잘 되나 1080P  동영상 안돌아감, 720P 동영상도 힘들어함


그래서 대체 이놈을 뭐에 써먹나 고민중에 내린 결론이 뮤직서버로 만들면 어떨까 해서 검삭해보니

리눅스를 뮤직 스트리밍 서버로 사용하는 사람이 좀 있었다!!!


여튼 그래서 Mint Linux 에 MPD(Music Player Daemon) 을 설치하기로 결정!!


1. ALSA 오디오 출력 설치 (기본적으로 깔려있을꺼임!)

 


역시 이미 깔려있슴


2. MPD와 MPD Client 설치 

 MPD Client Program은 뒤적여보니 ncmpc를 많이 쓰나 봄




이렇게 설치


3. MPD 설정

nano /etc/mpd.conf

로 mpd 설정파일 편집한다

(nano 말고 vi를 쓰건 뭘 쓰건 텍스트 편집기 사용은 자기 마음)






bind_to_address 에서 "localhost" -> "0.0.0.0" 으로

밑에 #port "6600" -> port "6600" (#제거)


포트6600이 있는 걸로 봐서 원격에서 접속시 공유기 포트포워딩 mpd포트 6600번 해주면 될것 같음



#metadata_to_use 에서 #제거하고 metadata_to_use 로 metadata 사용 가능하게 만들기




심볼릭링크를 이용해서 음악을 가져오기 위해서

follow_outside_symlinks 와 follow_inside_symlinks 앞에 # 제거

(심볼릭 링크가 뭔진 아직 모르겠슴.. 이럴때 걍 시키는 대로 하는거임 ^^;;)




원격접속시 사용할 비밀번호 설정

사용하고자 한다면 #password 앞에 # 제거 그래고 "password@read,add ..." 에서 password 부분에 원하는 암호 입력



input 비활성화.. 앞에 다 #붙여서 비활성화 시킨다. 

물론 입력기능을 쓰고자 한다면 알아서 쓰시길



오디오 출력관련

첨에 설치확인했던 것처럼 오디오 출력은 alsa를 이용한다. 여기서 type "alsa"를 제외하곤 다 비활성화 되어있음, 그대로 쓰면 됨

하나 확인해야할 것은 device다.

일단 출력이 하나고 기본출력이면 그냥쓰면 될것 같은데 난 일전에 구매한 USB-DAC를 사용하고자 한다. 

아직 그걸 붙인건 아니지만 그걸 붙이면 수정해줘야 할듯


그렇다면 자기 device 확인 방법은 일단 편집기 저장하고 터미널로 돌아가자


터미널에서 aplay -l 해보면 장치목록이 나온다.

여기서 보면 0번 카드 Intel HDA 어쩌구 저쩌구

즉 위에 mpd.conf 파일의 출력 관련 항목에서

device "hw:0,0" 에서 앞의 0이 출력장치 번호이다.


나중에 USB-DAC를 연결하고 장치목록을 확인해보니 1번이더라 그리고 그쪽으로 출력을 하고 싶다면

device "hw:1,0" 이라고 하면 된다.... 될꺼다 ^^;



httpd 출력 설정

보아하니 외부에서 http로 접속할 때 음악의 출력을 내보내주는 부분에 대한 설정인듯하다.

일단 이놈은 사용해야 할 듯해서 #을 지워서 사용가능하게 했다.

encoder 항복에서 vorbis는 ogg포맷 lame은 mp3포맷이라는데 ogg포맷은 문제가 생겼다고 해서 나도 mp3포맷으로 설정

bitrate 192kbps, format 44100:16:1 이면 CD음질 일게다.




Internal Buffer Size 설정

audio_buffer_size의 앞에 #을 제거하여 사용하도록 설정하고 원래 "2048" 이었는데 4배인 "8192"로 설정했다.

(이게 뭐 얼마나 리소스 잡아먹겠어 하는 맘임.. 결과는 아직 모르겠슴 ㅋ)


자 이렇게 일단 설정을 마치고

네트워크 설정을 해야하는데 우선 지금 회사 내부네트워크 이므로 별다른 설정없이 테스트해 볼 생각


ifconfig 로 현재 연결된 무선네트워크의 ip를 보니 192.168.0.15 다


기억해두고 리부팅


4. MPD 동작 확인



sudo /etc/init.d/mpd restart로 확인했는데 266라인에서 뭐가 문제가 생겼단다.. --+


자 그럼 다시 nano /etc/mpd.conf 로 문제의 266라인을 보자.



아.. 이런 #audio_output 에서 #을 안지웠다. 그래서 지우고

sudo /etc/init.d/mpd restart




오.. 이제 문제없이 MPD가 시작되었다.!


5. 음악파일 MPD폴더에 링크

테스트를 위해 다운 받은 음악폴더를 MPD폴더에 링크 시킨다.

여기서 외장하드 등의 다른 장치는 /media/장치/음악폴더 를 이용하여 링크시킨다.


링크는 ln -s 음악이 들어있는 경로 및 폴더 /var/lib/mpd/저장할링크이름 으로 한다.


나는 테스트할 음악을 /home/jslee/music에 넣어두었고 저장할 링크이름은 imusiclink로 했다.



6. ncmpc로 데이터베이스 업데이트

그리고 터미널에서 ncmpc 실행하고 아까 설정한 비밀번호 입력하여 들어간뒤 Ctrl + u 하면 데이터베이스를 업데이트 하는데...



이런 아무것도 안뜬다 ㅠㅜ


아마 음악파일 MDP폴더에 링크시키는 것에 문제가 있지 않았는데 그건 아니고 저건 그냥 playlists 재생한게 없어서 안뜨는거

3번 열람을 누르면 아까 링크한 음악들이 보인다 ㅋ


7. 그런데 ncmpc 가 불편해서 그런지 sonata라는 클라이언트를 쓰나보다. 아마 GUI를 지원해서 그런듯

sudo apt-get install sonata

 위와 같이 sonata 설치


설치가 완료되면  sonata 실행하고 아까 입력한 비밀번호 치면



위와 같이 화면이 뜨고 Library에 아까 링크한 음악들이 표시된다.

재생할 음악을 선택하나 뒤 우측 마우스 버튼 클릭해서 add 하면 Current탭에 음악이 표시되고 이 음악을 재생시킬 수 있다.

이렇게 sonata 실행해서 음악감상하면 끝....

끝???


아.. 내가 원한건 아이폰이나 아이패드등 다른 기기를 클라이언트로 이용하는 것인데...


8. 다른 기기를 클라이언트로 사용

 아이폰 : mpod

 아이패드 : mpad

 안드로이드 : mpdroid

라는 MPD 클라이언트 어플이 있단다.


 우선 아이폰에 mpod 설치하고 실행하고

 Connections/Server에 넷북의 무선 IP 192.168.0.15 입력

 Connections/Password에 아까 비밀번호 입력해서 연결하고

 Select Music/song/playlist 를 누르니

오~ 아까 그 음악들이 보인다.

여기서 재생하면 끝


물론 이것은 내부네트워크니까 그냥 한거고

아까 말한것처럼 외부에서도 사용하기 위해서는 공유기 포트포워딩 하고 DDNS 받아서 설정해야할 것이다.


아래 링크 참고해서 작성 (감사합니다)

http://ko.goldenears.net/board/ST_TipInfo/3155439




우선 제일 먼저 해본게 원을 검출하는 일이다. 

(이게 실제 내가 나중에 가장 중점적으로 해야할 일 중 하나이기도 해서 ^^;;)


기본적으로 다음과 같이 프로그래밍 하면 정석이지 않을까 싶어서 프로그래밍 시작.

 1. 영상 획득 

 2. 획득한 영상을 흑백영상으로 변환 ( cvCvtColor 이용)

 3. 흑백영상을 이진화 (cvThreshold 이용)

 4. Edge 검출 (cvCanny 이용)

 5. 원 검출 (cvHoughCircles 이용)

 6. 검출한 원을 중심점 및 반지름을 구해서 Window에 표시


이러면 될 것 같아서 짠 프로그램의 결과는 다음과 같았슴




 오오 잘된다!!! 그런데 60fps으로 입력받던 카메라 영상이 영상 처리하느라고 20프레임까지 떨어져 버림 ㅠㅜ

여튼 위에서 언급한 함수들을 적용하는데 파라미터 설정의 삽질이 좀 있었다. ㅠㅜ

그걸 좀 적어두자면


1. 흑백영상으로 변환하기 위해 GrayImg라고 Iplimage를 선언하고 cvCreateImage를 사용

C: IplImage* cvCreateImage(CvSize size, int depth, int channels)

Parameters:

size – Image width and height

depth – Bit depth of image elements. See IplImage for valid depths.

channels – Number of channels per pixel. See IplImage for details. This function only creates images with interleaved channels.


link:

http://docs.opencv.org/modules/core/doc/old_basic_structures.html?highlight=cvcreateimage#IplImage* cvCreateImage(CvSize size, int depth, int channels)


 여기서 첫번째 parameter size는 획득한 영상을 이용 cvGetSize()로 잘 얻어왔고, depth도 예제대로 IPL_DEPTH_8U(unsigned 8bit integer)로 문제 없었다.

문제는 channels parameter, 예제에 3으로 되어있어서 3으로 두었더니 


 what():  /home/ubuntu/release/opencv-2.4.8/modules/imgproc/src/color.cpp:4422: error: (-215) dst.data == dst0.data in function cvCvtColor


위와 같은 Run-time 에러가 발생한다. 대체 이게 뭔 문제가 싶어서 뒤적여본 결과 CreateImage를 할 때 3 channel로 RGB 컬러영상으로 만들고 나중에 cvCvtcolor로 영상변환을 하려고 하니

메모리가 다른거지 뭐 --;;;

그래서 Channel을 1 (흑백영상)로 바꾸고 해결


2. 영상을 흑백으로 변환

Converts an image from one color space to another.


C++: void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0 )

Python: cv2.cvtColor(src, code[, dst[, dstCn]]) → dst

C: void cvCvtColor(const CvArr* src, CvArr* dst, int code)

Python: cv.CvtColor(src, dst, code) → None

Parameters:

src – input image: 8-bit unsigned, 16-bit unsigned ( CV_16UC... ), or single-precision floating-point.

dst – output image of the same size and depth as src.

code – color space conversion code (see the description below).

dstCn – number of channels in the destination image; if the parameter is 0, the number of the channels is derived automatically from src and code ..


link:

http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html?highlight=cvcvtcolor#void cvCvtColor(const CvArr* src, CvArr* dst, int code)


cvCvtColor함수를 이용하는데 이건 뭐 예제대로 별 무리 없이 되었슴. 
흑백으로 변환 할 때 code parameter에 CV_BGR2GRAY 로 하면 지가 알아서 흑백으로 변환, 마지막 파라미터는 안씀

3. 이진화 
Applies a fixed-level threshold to each array element.

C++: double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
Python: cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
C: double cvThreshold(const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type)
Python: cv.Threshold(src, dst, threshold, maxValue, thresholdType) → None
Parameters:
src – input array (single-channel, 8-bit or 32-bit floating point).
dst – output array of the same size and type as src.
thresh – threshold value.
maxval – maximum value to use with the THRESH_BINARY and THRESH_BINARY_INV thresholding types.
type – thresholding type (see the details below).

link:
http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html?highlight=cvthreshold#double cvThreshold(const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type)
 
cvThreshold()를 이용  여기서는 Thresh parameter를 잘 선택해야함. 
type을 CV_THRESH_BINARY로 선택했기 때문에 임계값 초과는 255(white), 이하는 0(black)으로 만들기 때문이다.
적정한 값 대입해서 테스트 환경에 맞게 선택했슴

4. cvCanny를 이용하여 Edge 검출
Finds edges in an image using the [Canny86] algorithm.

C++: void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false )
Python: cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) → edges
C: void cvCanny(const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size=3 )
Python: cv.Canny(image, edges, threshold1, threshold2, aperture_size=3) → None¶
Parameters:
image – single-channel 8-bit input image.
edges – output edge map; it has the same size and type as image .
threshold1 – first threshold for the hysteresis procedure.
threshold2 – second threshold for the hysteresis procedure.
apertureSize – aperture size for the Sobel() operator.
L2gradient – a flag, indicating whether a more accurate  L_2 norm  =\sqrt{(dI/dx)^2 + (dI/dy)^2} should be used to calculate the image gradient magnitude ( L2gradient=true ), or whether the default  L_1 norm =|dI/dx|+|dI/dy| is enough ( L2gradient=false ).

link:
http://docs.opencv.org/modules/imgproc/doc/feature_detection.html?highlight=cvcanny#void cvCanny(const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size)

사실 cvCanny는 Edge Detector라는 것만 알고 Parameter의 사용법에 대해서 잘 몰랐슴..
역시 여기서도 중요한게 3,4번 parameter threshold1과 threshold2  5번 파라미터는 Edge의 두께 정도로 생각하면 될 듯.
여튼 threshold1과 threshold2의 설명을 읽어봐도 모르겠고 해서 뒤적였더니 각각은 다음과 같은 의미를 가지고 있는듯
threshold1 = low threshold
threshold2 = high threshold

 Edge를 검출함에 있어서 어두운 영역의 임계값과 밝은 영역의 임계값을 결정 짓는 요소인듯하다.
불확실한데 값들을 변경시켜가며 한 결과는 그런듯 ㅋ

5. cvHoughCircles를 이용하여 원 검출
Finds circles in a grayscale image using the Hough transform.

C++: void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0 )
C: CvSeq* cvHoughCircles(CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1=100, double param2=100, int min_radius=0, int max_radius=0 )

Parameters:
image – 8-bit, single-channel, grayscale input image.
circles – Output vector of found circles. Each vector is encoded as a 3-element floating-point vector (x, y, radius) .
circle_storage – In C function this is a memory storage that will contain the output sequence of found circles.
method – Detection method to use. Currently, the only implemented method is CV_HOUGH_GRADIENT , which is basically 21HT , described in [Yuen90].
dp – Inverse ratio of the accumulator resolution to the image resolution. For example, if dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has half as big width and height.
minDist – Minimum distance between the centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed.
param1 – First method-specific parameter. In case of CV_HOUGH_GRADIENT , it is the higher threshold of the two passed to the Canny() edge detector (the lower one is twice smaller).
param2 – Second method-specific parameter. In case of CV_HOUGH_GRADIENT , it is the accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first.
minRadius – Minimum circle radius.
maxRadius – Maximum circle radius.

link
http://docs.opencv.org/modules/imgproc/doc/feature_detection.html?highlight=cvhoughcircle#CvSeq* cvHoughCircles(CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1, double param2, int min_radius, int max_radius)

 여기서 dp 1이면 입력해상도와 동일한 해상도, 2면 절반이라는데 잘 모르겠으니 1만 썼다 ㅋ
minDist는 검출된 원의 중심사이의 거리를 설정하는 듯, 즉 이게 너무 좁으면 가까운 영역에서 많은 원들이 검출 될 수 있기에 적당히 조절할 필요가 있다.
minRadius 는 검출할 원의 최소 반지름, maxRadius는 검출할 원의 최대 반지름 <= 요넘들의 범위를 좁히면 cvHoughCircles()의 수행속도가 빨라진다. 그러므로 필요에 맞게 범위 조절할 것
여기서도 잘 모르겠는게 param1과 param2
param1은 Canny Edge Detector의 Threshold 값이라고 하고
param2는 accumulator 임계값이라는데 뭔소린지 모르겠슴.. ㅠㅜ (기본이 안되어 있다보니 이런게 어렵다 ㅠㅜ)
여튼 요놈들도 상황에 맞게 조절했슴 ㅋ


이렇게해서 프로그래밍한 소스는 다음과 같다.
//============================================================================
// Name        : OpenCVTest3.cpp
// Author      : jslee
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <stdio.h>
#include <sys/time.h>

#include "opencv2/opencv.hpp"

struct timeval start_time;
struct timeval end_time;
double elapsed_time;
float fps;
double total_fps = 0.0,average_fps = 0.0;
long cnt=1;



using namespace std;
int main() {

IplImage* img;
IplImage* GrayImg=0;
IplImage* Edge=0;

int key;
int k;
double dp = 1;
double min_dist = 100;



CvCapture* capture = cvCaptureFromCAM(0);
cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH, 640);
cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT, 480);
cvSetCaptureProperty( capture, CV_CAP_PROP_FPS, 60);


while(1)
{
gettimeofday(&start_time,NULL);

cvGrabFrame(capture);
img = cvRetrieveFrame(capture);

GrayImg = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
cvCvtColor(img,GrayImg,CV_BGR2GRAY);
cvThreshold(GrayImg,GrayImg,200,255,CV_THRESH_BINARY);

Edge = cvCreateImage(cvGetSize(GrayImg),IPL_DEPTH_8U,CV_THRESH_BINARY);

cvCanny(GrayImg,Edge,25,200,3);

CvMemStorage* storage = cvCreateMemStorage(0);

CvSeq* Circles = cvHoughCircles(Edge,storage,CV_HOUGH_GRADIENT,dp,min_dist,200,25,30,50);

for(k=0;k<Circles->total;k++)
{
float* circle;
int cx,cy,radius;

circle = (float*)cvGetSeqElem(Circles,k);
cvCircle(img,cvPoint(cvRound(circle[0]),cvRound(circle[1])),cvRound(circle[2]),CV_RGB(255,0,0),3,8,0);

}

cvNamedWindow( "PreviewImage", 1 );
cvShowImage( "PreviewImage", img );
cvNamedWindow( "EdgeImage", 1 );
cvShowImage( "EdgeImage", Edge );
key = cvWaitKey(1);
if(key > 10) break;

gettimeofday(&end_time, NULL);


elapsed_time = (double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0 - (double)(start_time.tv_sec) - (double)(start_time.tv_usec)/1000000.0;
fps = 1.0 / elapsed_time;
total_fps += fps;
average_fps = total_fps / (double)(cnt);
cnt++;
if(cnt == 100)
{
cnt = 1;
total_fps = 60.0;
}
elapsed_time *= 1000.0;

printf("Elapsed Time = %2.3f ms, Frame Rate = %2.3f (fps), Average Frame Rate = %2.3f \n",elapsed_time,fps,average_fps);



} //end of while(1)

cvReleaseCapture( &capture );

return 0;
}



그런데 parameter 들을 찾고 적용하면서 cvHoughCircles()의 parameter를 보고 문득 든 생각이....
 cvHoughCircles의 canny threshold가 있는 것을 보니 cvHoughCircles 함수 내부적으로 Edge Detect를 하는 것은 아닌가 하는 생각이 들었다.
결정적인 이유가 cvCanny 함수의 임계값과 cvHoughCircles에서 canny threshold를 잘못 적용하면 원을 검출 못하는 결과가 발생해서였다.
그리고 인터넷에 있던 예제들 중에도 별다른 과정 없이 cvHoughCircles만 사용하는 것들이 있었다.
그렇다면 실행속도도 단축시킬겸 한번 해보기로!!



 실행결과 잘 된다!!! 그런데 실행속도의 향상은 없다고 봐야겠다. T.T
 그리고 흑백처리를 하지 않으면 Run-Time Error가 발생해서 그거 찾기보단 일단 흑백변환은 해서 적용했다.
기존에 사용했던 cvThreshold(), cvCanny() 삭제하고 cvHoughCircles의 param2만 값을 50으로 바꿔서 수행했다.

자.. 이제 실행속도는 어떻게 향상시킬수 있을 지 고민 좀 해봐야겠다.



















 현재 Ubuntu 12.04에 OpenCV 설치해서 영상처리 프로그래밍 중임.


 640 * 480 해상도에 60fps 의 카메라가 필요해서 찾던 중 좋은 정보 발견!!!


 Sony PlayStation Eye가 USB Interface 임에도 640*480 (60fps), 320 * 240 (120fps) 임 가격도 44,000원 정도로 저렴!!


 여튼 이놈이 실제 60fps가 입력되는지 확인 할 필요가 있슴


 윈도우7에 Visual Studio2010 + OpenCV 에서는 확인 하였슴!! (오~ 조으네)


 여튼 실제 작업할 리눅스 환경에서 드라이버 문제라던지 있을 수 있으므로 동작 및 사양 확인할 필요가 있어서


1. 우선 영상을 녹화 -> 재생하여 해상도 및 프레임 확인 

  1) 가장 널리 알려진 웹캠 프로그램 Cheese에서 영상 녹화 및 확인 결과 640*480에 25fps 로 Fail !! (자동 설정된 Driver)

  2) lsusb 명령으로 현재 USB 장치 확인


       일단 lsusb로 확인한 USB에 연결된 Eye는 Playstation Eye로 확인 된다.

  3) 여기서 Driver 버전 확인, 터미널 열고 modinfo gspca-ov534 입력 

     

 

  3) 확인 결과 gspca/ov534 Driver 잘 잡힘, 즉 Cheese가 걍 25frame으로 녹화한 거임

  4) 다른 프로그램 없나 뒤적뒤적 -> 우분투 소프트웨어 관리자에서 cam으로 검색 camorama webcam viewer 라는 프로그램이 보임 -> 설치

  5) camorama webcam viewer 실행




    오오 하단을 보니 현재 설정 60fps , 평균 54.19fps 잘 된다!!! cheese가 역시 문제였슴



 2. 카메라의 인식 및 동작은 잘 되는 것으로 보이므로 프로그램을 직접 짜서 확인!




  하단 printf 된 콘솔을 확인해보니 프레임 잡아서 뿌리는데 걸리는 시간 그리고 이를 이용해 계산한 Frame Rate, 그리고 평균 Frame Rate를 보니 잘 되고 있다.

  여기서 cvWaitKey(1)의 사용으로 1ms 의 의미없는 지연시간이 들어간다. 

  실제 영상처리 연산을 하자면 60 fps 영상처리는 쉽지 않아 보이긴 한다 --;;


 3. 확인을 위하여 카메라를 30 fps로 설정하고 프로그램을 동작


  

   정확히 동작하고 있다.


 4. 동작을 확인한 소스코드는 다음과 같다. 


 

//============================================================================

// Name        : OpenCVTest3.cpp

// Author      : jslee

// Version     :

// Copyright   : Your copyright notice

// Description : Hello World in C++, Ansi-style

//============================================================================


#include <iostream>

#include <stdio.h>

#include <sys/time.h>


#include "opencv2/opencv.hpp"

struct timeval start_time;

struct timeval end_time;

double elapsed_time;

float fps;

double total_fps = 0.0,average_fps = 0.0;

long cnt=1;




using namespace std;

int main() {


IplImage* img = 0;

int key;


CvCapture* capture = cvCaptureFromCAM(0);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH, 640);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT, 480);

cvSetCaptureProperty( capture, CV_CAP_PROP_FPS, 30);


while(1)

{

gettimeofday(&start_time,NULL);


cvGrabFrame(capture);

img = cvRetrieveFrame(capture);


cvNamedWindow( "PreviewImage", 1 );

cvShowImage( "PreviewImage", img );


key = cvWaitKey(1);

if(key > 10) break;

//end_time = clock();

gettimeofday(&end_time, NULL);


// elapsed_time = (double)(end_time-start_time) / CLOCKS_PER_SEC;

elapsed_time = (double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0 - (double)(start_time.tv_sec) - (double)(start_time.tv_usec)/1000000.0;

fps = 1.0 / elapsed_time;

total_fps += fps;

average_fps = total_fps / (double)(cnt);

cnt++;

elapsed_time *= 1000.0;


printf("Elapsed Time = %2.3f ms, Frame Rate = %2.3f (fps), Average Frame Rate = %2.3f \n",elapsed_time,fps,average_fps);




} //end of while(1)


cvReleaseCapture( &capture );

//cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!

return 0;

}


 음.. 일단 오늘 작업은 이걸로 마무리.

 그러고보니 리눅스에서 OpenCV 설치에 관해 안적은듯.. 그건 내일 할수도 있고... 바쁘면 안할수도 있고 ㅋ

+ Recent posts