-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathapp.py
355 lines (283 loc) · 14.5 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
from flask import Flask, jsonify, request, send_file
import torch
from torchvision import models
import os
import sys
import json
from datetime import datetime
from flask_restx import Api, Resource, Namespace, fields
from flask_cors import CORS
# 총이미지 39012
# 고추
# 00_r0, a7_r1 , a8_r1, , b6_r1, b7_r1, b8_r1
# 정상 , 고추탄저병 , 고추흰가루병, 다량원소결핍 N,P,K
# 오이
# 00_r0 , a3_r1 , a3_r2, a3_r3, a4_r0, a4_r1, a4_r2, a4_r3, b1_r1, b1_r2, b1_r3, b6_r1, b8_r1, b7_r1
# 정상 , 오이노균병 , 오이흰가루병 , 냉해피해 , 다량원소결핍 (N,P,K)
# 토마토
# 00_r0, a5_r0, a5_r1, a5_r2, a6_r1, a6_r2, a6_r3, b2_r1, b2_r2, b2_r3, b3_r2, b6_r1, b7_r0, b7_r1, b8_r1
# 정상 , 토마토흰가루병 , 토마토잿빛곰팡이병 , 열과 , 칼슘결핍, 다량원소결핍(N,P,K)
# 딸기
# 00_r0, a1_r1, a1_r2, a1_r3, a2_r1, b1_r1, b1_r2, b6_r1, b7_r1, b8_r1
# 정상 , 딸기잿빛곰팡이병 , 딸기흰가루병,냉해피해 , 다량원소결핍 (N,P,K)
app = Flask(__name__)
# 업로드 파일 최대 크기 설정 (단위: 바이트)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 예: 16MB
disease_code = [
'00', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9',
'a10', 'a11', 'a12', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7',
'b8'
]
disease_name = [
'정상', '딸기잿빛곰팡이병', '딸기흰가루병', '오이노균병', '오이흰가루병', '토마토흰가루병', '토마토잿빛곰팡이병',
'고추탄저병', '고추흰가루병', '파프리카흰가루병', '파프리카잘록병', '시설포도탄저병', '시설포도노균병',
'냉해피해', '열과', '칼슘결핍', '일소피해', '축과병', '다량원소결핍 (N)', '다량원소결핍 (P)', '다량원소결핍 (K)'
]
strawberry_url = {
# 딸기
'정상' : None,
'딸기잿빛곰팡이병' : 'https://www.syngenta.co.kr/ddalgi-jaesbicgompangibyeong-gray-mold',
'딸기흰가루병' : 'https://www.syngenta.co.kr/ddalgi-hyingarubyeong-powdery-mildew',
'냉해피해': "http://www.hortitimes.com/news/articleView.html?idxno=3472",
'다량원소결핍 (N)' : "https://www.yara.kr/crop-nutrition/strawberries/986/671/",
'다량원소결핍 (P)' : "https://www.yara.kr/crop-nutrition/strawberries/986/671/",
'다량원소결핍 (K)' : "https://www.yara.kr/crop-nutrition/strawberries/986/671/",
}
cucumber_url = {
# 오이
'정상' : None,
'오이노균병' : 'https://www.syngenta.co.kr/oi-nogyunbyeongdowny-mildew',
'오이흰가루병' : 'https://www.syngenta.co.kr/oi-hyingarubyeongpowdery-mildew',
'냉해피해' : "http://www.yongamnonghyup.co.kr/index.php?mid=jaje_board&document_srl=2822&listStyle=viewer",
'다량원소결핍 (N)' : "https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=yaronongjang&logNo=140163388831",
'다량원소결핍 (P)' : "https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=yaronongjang&logNo=140163388831",
'다량원소결핍 (K)' : "https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=yaronongjang&logNo=140163388831",
}
tomato_url = {
# 토마토
'정상' : None,
'토마토흰가루병' : "https://farmmorning.com/disease/%ED%9D%B0%EA%B0%80%EB%A3%A8%EB%B3%91?crop=%ED%86%A0%EB%A7%88%ED%86%A0",
'토마토잿빛곰팡이병' : "https://www.syngenta.co.kr/tomato-jaesbicgompangibyeong-gray-mold",
'열과' : "https://www.yara.kr/crop-nutrition/tomato/tomato-health/influencing-tomato-cracking/",
'칼슘결핍' : "https://www.yara.kr/crop-nutrition/tomato/317/ca700/",
'다량원소결핍 (N)' : "https://www.yara.kr/crop-nutrition/tomato/317/250/",
'다량원소결핍 (P)' : "https://www.yara.kr/crop-nutrition/tomato/317/250/",
'다량원소결핍 (K)' : "https://www.yara.kr/crop-nutrition/tomato/317/250/",
}
pepper_url = {
# 고추
'정상' : None,
'고추탄저병' : "https://www.syngenta.co.kr/gocu-tanjeobyeong-anthracnose",
'고추흰가루병' : "https://www.syngenta.co.kr/gocu-hyingarubyeong-powdery-mildew",
'칼슘결핍' : "https://www.yara.kr/crop-nutrition/chili/62/ca92/",
'다량원소결핍 (N)' : "https://www.yara.kr/crop-nutrition/chili/62/104/",
'다량원소결핍 (P)' : "https://www.yara.kr/crop-nutrition/chili/62/104/",
'다량원소결핍 (K)' : "https://www.yara.kr/crop-nutrition/chili/62/104/",
}
disease_risk_code = ["r0", "r1", "r2", "r3"]
disease_risk_name = ["정상", "초기", "중기", "말기"]
input_dir = './img/input/'
output_dir = './img/output/'
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png'}
sys.path.insert(0, './model')
CORS(app, resources={r"/*": {"origins": "*"}})
# api swagger
api = Api(app, version='1.0', title='API 문서', description='Capstone Swagger 문서', doc="/api-docs")
predict_api = api.namespace(name='/', description='작물병해 판단')
predict_request = predict_api.model('Predict 요청', {
'image_file': fields.Raw(description='jpeg, jpg, png형식의 이미지 파일', required=True, example="test.jpeg"),
'crop_type' : fields.String(description='작물의 이름', required=True, example="tomato")
})
contents_fields = api.model('Predict Contents', {
'disease': fields.String(description='병해 이름', example="파프리카흰가루병"),
'disease_url' : fields.String(description='방제 정보 URL', example="https://www.syngenta.co.kr/gocu-tanjeobyeong-anthracnose"),
'percentage': fields.Float(description='확률', example=0.7027),
'crop' : fields.String(description='작물 이름', example="고추"),
'risk' : fields.String(description='병해 피해 정도', example="정상")
})
predict_response = predict_api.model('Predict 응답', {
'contents': fields.List(fields.Nested(contents_fields), description='병해 및 확률 리스트'),
'image_path': fields.String(description='이미지 경로', required=True, example="20231029151432123test3.jpeg"),
'result': fields.Boolean(description='결과', required=True, example=True)
})
predict_fail_response = predict_api.model('Predict 실패 응답', {
'error': fields.String(description='에러 문자', example="tomato, strawberry, cucumber, pepper 중 하나를 입력해주세요."),
'result' : fields.Boolean(description='성공 여부', example="false")
})
image_request = predict_api.model('Predict Image 요청', {
'image_name' : fields.String(description='결과 이미지 이름', example="20231027test.jpeg", required=True)
})
image_response = predict_api.model('Predict Image 응답', {
'image_file': fields.Raw(description='jpeg 형식의 이미지 파일', required=True, example="20231023160713test.jpeg"),
})
image_fail_response = predict_api.model('Predict Image 실패 응답', {
'error': fields.String(description='에러 문자', example="Image not found"),
'result' : fields.Boolean(description='성공 여부', example="false")
})
# 모델 로딩
tomato_model = torch.hub.load('./yolov5', 'custom', path='./model/final/strawberry-finalmodel.pt', source='local')
strawberry_model = torch.hub.load('./yolov5', 'custom', path='./model/final/strawberry-finalmodel.pt', source='local')
cucumber_model = torch.hub.load('./yolov5', 'custom', path='./model/final/cucumber-finalmodel.pt', source='local')
pepper_model = torch.hub.load('./yolov5', 'custom', path='./model/heonju_best.pt', source='local')
# 모델 옵션
def set_model_option(model):
model.max_det = 4 # 객체 탐지 수
model.conf = 0.1 # 신뢰도 값
model.multi_label = True # 라벨링이 여러개가 가능하도록 할지
model.iou = 0.45 # 0.4 ~ 0.5 값
# 이미지 저장
def save_image(file):
file.save(input_dir+ file.filename)
# 결과 작물영어 이름을 한글로 매치
def match_crop_name(en_name):
if en_name == 'strawberry':
return "딸기"
elif en_name == 'cucumber':
return "오이"
elif en_name == 'pepper':
return "고추"
elif en_name == 'tomato':
return "토마토"
else:
return None
# 병해 code를 한글로 매치
def match_disease_name(code):
for index in range(len(disease_code)):
if code == disease_code[index]:
return (disease_name[index])
return None
# 병해 risk code를 한글로 매치
def match_disease_risk_name(code):
for index in range(len(disease_risk_code)):
if code == disease_risk_code[index]:
return (disease_risk_name[index])
return None
# 작물에 따른 방제정보 링크
def match_crop_control_imformation(crop_name, disease_name):
if disease_name == None:
return None
elif crop_name == '딸기':
return strawberry_url[disease_name]
elif crop_name == '오이':
return cucumber_url[disease_name]
elif crop_name == '토마토':
return tomato_url[disease_name]
elif crop_name == '고추':
return pepper_url[disease_name]
# 이미지 고유시간으로 이름변경
def change_img_name(file):
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
changed_name = (timestamp) + file.filename
os.rename(input_dir+ file.filename, input_dir + changed_name)
return changed_name
# 판단결과를 list로 리턴
def add_result_list(result, crop_type):
output = result.pandas().xyxy[0] # 결과 text데이터
crop_result=[]
for idx in output.index:
name_parts = output.loc[idx, 'name'].split('_')
print(name_parts)
confidence = round(output.loc[idx, 'confidence'], 2)
crop = match_crop_name(name_parts[1]) # 고추
disease = match_disease_name(name_parts[0]) # 고추탄저병
print(confidence)
print(crop)
print(disease)
disease_url = match_crop_control_imformation(crop_name=crop, disease_name=disease)
# 선택한 작물에 대한 병이 아닐 때 제외
# if crop_type == crop:
crop_result.append({"crop" : crop, "disease" : disease, "percentage" : confidence, "disease_url" : disease_url})
if not crop_result:
crop_result.append(None)
return crop_result
# 유효한 작물타입(영어)인지 확인
def is_valid_crop_en(crop_type):
if crop_type == 'tomato' or crop_type == 'strawberry' or crop_type == 'cucumber' or crop_type == 'pepper':
return True
else:
return False
# 유효한 작물타입(한국어)인지 확인
def is_valid_crop_kr(crop_type):
if crop_type == '토마토' or crop_type == '딸기' or crop_type == '오이' or crop_type == '고추':
return True
else:
return False
# 허용된 파일 형식인지 확인
def is_allowed_file(input_img):
return '.' in input_img and input_img.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# 업로드된 파일이 정상인지 확인
def is_exist_file(input_img):
return (str(input_img) == "<FileStorage: '' (None)>" or input_img.filename == '')
# 작물 따라서 다른 모델 적용
def run_crop_model(crop_type, train_img, img_size):
print("모델의 type : ", crop_type)
if crop_type == '토마토':
return tomato_model(train_img, size = img_size)
elif crop_type == '딸기':
return strawberry_model(train_img, size = img_size)
elif crop_type == '오이':
return cucumber_model(train_img, size = img_size)
elif crop_type == '고추':
return pepper_model(train_img, size = img_size)
else:
return None
@predict_api.route('/predict')
class Predict(Resource):
@predict_api.doc(params={'image_file': '판단하고자 하는 이미지', 'crop_type' : '작물의 영어이름'})
@predict_api.expect(predict_request)
@predict_api.response(200, 'Success', predict_response)
@predict_api.response(500, 'Fail', predict_fail_response)
def post(self): # 작물 예측
"""작물이름과 작물 이미지를 받아 작물의 병해를 판단합니다."""
data = {}
crop_type = request.form.get('crop_type')
input_img = request.files['image_file']
# 작물 타입
if not crop_type:
return jsonify({"error": "작물정보가 업로드 되지 않았습니다.", "result": False})
# 작물 입력 오류
if not is_valid_crop_kr(crop_type):
return jsonify({"error" : "토마토, 딸기, 오이, 고추 중 하나를 입력해주세요.", "result" : False})
# 파일이 제대로 업로드 되었는지 확인
if is_exist_file(input_img):
return jsonify({"error" : "이미지가 업로드 되지 않았습니다.", "result" : False})
# 파일 형식이 jpeg, jpg, png가 맞는지
if not is_allowed_file(input_img.filename):
return jsonify({"error" : "jpeg, jpg, png형식의 파일을 업로드해주세요.", "result" : False})
# 이미지 저장, 이름 변경
save_image(input_img)
unique_name = change_img_name(input_img)
# 모델 실행
train_img = input_dir + unique_name
result = run_crop_model(crop_type, train_img, 416)
# 모델 결과 이미지
result.print()
result.save(save_dir=output_dir,exist_ok=True)
# 결과값 리스트로 저장
crop_reulst = add_result_list(result, crop_type)
data['result'] = True
data['contents'] = crop_reulst
# 문자열에서 마지막 점 이후의 모든 문자를 .jpeg로 대체
data['image_path'] = unique_name.rsplit('.', 1)[0] + ".jpeg"
return jsonify(data)
@predict_api.doc(params={'image_name': '판단 후 리턴받은 이미지의 이름'})
@predict_api.expect(image_request)
@predict_api.response(200, 'Success', image_response)
@predict_api.response(500, 'Fail', image_fail_response)
def get(self): # 결과 이미지 요청
"""판단 결과사진을 리턴해줍니다."""
try:
image_name = request.args.get('image_name')
image_path = output_dir + image_name
return send_file(image_path, mimetype='image/jpeg')
except FileNotFoundError:
response = jsonify({'error': 'Image not found', 'result' : False})
response.status_code = 404
return response
if __name__ == "__main__":
# 모델 옵션 적용
set_model_option(tomato_model)
set_model_option(cucumber_model)
set_model_option(pepper_model)
set_model_option(strawberry_model)
app.run(host='0.0.0.0', port=5050, debug=True)