|
import argparse |
|
import os |
|
import platform |
|
import sys |
|
import streamlit as st |
|
import torch |
|
import torch.backends.cudnn as cudnn |
|
import numpy as np |
|
from pathlib import Path |
|
from PIL import Image |
|
|
|
from torchvision import transforms, models |
|
|
|
|
|
|
|
FILE = Path(__file__).resolve() |
|
ROOT = FILE.parents[0] |
|
if str(ROOT) not in sys.path: |
|
sys.path.append(str(ROOT)) |
|
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) |
|
|
|
from models.common import DetectMultiBackend |
|
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams |
|
from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2, |
|
increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh) |
|
from utils.plots import Annotator, colors, save_one_box |
|
from utils.torch_utils import select_device, time_sync |
|
|
|
weights="appledd-yolov5s-800.pb" |
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
|
|
|
|
|
|
import torch.nn as nn |
|
import torch.nn.functional as F |
|
class NaturalSceneClassification(nn.Module): |
|
def __init__(self): |
|
super().__init__() |
|
self.network = torch.hub.load('pytorch/vision:v0.10.0', 'mobilenet_v2', pretrained=True) |
|
|
|
self.network.fc = nn.Sequential(nn.Linear(2048, 512), |
|
nn.ReLU(), |
|
nn.Dropout(0.2), |
|
nn.Linear(512, 10), |
|
nn.Softmax(dim=1)) |
|
|
|
|
|
def forward(self, xb): |
|
return self.network(xb) |
|
|
|
def training_step(self, batch): |
|
images, labels = batch |
|
images, labels = images.to(device), labels.to(device) |
|
out = self(images) |
|
loss = F.cross_entropy(out, labels) |
|
return loss |
|
|
|
def validation_step(self, batch): |
|
images, labels = batch |
|
images, labels = images.to(device), labels.to(device) |
|
out = self(images) |
|
loss = F.cross_entropy(out, labels) |
|
acc = accuracy(out, labels) |
|
return {'val_loss': loss.detach(), 'val_acc': acc} |
|
|
|
def validation_epoch_end(self, outputs): |
|
batch_losses = [x['val_loss'] for x in outputs] |
|
epoch_loss = torch.stack(batch_losses).mean() |
|
batch_accs = [x['val_acc'] for x in outputs] |
|
epoch_acc = torch.stack(batch_accs).mean() |
|
return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()} |
|
|
|
def epoch_end(self, epoch, result): |
|
print("Epoch [{}], train_loss: {:.4f}, val_loss: {:.4f}, val_acc: {:.4f}".format( |
|
epoch, result['train_loss'], result['val_loss'], result['val_acc'])) |
|
|
|
|
|
|
|
|
|
def increase_contrast(image): |
|
if isinstance(image, Image.Image): |
|
|
|
image = np.array(image) |
|
|
|
if not isinstance(image, np.ndarray): |
|
raise ValueError("Input must be a valid numpy array") |
|
|
|
|
|
if len(image.shape) == 3: |
|
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
|
|
|
|
|
min_val = image.min() |
|
max_val = image.max() |
|
|
|
if min_val == max_val: |
|
return image |
|
|
|
|
|
contrast_stretched = cv2.convertScaleAbs(image, alpha=255.0 / (max_val - min_val), beta=-min_val) |
|
|
|
return contrast_stretched |
|
|
|
def reduce_noise(image, kernel_size=(3, 3)): |
|
|
|
blurred = cv2.GaussianBlur(image, kernel_size, 0) |
|
|
|
return blurred |
|
@torch.no_grad() |
|
def run( |
|
weights=ROOT / 'yolov5s.pt', |
|
source=ROOT / 'data/images', |
|
data=ROOT / 'data.yaml', |
|
imgsz=(640, 640), |
|
conf_thres=0.25, |
|
iou_thres=0.45, |
|
max_det=1000, |
|
device='', |
|
view_img=False, |
|
save_txt=False, |
|
save_conf=False, |
|
save_crop=False, |
|
nosave=False, |
|
classes=None, |
|
agnostic_nms=False, |
|
augment=False, |
|
visualize=False, |
|
update=False, |
|
project=ROOT / 'runs/detect', |
|
name='exp', |
|
exist_ok=True, |
|
line_thickness=2, |
|
hide_labels=False, |
|
hide_conf=False, |
|
half=False, |
|
dnn=False, |
|
|
|
upl_image: np.ndarray=None, |
|
|
|
): |
|
|
|
source = str(source) |
|
save_img = not nosave and not source.endswith('.txt') |
|
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS) |
|
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://')) |
|
webcam = source.isnumeric() or source.endswith('.txt') or (is_url and not is_file) |
|
if is_url and is_file: |
|
source = check_file(source) |
|
|
|
|
|
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) |
|
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
device = select_device(device) |
|
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half) |
|
stride, names, pt = model.stride, model.names, model.pt |
|
imgsz = check_img_size(imgsz, s=stride) |
|
|
|
|
|
if webcam: |
|
view_img = check_imshow() |
|
cudnn.benchmark = True |
|
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt) |
|
bs = len(dataset) |
|
else: |
|
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt) |
|
bs = 1 |
|
vid_path, vid_writer = [None] * bs, [None] * bs |
|
|
|
|
|
model.warmup(imgsz=(1 if pt else bs, 3, *imgsz)) |
|
seen, windows, dt = 0, [], [0.0, 0.0, 0.0] |
|
for path, im, im0s, vid_cap, s in dataset: |
|
t1 = time_sync() |
|
|
|
im = torch.from_numpy(im).to(device) |
|
im = im.half() if model.fp16 else im.float() |
|
im /= 255 |
|
if len(im.shape) == 3: |
|
im = im[None] |
|
t2 = time_sync() |
|
dt[0] += t2 - t1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False |
|
pred = model(im, augment=augment, visualize=visualize) |
|
t3 = time_sync() |
|
dt[1] += t3 - t2 |
|
|
|
|
|
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) |
|
dt[2] += time_sync() - t3 |
|
|
|
|
|
|
|
|
|
|
|
for i, det in enumerate(pred): |
|
seen += 1 |
|
if webcam: |
|
p, im0, frame = path[i], im0s[i].copy(), dataset.count |
|
s += f'{i}: ' |
|
else: |
|
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0) |
|
|
|
p = Path(p) |
|
save_path = str(save_dir / p.name) |
|
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') |
|
s += '%gx%g ' % im.shape[2:] |
|
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] |
|
imc = im0.copy() if save_crop else im0 |
|
annotator = Annotator(im0, line_width=line_thickness, example=str(names)) |
|
if len(det): |
|
|
|
det[:, :4] = scale_coords(im.shape[2:], det[:, :4], im0.shape).round() |
|
|
|
|
|
for c in det[:, -1].unique(): |
|
n = (det[:, -1] == c).sum() |
|
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " |
|
|
|
|
|
for *xyxy, conf, cls in reversed(det): |
|
if save_txt: |
|
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() |
|
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) |
|
with open(f'{txt_path}.txt', 'a') as f: |
|
f.write(('%g ' * len(line)).rstrip() % line + '\n') |
|
|
|
if save_img or save_crop or view_img: |
|
c = int(cls) |
|
label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}') |
|
annotator.box_label(xyxy, label, color=colors(c, True)) |
|
if save_crop: |
|
save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True) |
|
|
|
|
|
im0 = annotator.result() |
|
if view_img: |
|
if platform.system() == 'Linux' and p not in windows: |
|
windows.append(p) |
|
cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO) |
|
cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0]) |
|
cv2.imshow(str(p), im0) |
|
cv2.waitKey(1) |
|
|
|
|
|
if save_img: |
|
if dataset.mode == 'image': |
|
|
|
print("Save") |
|
else: |
|
if vid_path[i] != save_path: |
|
vid_path[i] = save_path |
|
if isinstance(vid_writer[i], cv2.VideoWriter): |
|
vid_writer[i].release() |
|
if vid_cap: |
|
fps = vid_cap.get(cv2.CAP_PROP_FPS) |
|
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) |
|
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) |
|
else: |
|
fps, w, h = 30, im0.shape[1], im0.shape[0] |
|
save_path = str(Path(save_path).with_suffix('.mp4')) |
|
vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h)) |
|
vid_writer[i].write(im0) |
|
|
|
|
|
LOGGER.info(f'{s}Done. ({t3 - t2:.3f}s)') |
|
|
|
|
|
t = tuple(x / seen * 1E3 for x in dt) |
|
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t) |
|
if save_txt or save_img: |
|
s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '' |
|
LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}") |
|
if update: |
|
strip_optimizer(weights[0]) |
|
im0 = cv2.cvtColor(im0, cv2.COLOR_BGR2RGB) |
|
return im0 |
|
|
|
|
|
def parse_opt(): |
|
parser = argparse.ArgumentParser() |
|
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)') |
|
parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam') |
|
parser.add_argument('--data', type=str, default=ROOT / 'data.yaml', help='(optional) dataset.yaml path') |
|
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[800], help='inference size h,w') |
|
parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold') |
|
parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold') |
|
parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image') |
|
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') |
|
parser.add_argument('--view-img', action='store_true', help='show results') |
|
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') |
|
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels') |
|
parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes') |
|
parser.add_argument('--nosave', action='store_true', help='do not save images/videos') |
|
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3') |
|
parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') |
|
parser.add_argument('--augment', action='store_true', help='augmented inference') |
|
parser.add_argument('--visualize', action='store_true', help='visualize features') |
|
parser.add_argument('--update', action='store_true', help='update all models') |
|
parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name') |
|
parser.add_argument('--name', default='exp', help='save results to project/name') |
|
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') |
|
parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)') |
|
parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels') |
|
parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences') |
|
parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference') |
|
parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference') |
|
opt = parser.parse_args() |
|
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 |
|
print_args(vars(opt)) |
|
return opt |
|
|
|
def classify(model,img): |
|
img = img.to(device) |
|
prediction = model(img) |
|
sc, preds = torch.max(prediction, dim = 1) |
|
return sc[0].item(),preds[0].item() |
|
|
|
|
|
def main(opt,model,labels): |
|
|
|
|
|
st.image("logo.jpg", caption="") |
|
st.title("#Welcome to Deep Diagnosis") |
|
|
|
st.markdown( |
|
""" |
|
This app allows you to detect different apple diseases from leaf images. |
|
1) Scab |
|
2) Alternaria |
|
3) MLB |
|
4) Mossaic |
|
5) Powdery Mildew |
|
6) Necrosis |
|
""" |
|
) |
|
url="https://www.sciencedirect.com/science/article/abs/pii/S0168169922004100" |
|
st.write("Link to the research paper: [link] (%s)" %url) |
|
|
|
st.write("This app allows you to provide an image, and one of the most advanced Object Detection algorithms available will try to classify it for you. Upload your data to get started!") |
|
|
|
with st.sidebar: |
|
|
|
uploaded_file = st.file_uploader("Choose an Image", type=["png","jpg","jpeg"]) |
|
return_types = st.multiselect("Select Return Type", ["Image", "Labels"], ["Image", "Labels"]) |
|
|
|
if not uploaded_file: |
|
file_name = "sample.jpg" |
|
st.write("Upload apple leaf image to detect diseases") |
|
st.image("sample.jpg", caption='Sample Image',width=400) |
|
|
|
else: |
|
file_name = uploaded_file.name |
|
|
|
|
|
|
|
file_details = {"filename":uploaded_file.name, "filetype":uploaded_file.type,"filesize":uploaded_file.size} |
|
|
|
with open(file_name,"wb") as f: |
|
f.write((uploaded_file).getbuffer()) |
|
|
|
img = Image.open(uploaded_file) |
|
if img.format.lower() != "jpeg" or img.format.lower() !="jpg" : |
|
|
|
img = img.convert("RGB") |
|
temp_jpeg_file = "temp_image.jpg" |
|
img.save(temp_jpeg_file, "JPEG") |
|
|
|
img.close() |
|
|
|
|
|
img = Image.open(temp_jpeg_file) |
|
|
|
|
|
|
|
img = transforms.Resize((360,360))(img) |
|
img = transforms.ToTensor()(img) |
|
img = img.unsqueeze(0).to(device) |
|
res=classify(model,img) |
|
|
|
|
|
lb=labels[res[1]] |
|
sc=res[0] |
|
st.write(lb+" "+str(sc)) |
|
if(lb=="noleaf"): |
|
st.write("Invalid image! Try Some other image") |
|
elif(lb=="healthy"): |
|
st.write("Looks healthy to me") |
|
elif(lb=="demaged"): |
|
st.write("No recognizable disease found") |
|
else: |
|
if(sc>7): |
|
final_result = run(weights,file_name) |
|
st.image(final_result, caption='Diseases Detected', width=400) |
|
|
|
else: |
|
st.write("No disease detected") |
|
|
|
|
|
|
|
os.remove(file_name) |
|
|
|
os.remove(temp_jpeg_file) |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
opt = parse_opt() |
|
model=NaturalSceneClassification() |
|
model=torch.load("mobilenetv2-apple-10-class-pytorch.pth",map_location=device ) |
|
model.eval() |
|
|
|
labels=[] |
|
with open("labels.txt") as file: |
|
for line in file: |
|
line = line.strip() |
|
labels.append(line) |
|
main(opt,model,labels) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|