def vismask(img): a_img = pcv.rgb2gray_lab(img, channel='a') thresh_a = pcv.threshold.binary(a_img, 124, 255, 'dark') b_img = pcv.rgb2gray_lab(img, channel='b') thresh_b = pcv.threshold.binary(b_img, 127, 255, 'light') mask = pcv.logical_and(thresh_a, thresh_b) mask = pcv.fill(mask, 800) final_mask = pcv.dilate(mask, 2, 1) return final_mask
b = pcv.rgb2gray_lab(rgb_img=img1, channel='b') b_thresh = pcv.threshold.binary(gray_img=b, threshold=135, max_value=255, object_type='light') #Setting threshold continued b_cnt = pcv.threshold.binary(gray_img=b, threshold=135, max_value=255, object_type='light') # In[112]: #Join the blue and yellow binary images bs = pcv.logical_and(bin_img1=s_mblur, bin_img2=b_cnt) masked = pcv.apply_mask(img=img1, mask=bs, mask_color='white') #identify objects obj2 = id_objects, obj_hierarchy = pcv.find_objects(img=masked, mask=bs) #Define Range of Intrest # Inputs: # img - RGB or grayscale image to plot the ROI on # x - The x-coordinate of the upper left corner of the rectangle # y - The y-coordinate of the upper left corner of the rectangle # h - The height of the rectangle # w - The width of the rectangle roi1, roi_hierarchy = pcv.roi.rectangle(img=img1, x=75, y=60, h=20, w=20)
def segment_sort(skel_img, objects, mask=None, first_stem=True): """ Calculate segment curvature as defined by the ratio between geodesic and euclidean distance Inputs: skel_img = Skeletonized image objects = List of contours mask = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask. first_stem = (Optional) if True, then the first (bottom) segment always gets classified as stem Returns: labeled_img = Segmented debugging image with lengths labeled secondary_objects = List of secondary segments (leaf) primary_objects = List of primary objects (stem) :param skel_img: numpy.ndarray :param objects: list :param mask: numpy.ndarray :param first_stem: bool :return secondary_objects: list :return other_objects: list """ # Store debug debug = params.debug params.debug = None secondary_objects = [] primary_objects = [] if mask is None: labeled_img = np.zeros(skel_img.shape[:2], np.uint8) else: labeled_img = mask.copy() tips_img = find_tips(skel_img) tips_img = dilate(tips_img, 3, 1) # Loop through segment contours for i, cnt in enumerate(objects): segment_plot = np.zeros(skel_img.shape[:2], np.uint8) cv2.drawContours(segment_plot, objects, i, 255, 1, lineType=8) is_leaf = False overlap_img = logical_and(segment_plot, tips_img) # The first contour is the base, and while it contains a tip, it isn't a leaf if i == 0 and first_stem: primary_objects.append(cnt) # Sort segments else: if np.sum(overlap_img) > 0: secondary_objects.append(cnt) is_leaf = True else: primary_objects.append(cnt) # Plot segments where green segments are leaf objects and fuschia are other objects labeled_img = cv2.cvtColor(labeled_img, cv2.COLOR_GRAY2RGB) for i, cnt in enumerate(primary_objects): cv2.drawContours(labeled_img, primary_objects, i, (255, 0, 255), params.line_thickness, lineType=8) for i, cnt in enumerate(secondary_objects): cv2.drawContours(labeled_img, secondary_objects, i, (0, 255, 0), params.line_thickness, lineType=8) # Reset debug mode params.debug = debug # Auto-increment device params.device += 1 if params.debug == 'print': print_image(labeled_img, os.path.join(params.debug_outdir, str(params.device) + '_sorted_segments.png')) elif params.debug == 'plot': plot_image(labeled_img) return secondary_objects, primary_objects
sftp = client.open_sftp() with sftp.open('/plantdb/ftp-2019/{dbname}/{path}'.format(dbname=dbname,path=path)) as f: file_size = f.stat().st_size f.prefetch(file_size) img = cv2.imdecode(np.frombuffer(f.read(file_size), np.uint8), 1) # Convert RGB to HSV and extract the value channel s = pcv.rgb2gray_hsv(rgb_img=img, channel='v') # Threshold the saturation image removing highs and lows and join s_thresh_1 = pcv.threshold.binary(gray_img=s, threshold=10, max_value=255, object_type='light') s_thresh_2 = pcv.threshold.binary(gray_img=s, threshold=245, max_value=255, object_type='dark') s_thresh = pcv.logical_and(bin_img1=s_thresh_1, bin_img2=s_thresh_2) # Median Blur s_mblur = pcv.median_blur(gray_img=s_thresh, ksize=5) # Convert RGB to LAB and extract the Blue channel b = pcv.rgb2gray_lab(rgb_img=img, channel='b') # Threshold the blue image b_cnt = pcv.threshold.binary(gray_img=b, threshold=128, max_value=255, object_type='light') # Fill small objects b_fill = pcv.fill(b_cnt, 10)
def segment_insertion_angle(skel_img, segmented_img, leaf_objects, stem_objects, size): """ Find leaf insertion angles in degrees of skeleton segments. Fit a linear regression line to the stem. Use `size` pixels on the portion of leaf next to the stem find a linear regression line, and calculate angle between the two lines per leaf object. Inputs: skel_img = Skeletonized image segmented_img = Segmented image to plot slope lines and intersection angles on leaf_objects = List of leaf segments stem_objects = List of stem segments size = Size of inner leaf used to calculate slope lines Returns: labeled_img = Debugging image with angles labeled :param skel_img: numpy.ndarray :param segmented_img: numpy.ndarray :param leaf_objects: list :param stem_objects: list :param size: int :return labeled_img: numpy.ndarray """ # Store debug debug = params.debug params.debug = None rows,cols = segmented_img.shape[:2] labeled_img = segmented_img.copy() segment_slopes = [] insertion_segments = [] insertion_hierarchies = [] intersection_angles = [] label_coord_x = [] label_coord_y = [] valid_segment = [] # Create a list of tip tuples to use for sorting tips = find_tips(skel_img) tips = dilate(tips, 3, 1) tip_objects, tip_hierarchies = find_objects(tips, tips) tip_tuples = [] for i, cnt in enumerate(tip_objects): tip_tuples.append((cnt[0][0][0], cnt[0][0][1])) rand_color = color_palette(len(leaf_objects)) for i, cnt in enumerate(leaf_objects): # Draw leaf objects find_segment_tangents = np.zeros(segmented_img.shape[:2], np.uint8) cv2.drawContours(find_segment_tangents, leaf_objects, i, 255, 1, lineType=8) # Prune back ends of leaves pruned_segment = _iterative_prune(find_segment_tangents, size) # Segment ends are the portions pruned off segment_ends = find_segment_tangents - pruned_segment segment_end_obj, segment_end_hierarchy = find_objects(segment_ends, segment_ends) is_insertion_segment = [] if not len(segment_end_obj) == 2: print("Size too large, contour with ID#", i, "got pruned away completely.") else: # The contour can have insertion angle calculated valid_segment.append(cnt) # Determine if a segment is leaf end or leaf insertion segment for j, obj in enumerate(segment_end_obj): segment_plot = np.zeros(segmented_img.shape[:2], np.uint8) cv2.drawContours(segment_plot, obj, -1, 255, 1, lineType=8) overlap_img = logical_and(segment_plot, tips) # If none of the tips are within a segment_end then it's an insertion segment if np.sum(overlap_img) == 0: insertion_segments.append(segment_end_obj[j]) insertion_hierarchies.append(segment_end_hierarchy[0][j]) # Store coordinates for labels label_coord_x.append(leaf_objects[i][0][0][0]) label_coord_y.append(leaf_objects[i][0][0][1]) rand_color = color_palette(len(valid_segment)) for i, cnt in enumerate(valid_segment): cv2.drawContours(labeled_img, valid_segment, i, rand_color[i], params.line_thickness, lineType=8) # Plot stem segments stem_img = np.zeros(segmented_img.shape[:2], np.uint8) cv2.drawContours(stem_img, stem_objects, -1, 255, 2, lineType=8) branch_pts = find_branch_pts(skel_img) stem_img = stem_img + branch_pts stem_img = closing(stem_img) combined_stem, combined_stem_hier = find_objects(stem_img, stem_img) # Make sure stem objects are a single contour loop_count=0 while len(combined_stem) > 1 and loop_count<50: loop_count += 1 stem_img = dilate(stem_img, 2, 1) stem_img = closing(stem_img) combined_stem, combined_stem_hier = find_objects(stem_img, stem_img) if loop_count == 50: fatal_error('Unable to combine stem objects.') # Find slope of the stem [vx, vy, x, y] = cv2.fitLine(combined_stem[0], cv2.DIST_L2, 0, 0.01, 0.01) stem_slope = -vy / vx stem_slope = stem_slope[0] lefty = int((-x * vy / vx) + y) righty = int(((cols - x) * vy / vx) + y) cv2.line(labeled_img, (cols - 1, righty), (0, lefty), (150, 150, 150), 3) rand_color = color_palette(len(insertion_segments)) for t, segment in enumerate(insertion_segments): # Find line fit to each segment [vx, vy, x, y] = cv2.fitLine(segment, cv2.DIST_L2, 0, 0.01, 0.01) slope = -vy / vx left_list = int((-x * vy / vx) + y) right_list = int(((cols - x) * vy / vx) + y) segment_slopes.append(slope[0]) # Draw slope lines if possible if slope > 1000000 or slope < -1000000: print("Slope of contour with ID#", t, "is", slope, "and cannot be plotted.") else: cv2.line(labeled_img, (cols - 1, right_list), (0, left_list), rand_color[t], 1) # Store intersection angles between insertion segment and stem line intersection_angle = _slope_to_intesect_angle(slope[0], stem_slope) # Function measures clockwise but we want the acute angle between stem and leaf insertion if intersection_angle > 90: intersection_angle = 180 - intersection_angle intersection_angles.append(intersection_angle) segment_ids = [] for i, cnt in enumerate(insertion_segments): # Label slope lines w = label_coord_x[i] h = label_coord_y[i] text = "{:.2f}".format(intersection_angles[i]) cv2.putText(img=labeled_img, text=text, org=(w, h), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=params.text_size, color=(150, 150, 150), thickness=params.text_thickness) segment_label = "ID" + str(i) segment_ids.append(i) outputs.add_observation(variable='segment_insertion_angle', trait='segment insertion angle', method='plantcv.plantcv.morphology.segment_insertion_angle', scale='degrees', datatype=list, value=intersection_angles, label=segment_ids) # Reset debug mode params.debug = debug # Auto-increment device params.device += 1 if params.debug == 'print': print_image(labeled_img, os.path.join(params.debug_outdir, str(params.device) + '_segment_insertion_angles.png')) elif params.debug == 'plot': plot_image(labeled_img) return labeled_img
def main(): # create options object for argument parsing args = options() # set device device = 0 # set debug pcv.params.debug = args.debug outfile = False if args.writeimg: outfile = os.path.join(args.outdir, os.path.basename(args.image)[:-4]) # read in image img, path, filename = pcv.readimage(filename=args.image, debug=args.debug) # read in a background image for each zoom level config_file = open(args.bkg, 'r') config = json.load(config_file) config_file.close() if "z1500" in args.image: bkg_image = config["z1500"] elif "z2500" in args.image: bkg_image = config["z2500"] else: pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image)) bkg, bkg_path, bkg_filename = pcv.readimage(filename=bkg_image, debug=args.debug) # Detect edges in the background image device, bkg_sat = pcv.rgb2gray_hsv(img=bkg, channel="s", device=device, debug=args.debug) device += 1 bkg_edges = feature.canny(bkg_sat) if args.debug == "print": pcv.print_image(img=bkg_edges, filename=str(device) + '_background_edges.png') elif args.debug == "plot": pcv.plot_image(img=bkg_edges, cmap="gray") # Close background edge contours bkg_edges_closed = ndi.binary_closing(bkg_edges) device += 1 if args.debug == "print": pcv.print_image(img=bkg_edges_closed, filename=str(device) + '_closed_background_edges.png') elif args.debug == "plot": pcv.plot_image(img=bkg_edges_closed, cmap="gray") # Fill in closed contours in background bkg_fill_contours = ndi.binary_fill_holes(bkg_edges_closed) device += 1 if args.debug == "print": pcv.print_image(img=bkg_fill_contours, filename=str(device) + '_filled_background_edges.png') elif args.debug == "plot": pcv.plot_image(img=bkg_fill_contours, cmap="gray") # Naive Bayes image classification/segmentation device, mask = pcv.naive_bayes_classifier(img=img, pdf_file=args.pdf, device=device, debug=args.debug) # Do a light cleaning of the plant mask to remove small objects cleaned = morphology.remove_small_objects(mask["plant"].astype(bool), 2) device += 1 if args.debug == "print": pcv.print_image(img=cleaned, filename=str(device) + '_cleaned_mask.png') elif args.debug == "plot": pcv.plot_image(img=cleaned, cmap="gray") # Convert the input image to a saturation channel grayscale image device, sat = pcv.rgb2gray_hsv(img=img, channel="s", device=device, debug=args.debug) # Detect edges in the saturation image edges = feature.canny(sat) device += 1 if args.debug == "print": pcv.print_image(img=edges, filename=str(device) + '_plant_edges.png') elif args.debug == "plot": pcv.plot_image(img=edges, cmap="gray") # Combine pixels that are in both foreground edges and the filled background edges device, combined_bkg = pcv.logical_and(img1=edges.astype(np.uint8) * 255, img2=bkg_fill_contours.astype(np.uint8) * 255, device=device, debug=args.debug) # Remove background pixels from the foreground edges device += 1 filtered = np.copy(edges) filtered[np.where(combined_bkg == 255)] = False if args.debug == "print": pcv.print_image(img=filtered, filename=str(device) + '_filtered_edges.png') elif args.debug == "plot": pcv.plot_image(img=filtered, cmap="gray") # Combine the cleaned naive Bayes mask and the filtered foreground edges device += 1 combined = cleaned + filtered if args.debug == "print": pcv.print_image(img=combined, filename=str(device) + '_combined_foreground.png') elif args.debug == "plot": pcv.plot_image(img=combined, cmap="gray") # Close off broken edges and other incomplete contours device += 1 closed_features = ndi.binary_closing(combined, structure=np.ones((3, 3))) if args.debug == "print": pcv.print_image(img=closed_features, filename=str(device) + '_closed_features.png') elif args.debug == "plot": pcv.plot_image(img=closed_features, cmap="gray") # Fill in holes in contours # device += 1 # fill_contours = ndi.binary_fill_holes(closed_features) # if args.debug == "print": # pcv.print_image(img=fill_contours, filename=str(device) + '_filled_contours.png') # elif args.debug == "plot": # pcv.plot_image(img=fill_contours, cmap="gray") # Use median blur to break horizontal and vertical thin edges (pot edges) device += 1 blurred_img = ndi.median_filter(closed_features.astype(np.uint8) * 255, (3, 1)) blurred_img = ndi.median_filter(blurred_img, (1, 3)) # Remove small objects left behind by blurring cleaned2 = morphology.remove_small_objects(blurred_img.astype(bool), 200) if args.debug == "print": pcv.print_image(img=cleaned2, filename=str(device) + '_cleaned_by_median_blur.png') elif args.debug == "plot": pcv.plot_image(img=cleaned2, cmap="gray") # Define region of interest based on camera zoom level for masking the naive Bayes classified image # if "z1500" in args.image: # h = 1000 # elif "z2500" in args.image: # h = 1050 # else: # pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image)) # roi, roi_hierarchy = pcv.roi.rectangle(x=300, y=150, w=1850, h=h, img=img) # Mask the classified image to remove noisy areas prior to finding contours # side_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8) # cv2.drawContours(side_mask, roi, -1, (255), -1) # device, masked_img = pcv.apply_mask(img=cv2.merge([mask["plant"], mask["plant"], mask["plant"]]), mask=side_mask, # mask_color="black", device=device, debug=args.debug) # Convert the masked image back to grayscale # masked_img = masked_img[:, :, 0] # Close off contours at the base of the plant # if "z1500" in args.image: # pt1 = (1100, 1118) # pt2 = (1340, 1120) # elif "z2500" in args.image: # pt1 = (1020, 1162) # pt2 = (1390, 1166) # else: # pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image)) # masked_img = cv2.rectangle(np.copy(masked_img), pt1, pt2, (255), -1) # closed_mask = ndi.binary_closing(masked_img.astype(bool), iterations=3) # Find objects in the masked naive Bayes mask # device, objects, obj_hierarchy = pcv.find_objects(img=img, mask=np.copy(masked_img), device=device, # debug=args.debug) # objects, obj_hierarchy = cv2.findContours(np.copy(closed_mask.astype(np.uint8) * 255), cv2.RETR_CCOMP, # cv2.CHAIN_APPROX_NONE)[-2:] # Clean up the combined plant edges/mask image by removing filled in gaps/holes # device += 1 # cleaned3 = np.copy(cleaned2) # cleaned3 = cleaned3.astype(np.uint8) * 255 # # Loop over the contours from the naive Bayes mask # for c, contour in enumerate(objects): # # Calculate the area of each contour # # area = cv2.contourArea(contour) # # If the contour is a hole (i.e. it has no children and it has a parent) # # And it is not a small hole in a leaf that was not classified # if obj_hierarchy[0][c][2] == -1 and obj_hierarchy[0][c][3] > -1: # # Then fill in the contour (hole) black on the cleaned mask # cv2.drawContours(cleaned3, objects, c, (0), -1, hierarchy=obj_hierarchy) # if args.debug == "print": # pcv.print_image(img=cleaned3, filename=str(device) + '_gaps_removed.png') # elif args.debug == "plot": # pcv.plot_image(img=cleaned3, cmap="gray") # Find contours using the cleaned mask device, contours, contour_hierarchy = pcv.find_objects(img=img, mask=np.copy(cleaned2.astype(np.uint8)), device=device, debug=args.debug) # Define region of interest based on camera zoom level for contour filtering if "z1500" in args.image: h = 940 elif "z2500" in args.image: h = 980 else: pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image)) roi, roi_hierarchy = pcv.roi.rectangle(x=300, y=150, w=1850, h=h, img=img) # Filter contours in the region of interest device, roi_objects, hierarchy, kept_mask, obj_area = pcv.roi_objects(img=img, roi_type='partial', roi_contour=roi, roi_hierarchy=roi_hierarchy, object_contour=contours, obj_hierarchy=contour_hierarchy, device=device, debug=args.debug) # Analyze only images with plants present if len(roi_objects) > 0: # Object combine kept objects device, plant_contour, plant_mask = pcv.object_composition(img=img, contours=roi_objects, hierarchy=hierarchy, device=device, debug=args.debug) if args.writeimg: # Save the plant mask if requested pcv.print_image(img=plant_mask, filename=outfile + "_mask.png") # Find shape properties, output shape image device, shape_header, shape_data, shape_img = pcv.analyze_object(img=img, imgname=args.image, obj=plant_contour, mask=plant_mask, device=device, debug=args.debug, filename=outfile) # Set the boundary line based on the camera zoom level if "z1500" in args.image: line_position = 930 elif "z2500" in args.image: line_position = 885 else: pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image)) # Shape properties relative to user boundary line device, boundary_header, boundary_data, boundary_img = pcv.analyze_bound_horizontal(img=img, obj=plant_contour, mask=plant_mask, line_position=line_position, device=device, debug=args.debug, filename=outfile) # Determine color properties: Histograms, Color Slices and Pseudocolored Images, # output color analyzed images device, color_header, color_data, color_img = pcv.analyze_color(img=img, imgname=args.image, mask=plant_mask, bins=256, device=device, debug=args.debug, hist_plot_type=None, pseudo_channel="v", pseudo_bkg="img", resolution=300, filename=outfile) # Output shape and color data result = open(args.result, "a") result.write('\t'.join(map(str, shape_header)) + "\n") result.write('\t'.join(map(str, shape_data)) + "\n") for row in shape_img: result.write('\t'.join(map(str, row)) + "\n") result.write('\t'.join(map(str, color_header)) + "\n") result.write('\t'.join(map(str, color_data)) + "\n") result.write('\t'.join(map(str, boundary_header)) + "\n") result.write('\t'.join(map(str, boundary_data)) + "\n") result.write('\t'.join(map(str, boundary_img)) + "\n") for row in color_img: result.write('\t'.join(map(str, row)) + "\n") result.close()
def main(): # Get options args = options() # Set variables pcv.params.debug = args.debug # Replace the hard-coded debug with the debug flag img_file = args.image # Replace the hard-coded input image with image flag ############### Image read-in ################ # Read target image img, path, filename = pcv.readimage(filename = img_file, mode = "rgb") ############### Find scale and crop ################ # find colour card in the image to be analysed df, start, space = pcv.transform.find_color_card(rgb_img = img) if int(start[0]) < 2000: img = imutils.rotate_bound(img, -90) rotated = 1 df, start, space = pcv.transform.find_color_card(rgb_img = img) else: rotated = 0 #if img.shape[0] > 6000: # rotated = 1 #else: rotated = 0 img_mask = pcv.transform.create_color_card_mask(rgb_img = img, radius = 10, start_coord = start, spacing = space, ncols = 4, nrows = 6) # write the spacing of the colour card to file as size marker with open(r'size_marker.csv', 'a') as f: writer = csv.writer(f) writer.writerow([filename, space[0]]) # define a bounding rectangle around the colour card x_cc,y_cc,w_cc,h_cc = cv2.boundingRect(img_mask) x_cc = int(round(x_cc - 0.3 * w_cc)) y_cc = int(round(y_cc - 0.3 * h_cc)) h_cc = int(round(h_cc * 1.6)) w_cc = int(round(w_cc * 1.6)) # crop out colour card start_point = (x_cc, y_cc) end_point = (x_cc+w_cc, y_cc+h_cc) colour = (0, 0, 0) thickness = -1 crop_img = cv2.rectangle(img, start_point, end_point, colour, thickness) ############### Fine segmentation ################ # Threshold A and B channels of the LAB colourspace and the Hue channel of the HSV colourspace l_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[70,0,0], upper_thresh=[255,255,255], channel='LAB') a_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[0,0,0], upper_thresh=[255,145,255], channel='LAB') b_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[0,0,123], upper_thresh=[255,255,255], channel='LAB') h_thresh_low, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[0,0,0], upper_thresh=[130,255,255], channel='HSV') h_thresh_high, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[150,0,0], upper_thresh=[255,255,255], channel='HSV') h_thresh = pcv.logical_or(h_thresh_low, h_thresh_high) # Join the thresholded images to keep only consensus pixels ab = pcv.logical_and(b_thresh, a_thresh) lab = pcv.logical_and(l_thresh, ab) labh = pcv.logical_and(lab, h_thresh) # Fill small objects labh_clean = pcv.fill(labh, 200) # Dilate to close broken borders #labh_dilated = pcv.dilate(labh_clean, 4, 1) labh_dilated = labh_clean # Apply mask (for VIS images, mask_color=white) masked = pcv.apply_mask(crop_img, labh_dilated, "white") # Identify objects contours, hierarchy = pcv.find_objects(crop_img, labh_dilated) # Define ROI if rotated == 1: roi_height = 3000 roi_lwr_bound = y_cc + (h_cc * 0.5) - roi_height roi_contour, roi_hierarchy= pcv.roi.rectangle(x=1000, y=roi_lwr_bound, h=roi_height, w=2000, img=crop_img) else: roi_height = 1500 roi_lwr_bound = y_cc + (h_cc * 0.5) - roi_height roi_contour, roi_hierarchy= pcv.roi.rectangle(x=2000, y=roi_lwr_bound, h=roi_height, w=2000, img=crop_img) # Decide which objects to keep filtered_contours, filtered_hierarchy, mask, area = pcv.roi_objects(img = crop_img, roi_type = 'partial', roi_contour = roi_contour, roi_hierarchy = roi_hierarchy, object_contour = contours, obj_hierarchy = hierarchy) # Combine kept objects obj, mask = pcv.object_composition(crop_img, filtered_contours, filtered_hierarchy) ############### Analysis ################ outfile=False if args.writeimg==True: outfile_black=args.outdir+"/"+filename+"_black" outfile_white=args.outdir+"/"+filename+"_white" outfile_analysed=args.outdir+"/"+filename+"_analysed" # analyse shape shape_img = pcv.analyze_object(crop_img, obj, mask) pcv.print_image(shape_img, outfile_analysed) # analyse colour colour_img = pcv.analyze_color(crop_img, mask, 'hsv') # keep the segmented plant for visualisation picture_mask = pcv.apply_mask(crop_img, mask, "black") pcv.print_image(picture_mask, outfile_black) picture_mask = pcv.apply_mask(crop_img, mask, "white") pcv.print_image(picture_mask, outfile_white) # print out results pcv.outputs.save_results(filename=args.result, outformat="json")
def roi_objects(img, roi_contour, roi_hierarchy, object_contour, obj_hierarchy, roi_type="partial"): """Find objects partially inside a region of interest or cut objects to the ROI. Inputs: img = RGB or grayscale image data for plotting roi_contour = contour of roi, output from "View and Adjust ROI" function roi_hierarchy = contour of roi, output from "View and Adjust ROI" function object_contour = contours of objects, output from "find_objects" function obj_hierarchy = hierarchy of objects, output from "find_objects" function roi_type = 'cutto', 'partial' (for partially inside, default), or 'largest' (keep only the largest contour) Returns: kept_cnt = kept contours hierarchy = contour hierarchy list mask = mask image obj_area = total object pixel area :param img: numpy.ndarray :param roi_type: str :param roi_contour: list :param roi_hierarchy: numpy.ndarray :param object_contour: list :param obj_hierarchy: numpy.ndarray :return kept_cnt: list :return hierarchy: numpy.ndarray :return mask: numpy.ndarray :return obj_area: int """ # Store debug debug = params.debug params.debug = None params.device += 1 # Create an empty grayscale (black) image the same dimensions as the input image mask = np.zeros(np.shape(img)[:2], dtype=np.uint8) cv2.drawContours(mask, object_contour, -1, (255), -1, lineType=8, hierarchy=obj_hierarchy) # Create a mask of the filled in ROI roi_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8) roi_points = np.vstack(roi_contour[0]) cv2.fillPoly(roi_mask, [roi_points], (255)) # Make a copy of the input image for plotting ori_img = np.copy(img) # If the reference image is grayscale convert it to color if len(np.shape(ori_img)) == 2: ori_img = cv2.cvtColor(ori_img, cv2.COLOR_GRAY2BGR) # Allows user to find all objects that are completely inside or overlapping with ROI if roi_type.upper() == 'PARTIAL' or roi_type.upper() == 'LARGEST': # Filter contours outside of the region of interest for c, cnt in enumerate(object_contour): filtering_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8) cv2.fillPoly(filtering_mask, [np.vstack(object_contour[c])], (255)) overlap_img = logical_and(filtering_mask, roi_mask) # Delete contours that do not overlap at all with the ROI if np.sum(overlap_img) == 0: cv2.drawContours(mask, object_contour, c, (0), -1, lineType=8, hierarchy=obj_hierarchy) # Find the kept contours and area kept_cnt, kept_hierarchy = cv2.findContours(np.copy(mask), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:] obj_area = cv2.countNonZero(mask) # Find the largest contour if roi_type is set to 'largest' if roi_type.upper() == 'LARGEST': # Print warning statement about this feature print("Warning: roi_type='largest' will only return the largest contour and its immediate children. Other " "subcontours will be dropped.") # Find the index of the largest contour in the list of contours largest_area = 0 index = 0 for c, cnt in enumerate(kept_cnt): area = len(cnt) if area > largest_area: largest_area = area index = c # Store the largest contour as a list largest_cnt = [kept_cnt[index]] # Store the hierarchy of the largest contour into a list largest_hierarchy = [kept_hierarchy[0][index]] # Iterate through contours to find children of the largest contour for i, khi in enumerate(kept_hierarchy[0]): if khi[3] == index: # is the parent equal to the largest contour? largest_hierarchy.append(khi) largest_cnt.append(kept_cnt[i]) # Make the kept hierarchies into an array so that cv2 can use it largest_hierarchy = np.array([largest_hierarchy]) # Overwrite mask so it only has the largest contour mask = np.zeros(np.shape(img)[:2], dtype=np.uint8) for i, cnt in enumerate(largest_cnt): # print(cnt) if i == 0: color = (255) else: color = (0) # print(i) cv2.drawContours(mask, largest_cnt, i, color, -1, lineType=8, hierarchy=largest_hierarchy, maxLevel=0) # Refind contours and hierarchy from new mask so they are easier to work with downstream kept_cnt, kept_hierarchy = cv2.findContours(np.copy(mask), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:] # Compute object area obj_area = cv2.countNonZero(mask) cv2.drawContours(ori_img, kept_cnt, -1, (0, 255, 0), -1, lineType=8, hierarchy=kept_hierarchy) cv2.drawContours(ori_img, roi_contour, -1, (255, 0, 0), params.line_thickness, lineType=8, hierarchy=roi_hierarchy) # Allows user to cut objects to the ROI (all objects completely outside ROI will not be kept) elif roi_type.upper() == 'CUTTO': background1 = np.zeros(np.shape(img)[:2], dtype=np.uint8) background2 = np.zeros(np.shape(img)[:2], dtype=np.uint8) cv2.drawContours(background1, object_contour, -1, (255), -1, lineType=8, hierarchy=obj_hierarchy) roi_points = np.vstack(roi_contour[0]) cv2.fillPoly(background2, [roi_points], (255)) mask = cv2.multiply(background1, background2) obj_area = cv2.countNonZero(mask) kept_cnt, kept_hierarchy = cv2.findContours(np.copy(mask), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:] cv2.drawContours(ori_img, kept_cnt, -1, (0, 255, 0), -1, lineType=8, hierarchy=kept_hierarchy) cv2.drawContours(ori_img, roi_contour, -1, (255, 0, 0), params.line_thickness, lineType=8, hierarchy=roi_hierarchy) else: # Reset debug mode params.debug = debug fatal_error('ROI Type ' + str(roi_type) + ' is not "cutto", "largest", or "partial"!') # Reset debug mode params.debug = debug if params.debug == 'print': print_image(ori_img, os.path.join(params.debug_outdir, str(params.device) + '_obj_on_img.png')) print_image(mask, os.path.join(params.debug_outdir, str(params.device) + '_roi_mask.png')) elif params.debug == 'plot': plot_image(ori_img) plot_image(mask, cmap='gray') return kept_cnt, kept_hierarchy, mask, obj_area
def get_coordinates(self): if self.debug: pcv.params.debug = 'print' # set debug mode pcv.params.debug_outdir = './images/DracaenaVision/' # set output directory blue_threshold = pcv.threshold.binary(gray_img=self.blue, threshold=50, max_value=255, object_type='dark') pcv.apply_mask(self.colour_image, mask=blue_threshold, mask_color='white') # Calculate moments of binary image moments = cv.moments(blue_threshold) # Calculate x,y coordinate of center self.centre_x = int(moments["m10"] / moments["m00"]) self.centre_y = int(moments["m01"] / moments["m00"]) # Put text and highlight the center cv.circle(self.colour_image, (self.centre_x, self.centre_y), 5, (255, 255, 255), -1) cv.putText(self.colour_image, "Centre", (self.centre_x - 25, self.centre_y - 25), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2) red_threshold = pcv.threshold.binary(gray_img=self.red, threshold=70, max_value=255, object_type='light') # Erode/Dilate the red threshold to remove noise red_threshold = pcv.erode(red_threshold, ksize=5, i=1) red_threshold = pcv.dilate(red_threshold, ksize=5, i=2) # Create mask of area that is of interest mask_pts = np.array( [[576, 415], [800, 420], [1105, 630], [720, 685], [285, 590]], np.int32).reshape((-1, 1, 2)) area_of_interest = np.zeros(self.colour_image.shape[:2], np.uint8) cv.fillPoly(area_of_interest, [mask_pts], (255, 255, 255)) red_threshold_in_area_of_interest = pcv.logical_and( area_of_interest, red_threshold) pcv.apply_mask(img=self.colour_image, mask=red_threshold_in_area_of_interest, mask_color='black') params = cv.SimpleBlobDetector_Params() # Unused filters params.filterByCircularity = False params.filterByConvexity = False params.filterByInertia = False # Area filter params.filterByArea = True params.maxArea = 50000 params.minArea = 100 # Colour filter params.filterByColor = True params.blobColor = 255 # Misc options params.minDistBetweenBlobs = 100 blob_detector = cv.SimpleBlobDetector_create(params) keypoints = blob_detector.detect(red_threshold, mask=area_of_interest) keypoints.sort(reverse=True, key=lambda kp: kp.size) now = datetime.now().strftime('%d-%m %H %M %S') im_with_keypoints = cv.drawKeypoints( self.colour_image, keypoints, np.array([]), (0, 0, 255), cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) self.detection_image = im_with_keypoints.copy() if self.full_screen: self.detection_image = cv.resize(self.detection_image, (1920, 1080)) cv.imshow(self.window_name, self.detection_image) cv.waitKey(1) cv.polylines(im_with_keypoints, [mask_pts], True, (0, 255, 0), thickness=3) cv.imwrite('./images/DracaenaVision/{} detections.png'.format(now), im_with_keypoints) coordinates = [] for keypoint in keypoints: x = keypoint.pt[0] y = keypoint.pt[1] # For each X and Y determine depth (z-coordinate) depth = -1 step_size_to_centre_x = (self.centre_x - x) / 100 step_size_to_centre_y = (self.centre_y - y) / 100 for i in range(21): delta_x = step_size_to_centre_x * i delta_y = step_size_to_centre_y * i new_x = round(x + delta_x) new_y = round(y + delta_y) depth = self.depth_array[new_y, new_x] if depth < 0.55: print('Depth found with delta ({},{})'.format( delta_x, delta_y)) print('Depth of: {} at {} X, {} Y'.format( depth, new_x, new_y)) break z = depth if depth < 0.55 or depth <= 0 else 0.55 # Determine the angle at which the tool should be held towards the plant angle = 180 - math.degrees( math.atan2(y - self.centre_y, x - self.centre_x)) coordinate = [x, y, z, angle] coordinates.append(coordinate) return coordinates
def main(): # Get options args = options() # Set variables pcv.params.debug = args.debug # Replace the hard-coded debug with the debug flag pcv.params.debug_outdir = args.outdir # set output directory ### Main pipeline ### # Read image (readimage mode defaults to native but if image is RGBA then specify mode='rgb') img, path, filename = pcv.readimage(args.image, mode='rgb') # Read reference image for colour correction (currently unused) #ref_img, ref_path, ref_filename = pcv.readimage( # "/home/leonard/Dropbox/2020-01_LAC_phenotyping/images/top/renamed/20200128_2.jpg", # mode="rgb") # Find colour cards #df, start, space = pcv.transform.find_color_card(rgb_img=ref_img) #ref_mask = pcv.transform.create_color_card_mask(rgb_img=ref_img, radius=10, start_coord=start, spacing=space, ncols=4, nrows=6) df, start, space = pcv.transform.find_color_card(rgb_img=img) img_mask = pcv.transform.create_color_card_mask(rgb_img=img, radius=10, start_coord=start, spacing=space, ncols=4, nrows=6) output_directory = "." # Correct colour (currently unused) #target_matrix, source_matrix, transformation_matrix, corrected_img = pcv.transform.correct_color(ref_img, ref_mask, img, img_mask, output_directory) # Check that the colour correction worked (source~target should be strictly linear) #pcv.transform.quick_color_check(source_matrix = source_matrix, target_matrix = target_matrix, num_chips = 24) # Write the spacing of the colour card to file as size marker with open(os.path.join(path, 'output/size_marker_trays.csv'), 'a') as f: writer = csv.writer(f) writer.writerow([filename, space[0]]) ### Crop tray ### # Define a bounding rectangle around the colour card x_cc, y_cc, w_cc, h_cc = cv2.boundingRect(img_mask) x_cc = int(round(x_cc - 0.3 * w_cc)) y_cc = int(round(y_cc - 0.3 * h_cc)) h_cc = int(round(h_cc * 1.6)) w_cc = int(round(w_cc * 1.6)) # Crop out colour card start_point = (x_cc, y_cc) end_point = (x_cc + w_cc, y_cc + h_cc) colour = (0, 0, 0) thickness = -1 card_crop_img = cv2.rectangle(img, start_point, end_point, colour, thickness) # Convert RGB to HSV and extract the value channel v = pcv.rgb2gray_hsv(card_crop_img, "v") # Threshold the value image v_thresh = pcv.threshold.binary( v, 100, 255, "light" ) # start threshold at 150 with bright corner-markers, 100 without # Fill out bright imperfections (siliques and other dirt on the background) v_thresh = pcv.fill( v_thresh, 100) # fill at 500 with bright corner-markers, 100 without # Create bounding rectangle around the tray x, y, w, h = cv2.boundingRect(v_thresh) # Crop image to tray #crop_img = card_crop_img[y:y+h, x:x+int(w - (w * 0.03))] # crop extra 3% from right because of tray labels crop_img = card_crop_img[y:y + h, x:x + w] # crop symmetrically # Save cropped image for quality control pcv.print_image(crop_img, filename=path + "/output/" + "cropped" + filename + ".png") ### Threshold plants ### # Threshold the green-magenta, blue, and hue channels a_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[0, 0, 0], upper_thresh=[255, 108, 255], channel='LAB') b_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[0, 0, 135], upper_thresh=[255, 255, 255], channel='LAB') h_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[35, 0, 0], upper_thresh=[70, 255, 255], channel='HSV') # Join the thresholds (AND) ab = pcv.logical_and(b_thresh, a_thresh) abh = pcv.logical_and(ab, h_thresh) # Fill small objects depending on expected plant size based on DPG (make sure to take the correct file suffix jpg/JPG/jpeg...) match = re.search("(\d+).(\d)\.jpg$", filename) if int(match.group(1)) < 10: abh_clean = pcv.fill(abh, 50) print("50") elif int(match.group(1)) < 15: abh_clean = pcv.fill(abh, 200) print("200") else: abh_clean = pcv.fill(abh, 500) print("500") # Dilate to close broken borders abh_dilated = pcv.dilate(abh_clean, 3, 1) # Close holes # abh_fill = pcv.fill_holes(abh_dilated) # silly -- removed abh_fill = abh_dilated # Apply mask (for VIS images, mask_color=white) masked = pcv.apply_mask(crop_img, abh_fill, "white") # Save masked image for quality control pcv.print_image(masked, filename=path + "/output/" + "masked" + filename + ".png") ### Filter and group contours ### # Identify objects id_objects, obj_hierarchy = pcv.find_objects(crop_img, abh_fill) # Create bounding box with margins to avoid border artifacts roi_y = 0 + crop_img.shape[0] * 0.05 roi_x = 0 + crop_img.shape[0] * 0.05 roi_h = crop_img.shape[0] - (crop_img.shape[0] * 0.1) roi_w = crop_img.shape[1] - (crop_img.shape[0] * 0.1) roi_contour, roi_hierarchy = pcv.roi.rectangle(crop_img, roi_y, roi_x, roi_h, roi_w) # Keep all objects in the bounding box roi_objects, roi_obj_hierarchy, kept_mask, obj_area = pcv.roi_objects( img=crop_img, roi_type='partial', roi_contour=roi_contour, roi_hierarchy=roi_hierarchy, object_contour=id_objects, obj_hierarchy=obj_hierarchy) # Cluster the objects by plant clusters, contours, hierarchies = pcv.cluster_contours( crop_img, roi_objects, roi_obj_hierarchy, 3, 5) # Split image into single plants out = args.outdir #output_path, imgs, masks = pcv.cluster_contour_splitimg(crop_img, # clusters, # contours, # hierarchies, # out, # file = filename) ### Analysis ### # Approximate the position of the top left plant as grid start coord_y = int( round(((crop_img.shape[0] / 3) * 0.5) + (crop_img.shape[0] * 0.025))) coord_x = int( round(((crop_img.shape[1] / 5) * 0.5) + (crop_img.shape[1] * 0.025))) # Set the ROI spacing relative to image dimensions spc_y = int((round(crop_img.shape[0] - (crop_img.shape[0] * 0.05)) / 3)) spc_x = int((round(crop_img.shape[1] - (crop_img.shape[1] * 0.05)) / 5)) # Set the ROI radius relative to image width if int(match.group(1)) < 16: r = int(round(crop_img.shape[1] / 12.5)) else: r = int(round(crop_img.shape[1] / 20)) # Make a grid of ROIs at the expected positions of plants # This allows for gaps due to dead/not germinated plants, without messing up the plant numbering imgs, masks = pcv.roi.multi(img=crop_img, nrows=3, ncols=5, coord=(coord_x, coord_y), radius=r, spacing=(spc_x, spc_y)) # Loop through the ROIs in the grid for i in range(0, len(imgs)): # Find objects within the ROI filtered_contours, filtered_hierarchy, filtered_mask, filtered_area = pcv.roi_objects( img=crop_img, roi_type="partial", roi_contour=imgs[i], roi_hierarchy=masks[i], object_contour=id_objects, obj_hierarchy=obj_hierarchy) # Continue only if not empty if len(filtered_contours) > 0: # Combine objects within each ROI plant_contour, plant_mask = pcv.object_composition( img=crop_img, contours=filtered_contours, hierarchy=filtered_hierarchy) # Analyse the shape of each plant analysis_images = pcv.analyze_object(img=crop_img, obj=plant_contour, mask=plant_mask) pcv.print_image(analysis_images, filename=path + "/output/" + filename + "_" + str(i) + "_analysed.png") # Determine color properties color_images = pcv.analyze_color(crop_img, plant_mask, "hsv") # Watershed plant area to count leaves (computationally intensive, use when needed) #watershed_images = pcv.watershed_segmentation(crop_img, plant_mask, 15) # Print out a .json file with the analysis data for the plant pcv.outputs.save_results(filename=path + "/" + filename + "_" + str(i) + '.json') # Clear the measurements stored globally into the Ouptuts class pcv.outputs.clear()
def main(): # Get options args = options() pcv.params.debug = args.debug # set debug mode pcv.params.debug_outdir = args.outdir # set output directory # Read image img, path, filename = pcv.readimage(filename=args.image) # Convert RGB to HSV and extract the value channel s = pcv.rgb2gray_hsv(rgb_img=img, channel='v') # Threshold the saturation image removing highs and lows and join s_thresh_1 = pcv.threshold.binary(gray_img=s, threshold=10, max_value=255, object_type='light') s_thresh_2 = pcv.threshold.binary(gray_img=s, threshold=245, max_value=255, object_type='dark') s_thresh = pcv.logical_and(bin_img1=s_thresh_1, bin_img2=s_thresh_2) # Median Blur s_mblur = pcv.median_blur(gray_img=s_thresh, ksize=5) # Convert RGB to LAB and extract the Blue channel b = pcv.rgb2gray_lab(rgb_img=img, channel='b') # Threshold the blue image b_cnt = pcv.threshold.binary(gray_img=b, threshold=128, max_value=255, object_type='light') # Fill small objects b_fill = pcv.fill(b_cnt, 10) # Join the thresholded saturation and blue-yellow images bs = pcv.logical_or(bin_img1=s_mblur, bin_img2=b_fill) # Apply Mask (for VIS images, mask_color=white) masked = pcv.apply_mask(rgb_img=img, mask=bs, mask_color='white') # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels masked_a = pcv.rgb2gray_lab(rgb_img=masked, channel='a') masked_b = pcv.rgb2gray_lab(rgb_img=masked, channel='b') # Threshold the green-magenta and blue images maskeda_thresh = pcv.threshold.binary(gray_img=masked_a, threshold=127, max_value=255, object_type='dark') maskedb_thresh = pcv.threshold.binary(gray_img=masked_b, threshold=128, max_value=255, object_type='light') # Join the thresholded saturation and blue-yellow images (OR) ab = pcv.logical_or(bin_img1=maskeda_thresh, bin_img2=maskedb_thresh) # Fill small objects ab_fill = pcv.fill(bin_img=ab, size=200) # Apply mask (for VIS images, mask_color=white) masked2 = pcv.apply_mask(rgb_img=masked, mask=ab_fill, mask_color='white') # Identify objects id_objects, obj_hierarchy = pcv.find_objects(img=masked2, mask=ab_fill) # Define ROI w = 1600 h = 1200 pot = 230 #340 roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=(2472 - w) / 2, y=(3296 - h - pot), h=h, w=w) # Decide which objects to keep roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects( img=img, roi_contour=roi1, roi_hierarchy=roi_hierarchy, object_contour=id_objects, obj_hierarchy=obj_hierarchy, roi_type='partial') # Object combine kept objects obj, mask = pcv.object_composition(img=img, contours=roi_objects, hierarchy=hierarchy3) # Find shape properties, output shape image (optional) shape_imgs = pcv.analyze_object(img=img, obj=obj, mask=mask) if args.writeimg == True: pcv.print_image(img=shape_imgs, filename="{}_shape.png".format(args.result[:-5])) pcv.print_image(img=masked2, filename="{}_obj_on_img.png".format(args.result[:-5])) # Shape properties relative to user boundary line (optional) #boundary_img1 = pcv.analyze_bound_horizontal(img=img, obj=obj, mask=mask, line_position=1680) # Determine color properties: Histograms, Color Slices, output color analyzed histogram (optional) color_histogram = pcv.analyze_color(rgb_img=img, mask=kept_mask, hist_plot_type='all') # Pseudocolor the grayscale image pseudocolored_img = pcv.visualize.pseudocolor(gray_img=s, mask=kept_mask, cmap='jet') # Write shape and color data to results file pcv.print_results(filename=args.result)
def main(): # Get options args = options() pcv.params.debug = args.debug # set debug mode pcv.params.debug_outdir = args.outdir # set output directory # Read metadata with open(args.metadata, 'r', encoding='utf-8') as f: md = json.load(f) camera_label = md['camera_label'] # Read image img, path, filename = pcv.readimage(filename=args.image) # Convert RGB to HSV and extract the value channel s = pcv.rgb2gray_hsv(rgb_img=img, channel='v') # Threshold the saturation image removing highs and lows and join s_thresh_1 = pcv.threshold.binary(gray_img=s, threshold=10, max_value=255, object_type='light') s_thresh_2 = pcv.threshold.binary(gray_img=s, threshold=245, max_value=255, object_type='dark') s_thresh = pcv.logical_and(bin_img1=s_thresh_1, bin_img2=s_thresh_2) # Median Blur s_mblur = pcv.median_blur(gray_img=s_thresh, ksize=5) # Convert RGB to LAB and extract the Blue channel b = pcv.rgb2gray_lab(rgb_img=img, channel='b') # Threshold the blue image b_cnt = pcv.threshold.binary(gray_img=b, threshold=128, max_value=255, object_type='light') # Fill small objects b_fill = pcv.fill(b_cnt, 10) # Join the thresholded saturation and blue-yellow images bs = pcv.logical_or(bin_img1=s_mblur, bin_img2=b_fill) # Apply Mask (for VIS images, mask_color=white) masked = pcv.apply_mask(rgb_img=img, mask=bs, mask_color='white') # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels # Threshold the green-magenta and blue images masked_a = pcv.rgb2gray_lab(rgb_img=masked, channel='a') maskeda_thresh = pcv.threshold.binary(gray_img=masked_a, threshold=127, max_value=255, object_type='dark') # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels # Threshold the green-magenta and blue images masked_b = pcv.rgb2gray_lab(rgb_img=masked, channel='b') maskedb_thresh = pcv.threshold.binary(gray_img=masked_b, threshold=128, max_value=255, object_type='light') # Join the thresholded saturation and blue-yellow images (OR) ab = pcv.logical_or(bin_img1=maskeda_thresh, bin_img2=maskedb_thresh) # Fill small objects ab_fill = pcv.fill(bin_img=ab, size=200) # Apply mask (for VIS images, mask_color=white) masked2 = pcv.apply_mask(rgb_img=masked, mask=ab_fill, mask_color='white') # Identify objects id_objects, obj_hierarchy = pcv.find_objects(img=masked2, mask=ab_fill) # Define ROI W = 2472 H = 3296 if "far" in camera_label: # SIDE FAR w = 1600 h = 1200 pot = 230 #340 roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=(W - w) / 2, y=(H - h - pot), h=h, w=w) elif "lower" in camera_label: # SIDE LOWER w = 800 h = 2400 pot = 340 roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=1000 - w / 2, y=(H - h - pot), h=h, w=w) elif "upper" in camera_label: # SIDE UPPER w = 600 h = 800 pot = 550 roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=1400 - w / 2, y=(H - h - pot), h=h, w=w) elif "top" in camera_label: # TOP w = 450 h = 450 roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=(H - h) / 2, y=(W - w) / 2, h=h, w=w) # Decide which objects to keep roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects( img=img, roi_contour=roi1, roi_hierarchy=roi_hierarchy, object_contour=id_objects, obj_hierarchy=obj_hierarchy, roi_type='partial') # Object combine kept objects obj, mask = pcv.object_composition(img=img, contours=roi_objects, hierarchy=hierarchy3) #TODO: Update for plantCV metadata import for key in md.keys(): if str(md[key]).isdigit(): pcv.outputs.add_observation(variable=key, trait=key, method='', scale='', datatype=int, value=md[key], label='') else: pcv.outputs.add_observation(variable=key, trait=key, method='', scale='', datatype=str, value=md[key], label='') if obj is not None: # Find shape properties, output shape image (optional) shape_imgs = pcv.analyze_object(img=img, obj=obj, mask=mask) # Shape properties relative to user boundary line (optional) #boundary_img1 = pcv.analyze_bound_horizontal(img=img, obj=obj, mask=mask, line_position=1680) # Determine color properties: Histograms, Color Slices, output color analyzed histogram (optional) color_histogram = pcv.analyze_color(rgb_img=img, mask=kept_mask, hist_plot_type='all') # Pseudocolor the grayscale image #pseudocolored_img = pcv.visualize.pseudocolor(gray_img=s, mask=kept_mask, cmap='jet') #print(pcv.outputs.images) if args.writeimg == True: for idx, item in enumerate(pcv.outputs.images[0]): pcv.print_image(item, "{}_{}.png".format(args.result[:-5], idx)) # Write shape and color data to results file pcv.print_results(filename=args.result)
def test(true_positive_file, test_parameters): saturation_lower_tresh = test_parameters[0] saturation_higher_tresh = test_parameters[1] hue_lower_tresh = test_parameters[2] hue_higher_tresh = test_parameters[3] value_lower_tresh = test_parameters[4] value_higher_tresh = test_parameters[5] l_lower_thresh = test_parameters[6] a_lower_thresh_1 = test_parameters[7] a_lower_thresh_2 = test_parameters[8] b_lower_thresh_1 = test_parameters[9] b_higher_thresh_1 = test_parameters[10] b_lower_thresh_2 = test_parameters[11] b_higher_thresh_2 = test_parameters[12] HSV_blur_k = test_parameters[13] LAB_blur_k = test_parameters[14] b_fill_k = test_parameters[15] LAB_fill_k = test_parameters[16] class args: #image = "C:\\Users\\RensD\\OneDrive\\studie\\Master\\The_big_project\\top_perspective\\0214_2018-03-07 08.55 - 26_cam9.png" image = true_positive_file outdir = "C:\\Users\\RensD\\OneDrive\\studie\\Master\\The_big_project\\top_perspective\\output" debug = debug_setting result = "results.txt" # Get options pcv.params.debug = args.debug #set debug mode pcv.params.debug_outdir = args.outdir #set output directory pcv.params.debug = args.debug # set debug mode pcv.params.debug_outdir = args.outdir # set output directory # Read image img, path, filename = pcv.readimage(filename=args.image) #______________________________________________________________#### BEGIN HSV COLORSPACE WORKFLOW ### # Convert RGB to HSV and extract the saturation channel # Threshold the saturation s = pcv.rgb2gray_hsv(rgb_img=img, channel='s') s_thresh, maskeds_image = pcv.threshold.custom_range( rgb_img=s, lower_thresh=[saturation_lower_tresh], upper_thresh=[saturation_higher_tresh], channel='gray') # Threshold the hue h = pcv.rgb2gray_hsv(rgb_img=img, channel='h') h_thresh, maskedh_image = pcv.threshold.custom_range( rgb_img=h, lower_thresh=[hue_lower_tresh], upper_thresh=[hue_higher_tresh], channel='gray') v = pcv.rgb2gray_hsv(rgb_img=img, channel='v') v_thresh, maskedv_image = pcv.threshold.custom_range( rgb_img=v, lower_thresh=[value_lower_tresh], upper_thresh=[value_higher_tresh], channel='gray') # Join saturation, Hue and Value sh = pcv.logical_and(bin_img1=s_thresh, bin_img2=h_thresh) hsv = pcv.logical_and(bin_img1=sh, bin_img2=v_thresh) # Median Blur s_mblur = pcv.median_blur(gray_img=hsv, ksize=HSV_blur_k) #s_cnt = pcv.median_blur(gray_img=s_thresh, ksize=5) #______________________________________________________________#### END HSV COLORSPACE WORKFLOW ### #______________________________________________________________#### BEGIN CIELAB COLORSPACE WORKFLOW ### # Convert RGB to LAB and extract the Blue channel b = pcv.rgb2gray_lab(rgb_img=img, channel='b') # Threshold the blue image b_thresh = pcv.threshold.binary(gray_img=b, threshold=b_lower_thresh_1, max_value=b_higher_thresh_1, object_type='light') b_cnt = pcv.threshold.binary(gray_img=b, threshold=b_lower_thresh_1, max_value=b_higher_thresh_1, object_type='light') # Fill small objects b_cnt = pcv.fill( b_thresh, b_fill_k ) # If the fill step fails because of small objects try a smaller fill, else abort. # Join the thresholded saturation and blue-yellow images bs = pcv.logical_and(bin_img1=s_mblur, bin_img2=b_cnt) # CHANGER OR TO AND # Apply Mask (for VIS images, mask_color=white) masked = pcv.apply_mask(rgb_img=img, mask=bs, mask_color='white') #Now the background is filtered away. Next step is to capture the plant. # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels masked_l = pcv.rgb2gray_lab(rgb_img=masked, channel='l') masked_a = pcv.rgb2gray_lab(rgb_img=masked, channel='a') masked_b = pcv.rgb2gray_lab(rgb_img=masked, channel='b') # Threshold the green-magenta and blue images maskedl_thresh, maskedl_image = pcv.threshold.custom_range( rgb_img=masked_l, lower_thresh=[120], upper_thresh=[247], channel='gray') maskeda_thresh, maskeda_image = pcv.threshold.custom_range( rgb_img=masked_a, lower_thresh=[0], upper_thresh=[114], channel='gray') maskedb_thresh, maskedb_image = pcv.threshold.custom_range( rgb_img=masked_b, lower_thresh=[130], upper_thresh=[240], channel='gray') # Join the thresholded saturation and blue-yellow images (OR) ab1 = pcv.logical_and(bin_img1=maskeda_thresh, bin_img2=maskedb_thresh) ab = pcv.logical_and(bin_img1=maskedl_thresh, bin_img2=ab1) # Fill small objects ab_fill = pcv.median_blur(gray_img=ab, ksize=LAB_blur_k) ab_fill = pcv.fill(bin_img=ab_fill, size=LAB_fill_k) # Apply mask (for VIS images, mask_color=white) masked2 = pcv.apply_mask(rgb_img=masked, mask=ab_fill, mask_color='white') # Identify objects id_objects, obj_hierarchy = pcv.find_objects(img=masked2, mask=ab_fill) # Define ROI roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=0, y=0, h=960, w=1280) # Decide which objects to keep roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects( img=img, roi_contour=roi1, roi_hierarchy=roi_hierarchy, object_contour=id_objects, obj_hierarchy=obj_hierarchy, roi_type='partial') # Object combine kept objects obj, mask = pcv.object_composition(img=img, contours=roi_objects, hierarchy=hierarchy3) if use_mask == True: return (mask) else: masked2 = pcv.apply_mask(rgb_img=masked, mask=mask, mask_color='white') return (masked2)
for i,g in enumerate(grps): print(i,g) # read fluor data imgfns = get_filenames(basepath,g) Fo, Fm, FsLss, FmpLss = get_fluor(imgfns, os.path.join(basepath,g)) # make mask # filters.try_all_threshold(Fm_gray) ty = filters.threshold_otsu(Fm) print(ty) mask = pcv.threshold.binary(Fm, ty, 255, 'light') mask = pcv.fill(mask, 100) _,rect,_,_ = pcv.rectangle_mask(mask,(300,180),(750,600),'white') mask = pcv.logical_and(mask,rect) # compute apply mask and compute paramters out_flt = np.zeros_like(Fm, dtype='float') # fv/fm Fv = np.subtract(Fm,Fo, out=out_flt.copy(), where=mask>0) FvFm = np.divide(Fv,Fm, out=out_flt.copy(), where=np.logical_and(mask>0, Fo>0)) fvfm_fig = pcv.visualize.pseudocolor(FvFm, mask=mask, cmap='viridis', max_value=1) outfn = g+'_fvfm.png' fvfm_fig.set_size_inches(6, 6, forward=False) fvfm_fig.savefig(os.path.join(outdir,outfn), bbox_inches='tight',