September 25, 2018

디바이스마트 미디어:

[48호]A.F.T.S(automatic fruit thinning system)

Cap 2017-10-18 13-15-49-260

2017 ICT 융합 프로젝트 공모전 참가상

A.F.T.S(automatic fruit thinning system)

글 | 부산대학교 홍준영, 김진우, 백건우, 신인철, 안태경, 우승빈

1. 심사평
칩센 작품 개발자의 의견과 같이 농업 분야와 같은 1차 산업의 경쟁력을 높이기 위해서는 스마트팜과 같은 IT 기술 부분이 반드시 접목이 되어야한다는 것도 사실입니다. 그러한 동일한 의견을 가지고 작품의 보고서를 살펴 보았음에도 작품의 구상은 있으나 구체적 결과에 대한 내용을 찾아 볼 수 없습니다. 키넥트 센서를 사용하여 구현하게 되는 경우에 대한 내용도 한쪽 면에서 센싱 측정된 이미지 및 결과를 기준으로 평가를 한 것으로 보입니다. 이를 통해서는 적적하게 상품성 없는 과일을 솎아낼수 있으리라 생각되지 않습니다. 구체적인 작품 구현을 해본다면 자연스럽게 보고서에 적시된 심플한 기법만으로 적과 시스템의 안정성이 보장되지 않는 것을 느끼게 되리라 생각됩니다.

뉴티씨 아이디어 자체는 좋습니다. 농가에 실제로 이런게 배급되면 사용할 사람이 생길 것 같습니다. 실제 시연 영상이 있으면 더 좋았겠습니다. 실제 매우 필요한 시스템 같습니다.

위드로봇 농업에 ICT 기술을 접목하는 시도는 최근 트렌드에 비추어 적절한 방향이라 생각됩니다. 적외선 카메라, RGB-D 카메라 등을 이용하는 부분은 좋았으나 완성도 측면에서는 부족한 부분이 많습니다. 좀 더 추가로 연구하면 좋은 결과를 얻을 수 있을 것이라 생각됩니다.

2. 작품 개요
2.1. 배경 및 필요성
우리나라 농촌분야 산업을 보면 농촌인구의 대부분이 감소하고 고령화가 되어가고 있으며 농가의 소득 정체, 곡물의 자급률 하락 등 많은 어려움을 겪고 있다. 그 뿐 아니라 수입 농산물은 매년 증가하고 있으며 전체 산업에서 농업의 비중은 계속 낮아지고 있다. 이런 노동인구 및 농지 감소, 농업경쟁력 약화 등으로 농업 산업이 경쟁력을 잃어가고 있는 문제를 해결하기 위해 ICT 기반 스마트 농업 기술 도입이 필요하다. 농업과 ICT 융합으로 정보를 수집하고 분석, 활용하여 농업의 각 단계에서 생산성의 향상을 기대할 수 있다. 기존의 단순 생산 중심의 농업이 ICT 융합으로 고부가가치 산업으로 확대 발전하고 있다.

현재 미국이나 일본 유럽에는 관련된 스마트 팜 농장이 많이 발전되어 있으며, 우리나라에도 통신 3사 중심으로 스마트팜 보급이 확산되고 있는 추세이다. 발달하고 있는 스마트 농업은 온실 농장의 내부 온도, 습도, 수분 상태의 파악 등 많은 부분에서 농가의 자동화를 하고 있다. 하지만 아직은 과수분야에서의 발육 정도를 감별해 수확 작업의 자동화를 하는 기술이 부족하다고 한다. 이처럼 스마트 농업기술에서는 과수를 탐지, 감별하는 작업은 반드시 필요하며 사람의 손길이 많이 들어가기 때문에 이를 해결하고자 한다. 과수의 과일을 손쉽고 정확하게 구분하여 수확작업의 자동화를 기대하고 있다.

2.2. 목표
과일 농사를 지을 때 나무를 보호하고 좋은 과실을 얻기 위하여 너무 많이 달린 과일을 솎아내는 작업이 꼭 필요하다(적과). 흔히 ‘적과‘라고 알려진 이 작업은 현재 감소하는 농업 인구에 비해 너무나도 큰 노동력을 필요로 하는 작업이다. 이에 우리는 노동력 절감과 농촌산업의 활성화를 위하여 솎아내야 하는 열매를 탐지하고 과수의 생장을 파악할 수 있는 새로운 플랫폼을 개발하고자 한다.

우리가 Automatic Fruit Thinning System(AFTS)라 명명한 자동 적과 시스템은 기본적으로 키넥트 센서를 이용하는 장치이다. 키넥트에 포함된 RGB 카메라를 이용하여 이미지 데이터를 처리하여 과실을 잎이나 줄기등과 구별한다. 여기에 추가적인 장치로 적외선 열 카메라를 활용하여 잎과 줄기의 생장 상태를 측정한다. 이 과정은 적과(열매를 솎아내는 과정)의 자동화를 달성하는데 큰 기여를 할 수 있을 것으로 기대된다.

이를 토대로, 이용자들의 다양한 형태의 데이터베이스를 확보하여 이를 분석, 활용해 개별적 사용자들의 요구에 따른 맞춤형 정보를 제공할 수 있다. 이는 빅데이터 기술, 딥러닝을 활용하여 실현될 수 있다. 이러한 접근은 사용자들의 성향과 재배 방식을 예측할 수 있다. 결과적으로, 농촌산업의 노동력 절감, 효율적 작동, 새로운 농업 인구의 유입에 이바지할 수 있다.

2.3. 기대효과
농촌 고령화율 추이와 농촌 인구 중 순수 농업인 비중은 크게 감소하고 있는 추세이다. 이에 따라 노동력은 자연히 감소하게 되는데 과수재배에 있어 노동력을 많이 필요로 하는 적과 과정에서 이 프로젝트의 시스템을 이용하여 노동력 절감 효과를 기대할 수 있다. 적과 작업은 장기간에 걸쳐 지속적으로 노동력이 필요로 하는 작업이기 때문에 A.F.T.S 시스템을 이용하여 직접 적과 선별 과정에 개입하지 않아도 되므로 줄어드는 노동인구를 보완할 수 있을 것이다.
또한 해마다 귀농, 귀촌 인구가 증가함에 따라 초보 농업인 인구가 증가한다. 하지만 초보 농업인들은 기존 농업인들에 비해 농업 관련 데이터가 부족해 잦은 귀농 실패가 발생한다. 하지만 A.F.T.S를 이용해 기존에 다른 농장에서 사용한 데이터를 빅데이터화 하여, 새로 기농한 초보 농업인에게 제공함으로써 귀농 실패를 막고 귀농 진입장벽을 낮출 수 있을 것이라고 예상한다.

3. 작품 설명
3.1. 주요 동작 및 특징
이번 ICT 융합 프로젝트에서는 저렴하게 구할 수 있는 RGB 센서와 정확한 과일 측정을 위한 depth 센서가 있는 키넥트 센서와, 적외선 센서를 이용하여 보다 정확한 적과를 목표로 제작한다.

3.1.1. RGB를 이용한 이미지 처리
RGB 이미지 촬영을 위해 kinect 센서를 이용하게 되는데 키넥트 센서는 중앙의 RGB Camera 오른쪽에 부착 된 적외선 송출 프로젝터를 이용하여 전면의 물체에 픽셀 단위의 적외선을 송출한다. 그 다음 RGB Camera 옆에 부착된 적외선 카메라에서 적외선 송출 프로젝터에서 송출된 점들이 반사되는 것을 받아들여 물체를 인식한다.

이를 이용하여, 판별하고자 하는 과수의 이미지 테스트 작업을 먼저 실시하게 된다. 테스트 작업에서는 과수를 촬영한 이미지에서 픽셀 color 데이터를 추출하여 과일, 잎, 줄기, 배경 등으로 분류한다. 그 중에 과일 이미지의 color 데이터를 이용하여 실제 과수 촬영에 사용한다. 실제 작업에서는 이미지를 픽셀 RGB값과 비교하여 과일과 같은 색을 찾아내게 된다.

3.1.2. Blob 처리
과일로 분류한 데이터에도 오차가 발생하여 비슷한 줄기나 잎을 감지하게 된다. 이 때문에 BLOB(binary large object)방식을 이용해 관련된 픽셀끼리 연결하는 작업을 한다.
OpenCV를 이용하여 키넥트로부터 촬영한 RGB 이미지를 입력해 사진의 가로와 세로의 픽셀 개수만큼 이차원 배욜을 생성한 후, 각 픽셀의 위치에 대응하는 순서의 R, G, B 배열에 각각의 RGB 수치를 저장한다.

그 후, 입력받은 이미지의 픽셀을 RGB의 평균값을 기준으로 그보다 높으면 백색, 낮으면 검은색으로 흑백으로 이진화 처리를 한다. 다음으로 이진화 처리한 이미지를 이용하여 BLOB Detecting을 실행한다. 과일 blob으로 윤곽이 생기게 되고, 과일과 다른 줄기, 잎을 분리 할 수 있게 된다. RGB 이미지 처리와 blob 두 과정을 거치게 되면 대부분의 과일을 판별할 수 있게 된다.

3.1.3. depth를 이용한 과일 크기 판단
마지막 키넥트 센서를 depth를 이용한다. 키넥트 센서의 핵심 기능 중 하나인 IR Depth Sensor는 object와 센서간의 거리를 측정해준다. 센서와 과일의 거리를 측정하여, 과일의 위치 알 수 있으며, depth와 RGB 데이터를 처리하여 과일의 정확한 크기를 알 수 있다.

3.1.4. 적외선 카메라를 이용한 식물의 건강상태 확인
식물은 잎의 상태에 따라 특정 파장 반사하는 양이 크게 차이가 난다. 실제로 건강한 잎의 경우 가시광선을 8% 반사하는데 비해 적외선은 50%나 반사한다. 이를 이용하여 living leaf과 dead leaf으로 건강 상태를 판단할 수 있고, 병든 잎의 탐지로 병충해 유무 판별에도 사용 할 수 있다. 이런 데이터는 적과를 선택할 수 있는 기준을 제시해 줄 뿐 만 아니라 병충해의 발견으로 초기에 확산을 방지 할 수 있다.

3.2. 전체 시스템 구성

48 ict afts (1)
1. RGB이미지 픽셀 수치화 및 과일 RGB 판단
2. blob 작업을 위한 이미지 이진화 처리
3. blob을 통한 과일 이미지 윤곽 추출
4. 과일의 형태를 식별하여 위치 확인
5. depth를 이용한 깊이 측정 후 과일의 3차원 위치 확인
6. 적외선 카메라를 이용하여 잎의 건강상태 확인
7. 과일의 성장 상태 확인하여 적과 작업

3.3. 개발 환경(개발 언어, Tool, 사용 시스템 등)

48 ict afts (2)
kinect 센서 동작을 위해 C++언어를 이용하여 visual studio에서 개발한다. kinect 센서로 물체를 촬영하게 되면 RGB 데이터, depth 정보를 획득 및 저장하게 된다. 적외선 센서는 아두이노와 연결하고, 측정한 데이터를 컴퓨터로 전송한다. 이러한 정보를 C++를 이용하여 과일 탐지와 적과에 필요한 정보로 처리한다. 최종 정보와 과일의 위치를 알려준다.

4. 단계별 제작 과정
초기 작품 구상
상품 가치가 없는 과일을 솎아 내기 위하여 과일의 색을 활용하여 적과를 판단한 뒤 과일과 machine의 거리와 image에서 적과의 좌표를 활용하여 적과의 위치를 찾아낸다. 그리고 그 정보들을 취합하여 적과한다.

48 ict afts (3)

구체화

적과를 판단하기 위해 먼저 나무의 사진을 찍은 후 색 대비를 이용하여 전체 image중 과일만을 추출해낸다. 추출해낸 과일 image에서의 RGB값을 통해 데이터베이스에 쌓인 상품 가치가 높은 과일의 RGB값과 비교하고, 성장속도를 판단하여 성장 속도가 더디고, 성장했을 때 상품가치가 없을 확률이 높은 과일을 선택한다. 그 후 3차원 가상 좌표에 Depth Sensor와 RGB Sensor로 촬영한 image로 X, Y, Z좌표를 대입하여 과일의 위치를 찾아낸다.

48 ict afts (4)

최종
Kinect Sensor를 통해 나무의 image를 촬영하여 Main Computer에 보낸다. Main Computer에서 image processing을 통해 해당 과일의 상품가치를 판단한다. 만약 상품가치가 없을 경우 Main Computer에서는 Kinect Sensor와 적외선 센서로 받은 과일의 위치와 함께 적과하라는 명령을 내린다.

48 ict afts (1)

5. 소스코드
5.1. 키넥트 RGB Code

void CColorBasics::ProcessColor()
{
HRESULT hr;
NUI_IMAGE_FRAME imageFrame;
hr = m_pNuiSensor->NuiImageStreamGetNextFrame(m_pColorStreamHandle, 0, &imageFrame);
if (FAILED(hr))
{
return;
}
INuiFrameTexture * pTexture = imageFrame.pFrameTexture;
NUI_LOCKED_RECT LockedRect;
pTexture->LockRect(0, &LockedRect, NULL, 0);
if (LockedRect.Pitch != 0)
{
m_pDrawColor->Draw(static_cast<BYTE *>(LockedRect.pBits), LockedRect.size);
if (m_bSaveScreenshot)
{
WCHAR statusMessage[cStatusMessageMaxLen];
WCHAR screenshotPath[MAX_PATH];
GetScreenshotFileName(screenshotPath, _countof(screenshotPath));
hr = SaveBitmapToFile(static_cast<BYTE *>(LockedRect.pBits), cColorWidth, cColorHeight, 32, screenshotPath);
if (SUCCEEDED(hr))
{
// Set the status bar to show where the screenshot was saved
StringCchPrintf( statusMessage, cStatusMessageMaxLen, L”Screenshot saved to %s”, screenshotPath);
}
else
{
StringCchPrintf( statusMessage, cStatusMessageMaxLen, L”Failed to write screenshot to %s”, screenshotPath);
}
SetStatusMessage(statusMessage);
m_bSaveScreenshot = false;
}
}
pTexture->UnlockRect(0);
m_pNuiSensor->NuiImageStreamReleaseFrame(m_pColorStreamHandle, &imageFrame);
}

5.2. 키넥트 Depth Code

void CDepthBasics::ProcessDepth()
{
HRESULT hr;
NUI_IMAGE_FRAME imageFrame;
hr = m_pNuiSensor->NuiImageStreamGetNextFrame(m_pDepthStreamHandle, 0, &imageFrame);
if (FAILED(hr))
{
return;
}
BOOL nearMode;
INuiFrameTexture* pTexture;
hr = m_pNuiSensor->NuiImageFrameGetDepthImagePixelFrameTexture(
m_pDepthStreamHandle, &imageFrame, &nearMode, &pTexture);
if (FAILED(hr))
{
goto ReleaseFrame;
}
NUI_LOCKED_RECT LockedRect;
pTexture->LockRect(0, &LockedRect, NULL, 0);
if (LockedRect.Pitch != 0)
{
int minDepth = (nearMode ? NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE : NUI_IMAGE_DEPTH_MINIMUM) >> NUI_IMAGE_PLAYER_INDEX_SHIFT;
int maxDepth = (nearMode ? NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE : NUI_IMAGE_DEPTH_MAXIMUM) >> NUI_IMAGE_PLAYER_INDEX_SHIFT;

BYTE * rgbrun = m_depthRGBX;
const NUI_DEPTH_IMAGE_PIXEL * pBufferRun = reinterpret_cast<const NUI_DEPTH_IMAGE_PIXEL *>(LockedRect.pBits);
const NUI_DEPTH_IMAGE_PIXEL * pBufferEnd = pBufferRun + (cDepthWidth * cDepthHeight);
while ( pBufferRun < pBufferEnd )
{
USHORT depth = pBufferRun->depth;
BYTE intensity = static_cast<BYTE>(depth >= minDepth && depth <= maxDepth – depth % 256 : 0);

*(rgbrun++) = intensity;
*(rgbrun++) = intensity;
*(rgbrun++) = intensity;
++rgbrun;
++pBufferRun;
}
m_pDrawDepth->Draw(m_depthRGBX, cDepthWidth * cDepthHeight * cBytesPerPixel);
}
pTexture->UnlockRect(0);
pTexture->Release();
ReleaseFrame:
m_pNuiSensor->NuiImageStreamReleaseFrame(m_pDepthStreamHandle, &imageFrame);
}

5.3. 이미지로부터 픽셀단위의 RGB값 추출과 배열화 Code

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat image = imread(“C:/…/image1.jpg”);
Vec3b buf;
for(int i = 0; i < image.rows; i++)
for(int j = 0; j < image.cols; j++)
{
buf = image.at<Vec3b>(i,j);
array_B[i][j] = buf[0];
array_G[i][j] = buf[1];
array_R[i][j] = buf[2];
}
//imwrite(“C:/…/image2.jpg”,image3);
imshow(“Image”,image);
waitKey(0);
}

5.4. 이미지 이진화 Code

int AutoThreshold(IplImage* img)
{
unsigned char t = 128;
unsigned char min = 255, max = 0;
unsigned char p;
for(int i=0; i<img->height; i+=2){
for(int j=0; j<img->width; j+=2){
p = img->imageData[i*img->widthStep + j ];
min = ( min > p ) ? p : min;
max = ( max < p ) ? p : max;
}
}
t = (min + max) / 2;
return t;
}

5.5. Blob 처리 Code

IplImage* contour(IplImage* img)
{
int di[8] = {-1,-1,-1, 0, 0, 1, 1, 1},
dj[8] = {-1, 0, 1,-1, 1,-1, 0, 1};

int mask[3][3]={{ -1, -1, -1 },
{ -1, 8, -1 },
{ -1, -1, -1 }};

int p;
Img2Aray(img);

for(int i=1; i<img->height-1; i++){
for(int j=1; j<img->width-1; j++){
p = Map[i][j].r;
p *= mask[1][1];

for(int k=0; k<8; k++){
p = p + ( (unsigned char)Map[i+di[k]][j+dj[k]].r *mask[1+di[k]][1+dj[k]] );
}
if(p > 255) p = 255;
else if(p < 0) p = 0;
img->imageData[i*img->widthStep + j ] = (unsigned char)p;
}
}
return img;
}

 

 

 

Leave A Comment

*