on
CV - PlantCV를 이용한 엽면적 측정기 개발
CV - 지난 글
프로젝트 배경
엽면적은 재배와 식물 연구에서 중요한 의미를 많이 가지고 있는 요소라 볼 수 있다. 생육조사
는 현 식물의 상태를 판단하는 중요한 과정이다.
그렇다면 이렇게 중요한 엽면적이 식믈의 재배 및 연구에서 어떤 의미를 가지는 걸까?
광합성에 영향
- 잎은 광합성에 필요한 광에너지를 흡수한다. 엽면적이 넓을수록 엽록소 함량, 광에너지 수용면적이 증가함을 의미한다.
- 식물은 잎을 통해 얻은 에너지를 통해 광합성 산물을 만들어 활용하여 성장 및 유지에 사용하기 때문에 수확량에 영향을 주게 된다.
증산에 영향
- 식물은 잎의 기공을 통해 증산을 하며, 이를 통해 엽온 조절, 수분손실 조절 등의 역할을 한다.
- 건조하거나, 습한 환경에 대해 증산량 조절을 통해 환경에 적응할 수 있기 떄문에 수분 요구량을 예측하는데 활용할 수 있다.
이렇게 엽면적은 식물의 재배와 연구에서 중요한 부분을 차지한다. 그렇다면 어떻게 엽면적을 구할 수 있을까?
기기를 통한 엽면적 측정 (Li3100c)
관행적으로 엽면적을 측정하는 방식으로는 기기를 활용하는 방식이 있다. 현재 연구실에서 사용하고 있는 Li3100c가 이에 해당한다. Li3100c는 롤러에 잎이 말려들어가고, 아래에서 비친 빛에 잎이 가리는 부분의 면적을 산출하여 잎의 면적을 계산한다.
ImageJ
ImageJ는 이미지의 조정, 필터링, 측정 및 분석 기능을 제공하는 소프트웨어로 실험을 통해 얻은 이미지 자료를 분석하는데에 용이하게 사용된다. 엽면적의 경우 잎을 찍은 사진에 스케일링을 하고, 잎의 면적을 폴리건으로 잡아주어 잎의 면적을 산출할 수 있다.
위 방법들은 관행적으로 잎의 면적을 산출하는 방법이다. 하지만 이러한 방법들에는 각각의 단점이 존재한다.
- Li3100c의 경우 아래에서 비추는 빛을 가리는 면적에 의해 값을 계산하기 때문에, 판에 이물질이 묻어있을 경우 정확도가 하락할 가능성이 있다.
- Li3100c이 경우 결과가 LCD 화면에 출력되기 때문에 결과를 직접 수기 또는 엑셀 테이블에 손수 옮겨 적어야 하는 단점이 존재한다.
- 무엇보다 Li3100c는 가격이 매우 높이 때문에 부담스럽다.
- ImageJ의 경우 스케일바, 잎의 면적 폴리곤 작업을 손수 직접 해야하기 때문에 시간이 오래 걸리는 단점이 존재한다.
이러한 단점들을 커버하면서 잎의 면적을 계산할 수 있는 방법이 없을까 평소에 고민하고 있었고, 최근에 ‘식물표현체학’에서 배운 이미지 프로세싱 기법과 평소에 관심있던 대형 딥러닝 기술을 활용하여 잎의 면적을 계산할 수 있는 프로그램을 이번 스마트농업프로그래밍 수업을 통해 만들어 보자고 생각하여 이번 프로젝트를 진행하게 되었다.
기존 Computer Vision 기술과 딥러닝
이미지를 컴퓨터를 이용하여 처리하는 Computer Vision (CV) 기술은 이미 오랜 과거 부터 연구되어 오던 분야이다. 예를들어
-
사진에서 사람, 자동차, 동물을 인식하는 기술로 다양한 촬영 기법, 센서, 카메라를 사용하여 촬영한 이미지를 인식하여 컴퓨터가 이미지 속의 객체, 패턴, 색상 등을 식별하고 분류하는 기술.
-
이미지를 개선하거나 수정하는 기술로, 노이즈 제거, 명암 조정, 필터링 등을 포함하는 영상 처리기술
최근 딥러닝을 이용한 이미지 처리 기술의 발달로 이미지 처리 기술의 발달은 가속화하게 된다.
기존의 컴퓨터 비전 방식은 특정한 작업에 최적화된 알고리즘을 사용하는 반면, 딥러닝 기반 방식은 데이터로부터 자동으로 특징을 학습하여 더 넓은 범위의 문제에 적용할 수 있다. 딥러닝의 등장으로 컴퓨터 비전 분야는 혁신적인 발전을 이루었으며, 이는 이미지 인식, 객체 감지, 분류 작업 등에서 획기적인 성능 향상을 가져왔다.
이번 프로젝트에서는 이러한 두가지의 Computer Vision 기술을 바탕으로 엽면적 측정 알고리즘을 만들고 두 기술의 결과를 비교해보면 재밌을 것 같다는 생각을 하게되었고, 이를 실행에 옮겨보았다.
제작 장비
제작에 사용된 장비는 다음과 같다.
- 젯슨 나노 (Jetson Nano)
젯슨 나노 보드는 소형 보드로 라즈베리파이에 GPU를 탑재한 싱글보드 컴퓨터라 이해하면 된다. 젯슨 나노를 사용하여 딥러닝이나 머신러닝과 같은 소프트웨어를 구현할 수 있는 장점이 있다. 이러한 이유로 최근 AI가 적용된 프로젝트에 메인보드로 많이 쓰이고 있는 추세이다.
- 아두이노 우노 (Arduino UNO)
아두이는 오픈소스를 기반으로 한 단일보드인 마이크로컨트롤로 완성된 보드와 관련 개발환경을 말한다. 2005년 이탈리아의 IDII에서 하드웨어에 익숙치 않은 학생들이 자신들의 디자인 작품을 손쉽게 제어할 수 있도록 하기 위해 고안되었다. 이 중 아두이노 우노는 R3 보드를 사용하며, 총 44개의 핀과 단자들로 구성되어있다. 각 핀과 단자들은 아두이와 다른 보드 또는 센서들의 제어에 이용될 수 있으며, 다양한 응용이 가능하다.
-
라즈베리파이 카메라 (Raspberry pi Camera)
-
LCD 패널
-
스위치 버튼
회로도 구성
-
젯슨 나노와 아두이노 우노간의 I2C통신을 위해 USB로 연결하였다.
-
라즈베리 카메라는 젯슨 나노와 연결하여 파이썬으로 제어한다. 파이썬의 OpenCV 4버전 라이브러리를 사용한여 이미지를 캡쳐한다.
-
LCD와 스위치 모두 아두이노로 부터 신호를 주고 받기 위해, 브레드 보드를 거쳐 연결하였다. LCD의 경우
LiquidCrystal_I2C
라이브러리를 이용하여 출력하였으며, 스위치의 경우 실행, 취소 버튼을 다르게 동작하기 위해 두개의 스위치로 구성하였으며, 각각 5번, 4번 핀에 연결하였다. -
LED는 USB 연결형으로 젯슨 나노에 연결하여 파워를 공급하였다.
-
파워 부족으로 전원이 꺼지는 상황이 발생함에 따라, 젯슨, 아두이노에 각각 5V 파워를 공급하였다.
-
잎 촬영을 위한 간이 박스를 구성하였으며, 가로 15cm, 높이 25cm의 공간을 제공한다.
엽면적 산출 방법
엽면적 산출을 위한 방법은 간단한 수학적 개념으로 접근하였다. 고정된 높이(25cm)와 비율(1280 x 1080)로 이미지를 촬영하여 비율을 통일하고, 해당 이미지에서 잎이 차지하는 픽셀수와 실제 잎의 면적간의 비율을 알아내면 다른 잎의 이미지를 가지고도 실제 잎의 면적을 추정할 수 있을 것이라 생각하였다. 해당 수식은 아래와 같다.
-
사진 내에서 잎이 차지하는 픽셀 수와 실제 잎의 면적간의 비율은 1픽셀이 의미하는 실제 면적이 될것이다.
-
이 비율을 이용하여 다른 잎의 이미지로 부터 추출한 잎이 차지하는 면적의 픽셀 수를 구하고, 비율을 곱하면 실제 잎의 면적을 추적할 수 있는 원리이다.
-
이를 위해서는 실제 잎의 면적을 가장 잘 분할한 표본 이미지를 가지고 비율을 구하는 과정이 선행되어야 한다.
PlantCV
PlantCV (Plant Computer Vision)는 식물 연구와 관련된 이미지 분석을 위한 오픈 소스 파이썬 라이브러리이다. 생물학자와 식물학자들이 식물의 성장, 건강, 생리학적 반응 등을 분석할 수 있도록 설계되었다.
-
PlantCV는 식물의 형태학적 특성(예: 크기, 모양, 색상, 질감)을 분석하는 데 사용되는 다양한 도구를 제공한다. 이를 통해 식물의 성장 패턴, 스트레스 반응, 유전적 변이 등을 정량적으로 평가할 수 있다.
-
PlantCV는 표준 RGB 이미지 뿐만 아니라 멀티스펙트럼 및 하이퍼스펙트럼 이미지도 처리할 수 있다. 이는 식물의 건강과 생리학적 상태를 더 세밀하게 분석하는 데 도움이 된다.
-
PlantCV는 실험 데이터를 통합하고 분석하기 위한 기능도 제공한다.
RGB to LAB
다음은 RGB를 LAB 색공간으로 해석하는 코드이다. Red, Green, Blue 세개의 색공간으로 되어있는 이미지를 LAB 색공간 (L: 밝기, a: Red, Green, b:Yellow, Blue)으로 전환하는 코드이다.
-
pcv.rgb2gray_lab
: 입력 이미지를 LAB 색공간으로 변환하고b
채널(파란색-노란색 대비)을 그레이스케일 이미지로 추출한다. -
pcv.threshold.binary
: 추출된b
채널에 대해 이진 임계값을 지정한다. 임계값은 138로 설정되어 있으며,light
객체 타입은 밝은 영역을 포착한다. -
cv2.countNonZero
: 임계값 처리된 이진 이미지에서 0이 아닌(백색 픽셀) 픽셀의 수를 계산한다. 이는 임계값을 넘는 영역의 크기를 나타낸다. -
actual_leaf_area
: 백색 픽셀은 곧 잎의 면적에 해당하는 픽셀을 의미하므로, 해당 픽셀의 개수를 샌 후 위에서 설명한 실제와 이미지 내 픽셀의 수와의 비율을 곱하여 실제 잎의 면적을 산정할 수 있다. -
ser.write()
: 아두이노의 LCD에 해당 엽면적 값을 전송하여 출력한다.
import cv2
import os
from plantcv import plantcv as pcv
now = datetime.now
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
time.sleep(2)
capture_counter = 0
def process_image(filename):
if not os.path.exists('processed_images'):
os.makedirs('processed_images')
img, path, _ = pcv.readimage(filename=os.path.join('capture_images', filename))
b = pcv.rgb2gray_lab(rgb_img=img, channel='b')
b_thresh = pcv.threshold.binary(gray_img=b, threshold=138, object_type='light')
mask_pixel_count = cv2.countNonZero(b_thresh)
processed_filename = os.path.join('processed_images', f'{filename[:-4]}_processed.jpg')
cv2.imwrite(processed_filename, b_thresh)
actual_leaf_area = mask_pixel_count * 0.000508
actual_leaf_area = round(actual_leaf_area, 2)
print("Actual leaf area: ", actual_leaf_area, 'cm^2')
ser.write(str(actual_leaf_area).encode())
return actual_leaf_area
CSI Camera Module
다음은 OpenCV를 이용하여 젯슨 나노와 연결된 라즈베리파이 카메라 (CSI Camera Module)을 제어하는 방법이다.
젯슨 나노는 라즈베리파이와 다르게 picamera
모듈을
사용할 수 없기 때문에 OpenCV를 통해 카메라 제어 파이프라인을 만들어주어야 한다. 다음은 해당 파이프라인을 이용해서 RGB 이미지를 캡쳐하고 저장하는 코드이다.
def gstreamer_pipeline():
return (
"nvarguscamerasrc ! "
"video/x-raw(memory:NVMM), width=(int)1280, height=(int)1080, format=(string)NV12, framerate=(fraction)30/1 ! "
"nvvidconv ! video/x-raw, format=(string)BGRx ! "
"videoconvert ! video/x-raw, format=(string)BGR ! appsink"
)
def capture_image(filename):
if not os.path.exists('capture_images'):
os.makedirs('capture_images')
full_filename = os.path.join('capture_images', filename)
cap = cv2.VideoCapture(gstreamer_pipeline(), cv2.CAP_GSTREAMER)
if cap.isOpened():
ret, frame = cap.read()
if ret:
cv2.imwrite(full_filename, frame)
cap.release()
else:
print("Unable to open the camera")
Arduino 제어
-
아두이노와 젯슨 사이의 I2C 통신을 위한 코드이다. 조건문 내의 스위치 제어문에 따라 첫번째 버튼을 누르면 YES, 두번째 버튼을 누르면 NO가 반환되어 PlantCV 파이썬 코드를 종료한다.
-
String leafArea = Serial.readString()
에서process_image
함수로부터ser.write()
된 엽면적 결과를 받아 LCD에 출력한다.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init();
lcd.backlight();
Serial.begin(9600);
pinMode(5, INPUT_PULLUP); // 5번 핀 ==> 진행 버튼
pinMode(4, INPUT_PULLUP); // 4번 핀 ==> 종료 버튼
}
void loop() {
int buttonState = digitalRead(5);
int exitButtonState = digitalRead(4);
if (buttonState == LOW) {
Serial.println("YES");
while (digitalRead(5) == LOW);
}
if (exitButtonState == LOW) {
Serial.println("NO");
while (digitalRead(4) == LOW);
}
if (Serial.available() > 0) {
String leafArea = Serial.readString();
lcd.setCursor(0, 0);
lcd.print("Leaf Area:");
lcd.setCursor(0, 1);
lcd.print(leafArea + " cm^2");
}
delay(1500);
}
파이썬 제어
-
5번핀에 진행 버튼, 4번핀에 종료 버튼이 있으며 진행 버튼을 누르면 사진을 찍고 result 리스트 안에 결과 값을 넣어준다. 종료 버튼을 누르면 파이썬 코드가 종료한다.
-
종료와 동시에 result 리스트에 들어가있던 결과는 pandas를 이용하여 Dataframe으로 정리되고, csv 파일로 저장된다.
def main():
results = []
image_count = 0
while True:
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8').rstrip()
if line == "YES":
image_count += 1
filename = f"csi_camera_image_{image_count}.jpg"
capture_image(filename)
results.append({'Filename': filename, 'Leaf_Area': process_image(filename)})
elif line == "NO":
break
df = pd.DataFrame(results)
df.to_csv(f'{datetime.now().strftime("%Y%m%d_%H")}_results.csv', index=False)
print("Results saved to 'results.csv'")
print("Exiting program.")
전체적인 flow chart는 다음과 같다.
결과
PlantCV를 통한 엽면적 마스킹 결과는 다음과 같다.
계산한 엽면적을 출력하는 LCD 화면은 다음과 같다.
저장된 csv 결과는 다음과 같다.
성능
-
PlantCV를 이용하여 엽면적을 추출한 결과, 색조의 차이에 의하여 엽면적을 분할하기 때문에 조명과 카메라에 영향을 많이 받았고, 주변을 백색 마스크로 잡는 등 노이즈가 많았다.
-
잎의 크기에 따라, 잎의 말림에 따라 실제 엽면적과의 차이가 발생하였으며, 잎의 일부를 잡지 못하는 등 불안정한 모습을 보이기도 하였다.
-
잎이 클수록 카메라의 왜곡이 발생하여 실제 엽면적과 다소 차이가 발생하기도 하였다.