Last active
November 6, 2018 22:21
-
-
Save bhive01/492101637c0c81f4bbc889901857a377 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Brandon Hurr | |
| # Assumptions | |
| # 1. Color card is Xrite Passport | |
| # 2. Card is not mirrored | |
| # 3. Only color calibration half of card is visible | |
| # 4. When remove edges is selected at command line, the card is not at the edge of the image. | |
| # 3. The scale factor is based upon median width of found objects (contours) that should be xrite squares, but could not be | |
| # could be problematic with lots of squares objects in image other than color card | |
| import argparse | |
| import sys | |
| import os.path | |
| import trans #pip install trans | |
| import time | |
| import cv2 | |
| import math | |
| import pandas as pd | |
| import numpy as np | |
| import statsmodels.formula.api as smf | |
| from itertools import product | |
| from skimage import color, restoration | |
| from scipy.signal import convolve2d as conv2 | |
| from scipy.spatial.distance import squareform, pdist | |
| from sklearn import linear_model | |
| from play2_funcs import * | |
| # known passport card values | |
| xritePassportR = [105.0,193.0,90.0,74.0,132.0,115.0,204.0,67.0,199.0,77.0,144.0,201.0,33.0,60.0,176.0,205.0,183.0,47.0,235.0,206.0,167.0,120.0,75.0,34.0] | |
| xritePassportG = [68.0,142.0,124.0,104.0,135.0,202.0,105.0,93.0,73.0,52.0,196.0,161.0,58.0,160.0,44.0,198.0,82.0,143.0,235.0,206.0,169.0,120.0,76.0,36.0] | |
| xritePassportB = [54.0,122.0,170.0,46.0,186.0,188.0,17.0,195.0,87.0,109.0,35.0,11.0,168.0,62.0,42.0,16.0,156.0,192.0,231.0,204.0,166.0,116.0,75.0,35.0] | |
| # set up parser to take input of folder with images to loop through | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--path", help = "Path to folder containing images") | |
| ap.add_argument("--cardsquare", help = "Draw square around color card if found, default is 0 (none)", nargs='?', const=0, type=int) | |
| ap.add_argument("--blurfactor", help = "Laplacian Fourier Transform detection of blurriness in image from http://www.pyimagesearch.com/2015/09/07/blur-detection-with-opencv/", nargs='?', const=40, type=int) | |
| ap.add_argument("--squaresimilarity", help = "How similar in size should the squares be (integer percentage, default is 90%)", nargs='?', const=90, type=int) | |
| ap.add_argument('--expandhist', help = 'In darker samples, the expansion of the histogram hinders finding the squares due to problems with the otsu thresholding. If your image has a bright background, expandhist, otherwise, do not.', action='store_true', default=False) | |
| ap.add_argument('--removeedges', help = 'If there are artifacts around the edge that are complicating finding the card, this will remove things within X% of the borders.', nargs='?', const=0, type=int) | |
| ap.add_argument('--threshold', help = 'Allows choice of different methods for segmentation. otsu, normal and adaptguass', choices = ["otsu","adaptgauss","normal"], nargs='?', default = "normal", type=str.lower) | |
| ap.add_argument('--threshvalue', help = 'If there are artifacts around the edge that are complicating finding the card, this will remove things within X% of the borders.', nargs='?', const=127, type=int) | |
| ap.add_argument('--debug', action='store_true', default=False) | |
| args = ap.parse_args() | |
| # create new folder for storing images (resolution to minute so can overwrite if you start two processes quickly after each other) | |
| currentTime = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') | |
| foldername = '{0}_Results'.format(currentTime) | |
| savedir = os.path.join(args.path, foldername) | |
| os.makedirs(savedir) | |
| #write out extra tables and images if having issues using the --debug flag | |
| if args.debug: | |
| savecolcardsdir = os.path.join(savedir, 'colcards') | |
| savefaildir = os.path.join(savedir, 'failures') | |
| saveedgesdir = os.path.join(savedir, 'edges') | |
| savemaskdir = os.path.join(savedir, 'masks') | |
| saveclahedir = os.path.join(savedir, 'clahe') | |
| saveblurdir = os.path.join(savedir, 'gaussblur') | |
| savethreshdir = os.path.join(savedir, 'threshold') | |
| os.makedirs(savecolcardsdir) | |
| os.makedirs(savefaildir) | |
| os.makedirs(saveedgesdir) | |
| os.makedirs(savemaskdir) | |
| os.makedirs(saveclahedir) | |
| os.makedirs(saveblurdir) | |
| os.makedirs(savethreshdir) | |
| #define types of tiles we're interested in | |
| filetypes = tuple([".JPG", ".jpg", ".JPEG", ".jpeg"]) | |
| # read the filelist from the path directory | |
| filelist = [f for f in os.listdir(args.path) if f.endswith(filetypes)] | |
| # how long is the list of files for progress reporting | |
| listlength = len(filelist) | |
| for fileindex, filename in enumerate(filelist): | |
| # progress bar | |
| pctdone = int(float(fileindex)/float(listlength) * 100) | |
| sys.stdout.write("\rPercentDone: %d%%" % pctdone) | |
| sys.stdout.flush() | |
| # read in image | |
| img = cv2.imread(os.path.join(args.path, filename)) | |
| # get image attributes | |
| height, width, channels = img.shape | |
| totalpx = float(height*width) # float for further processing | |
| # minimum and maximum square size based upon 12 MP image | |
| minarea = 1000./12000000. * totalpx # 1000px^2/12MP smallest size acceptable square | |
| maxarea = 150000./12000000. * totalpx #150kpx^2/12MP largest size acceptable square | |
| # create gray image for further processing | |
| gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) | |
| # Laplacian Fourier Transform detection of blurriness in image from http://www.pyimagesearch.com/2015/09/07/blur-detection-with-opencv/ | |
| blurfactor = cv2.Laplacian(gray_img, cv2.CV_64F).var() | |
| # if image is more blurry than blurfactor then try and deblur using kernel | |
| if blurfactor < args.blurfactor: | |
| # kernel from https://www.packtpub.com/mapt/book/Application+Development/9781785283932/2/ch02lvl1sec22/Sharpening | |
| kernel = np.array([[-1,-1,-1,-1,-1], | |
| [-1,2,2,2,-1], | |
| [-1,2,8,2,-1], | |
| [-1,2,2,2,-1], | |
| [-1,-1,-1,-1,-1]]) / 8.0 | |
| # store result back out for further processing | |
| gray_img = cv2.filter2D(gray_img, -1, kernel) | |
| if args.expandhist: | |
| # create a CLAHE object (Arguments are optional). | |
| clahe = cv2.createCLAHE(clipLimit=3.25, tileGridSize=(4,4)) | |
| # apply CLAHE histogram expansion to find squares better with canny edge detection | |
| gray_img = clahe.apply(gray_img) | |
| # thresholding | |
| if args.threshold == "otsu" : | |
| # blur slightly so defects on card squares and background patterns are less likely to be picked up | |
| gaussian = cv2.GaussianBlur(gray_img,(5,5),0) | |
| ret, threshold = cv2.threshold(gaussian, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) # | |
| elif args.threshold == "normal": | |
| # blur slightly so defects on card squares and background patterns are less likely to be picked up | |
| gaussian = cv2.GaussianBlur(gray_img,(5,5),0) | |
| ret,threshold = cv2.threshold(gaussian,args.threshvalue,255,cv2.THRESH_BINARY) | |
| elif args.threshold == "adaptgauss": | |
| # blur slightly so defects on card squares and background patterns are less likely to be picked up | |
| gaussian = cv2.GaussianBlur(gray_img,(11,11),0) | |
| threshold = cv2.adaptiveThreshold(gaussian,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,51,2) | |
| # canny edge detection with median-based thresholding | |
| edges = auto_canny(threshold) | |
| # save out files along the way | |
| if args.debug: | |
| if args.expandhist: | |
| savefilename = '{0}clahe.jpg'.format(filename) | |
| savefilecomplete = os.path.join(saveclahedir, savefilename) | |
| cv2.imwrite(savefilecomplete, gray_img, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) | |
| savefilename = '{0}blur.jpg'.format(filename) | |
| savefilecomplete = os.path.join(saveblurdir, savefilename) | |
| cv2.imwrite(savefilecomplete, gaussian, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) | |
| savefilename = '{0}threshold.jpg'.format(filename) | |
| savefilecomplete = os.path.join(savethreshdir, savefilename) | |
| cv2.imwrite(savefilecomplete, threshold, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) | |
| savefilename = '{0}edges.jpg'.format(filename) | |
| savefilecomplete = os.path.join(saveedgesdir, savefilename) | |
| cv2.imwrite(savefilecomplete, edges, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) | |
| # compute contours to find the squares of the card | |
| _, contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) | |
| mindex = [] # variable of which contour is which | |
| mu = [] # variable to store moments | |
| mc = [] # variable to x,y coordinates in tuples | |
| mx = [] # variable to x coordinate as integer | |
| my = [] # variable to y coordinate as integer | |
| marea = [] # variable to store area | |
| msquare = [] # variable to store whether something is a square (1) or not (0) | |
| msquarecoords = [] # variable to store square approximation coordinates | |
| mchild = [] # variable to store child hierarchy element | |
| mheight = [] # fitted rectangle height | |
| mwidth = [] # fitted rectangle width | |
| mwhratio = [] # ratio of height/width | |
| # extract moments from contour image | |
| for x in range(0, len(contours)): | |
| mu.append(cv2.moments(contours[x])) | |
| marea.append(cv2.contourArea(contours[x])) | |
| mchild.append(int(hierarchy[0][x][2])) | |
| mindex.append(x) | |
| # cycle through moment data and compute location for each moment | |
| for m in mu: | |
| if m['m00'] != 0: # this is the area term for a moment | |
| mc.append((int(m['m10']/m['m00']), int(m['m01']/m['m00']))) | |
| mx.append(int(m['m10']/m['m00'])) | |
| my.append(int(m['m01']/m['m00'])) | |
| else: | |
| mc.append((0,0)) | |
| mx.append(0) | |
| my.append(0) | |
| # loop over our contours and extract data about them | |
| for index, c in enumerate(contours): | |
| # area isn't 0 and is greater than minarea and less than maxarea | |
| if (marea[index] != 0 and marea[index] > minarea and marea[index] < maxarea): | |
| # finding squares | |
| # from http://www.pyimagesearch.com/2014/04/21/building-pokedex-python-finding-game-boy-screen-step-4-6/ | |
| # approximate the contour | |
| peri = cv2.arcLength(c, True) | |
| approx = cv2.approxPolyDP(c, 0.15 * peri, True) | |
| center, wh, angle = cv2.minAreaRect(c) #rotated rectangle | |
| mwidth.append(wh[0]) | |
| mheight.append(wh[1]) | |
| mwhratio.append(wh[0]/wh[1]) | |
| msquare.append(len(approx)) | |
| # if our approximated contour has four points, then | |
| # we can assume that we have found 4-sided objects (possibly squares) | |
| # 2016-10-05 added 5 to increase finding of card in blurred situations | |
| if len(approx) == 4 or 5: | |
| msquarecoords.append(approx) | |
| # if it's four pointed construct a mask for the contour, then compute mean RGB | |
| # creates a mask equal in size to img | |
| mask = np.zeros(img.shape[:2], dtype="uint8") | |
| else: # it's not a square | |
| # need to append these to keep array's the same length | |
| msquare.append(0) | |
| msquarecoords.append(0) | |
| else: # contour has an area of zero, not interesting | |
| # need to append these to keep array's the same length | |
| msquare.append(0) | |
| msquarecoords.append(0) | |
| mwidth.append(0) | |
| mheight.append(0) | |
| mwhratio.append(0) | |
| #make a pandas df from data for filtering out junk | |
| locarea = {'index' : mindex, 'X' : mx, 'Y' : my, 'width' : mwidth, 'height' : mheight, 'WHratio': mwhratio, 'Area' : marea, 'square' : msquare, 'child' : mchild} | |
| df = pd.DataFrame(locarea) | |
| # add filename and blurfactor to output | |
| df['File'] = filename | |
| df['blurriness'] = blurfactor | |
| # no ratio cutoff for debugging | |
| dfnoratio = df[(df['Area'] > minarea) & (df['Area'] < maxarea) & (df['child'] != -1) & (df['square'].isin([4,5]))] | |
| # filter df for attributes that would isolate squares of reasonable size | |
| df = df[(df['Area'] > minarea) & (df['Area'] < maxarea) & (df['child'] != -1) & (df['square'].isin([4,5])) & (df['WHratio'] < 1.2) & (df['WHratio'] > 0.85)] | |
| # filter nested squares from dataframe, was having issues with median being towards smaller nested squares | |
| df = df[~(df['index'].isin(df['index']+1))] | |
| # if you want to remove edges, this code selects the middle 96% of the image in both directions | |
| # card should not be right at edge if you select this | |
| if args.removeedges > 0: | |
| percentremove = float(args.removeedges) / 100. | |
| minwidth = percentremove * float(width) | |
| maxwidth = (1. - percentremove) * float(width) | |
| minheight = percentremove * float(height) | |
| maxheight = (1. - percentremove) * float(height) | |
| df = df[(df['X'] > minwidth) & (df['X'] < maxwidth) & (df['Y'] > minheight) & (df['Y'] < maxheight)] | |
| ###### find and count up squares that are within a given radius, more squares = more likelihood of them being the card | |
| # Median width of square time 2.5 gives proximity radius for searching for similar squares | |
| medianSqwidthpx = df["width"].median() | |
| #squares that are within 6 widths of the current square | |
| pixeldist = medianSqwidthpx*6 | |
| # computes euclidian distance matrix for the x and y contour centroids | |
| distmatrix = pd.DataFrame(squareform(pdist(df[['X', 'Y']]))) | |
| # add up distances that are less than ones have distance less than pixeldist pixels | |
| distmatrixflat = distmatrix.apply(lambda x: x[x<=pixeldist].count() - 1, axis=1) | |
| # | |
| # append distprox summary to dataframe | |
| df = df.assign(distprox = distmatrixflat.values) | |
| ##### Compute how similar in area the squares are. lots of similar values indicates card | |
| # isolate area measurements | |
| filteredArea = df['Area'] | |
| # create empty matrix for storing comparisons | |
| sizecomp = np.zeros((len(filteredArea), len(filteredArea))) | |
| # double loop through all areas to compare to each other | |
| for p in xrange(0, len(filteredArea)): | |
| for o in xrange(0, len(filteredArea)): | |
| big = max(filteredArea.iloc[p], filteredArea.iloc[o]) | |
| small = min(filteredArea.iloc[p], filteredArea.iloc[o]) | |
| pct = 100. * (small/big) | |
| sizecomp[p][o] = pct | |
| # how many comparisons for a given square are above squaresimilarity input (default 90%) | |
| sizematrix = pd.DataFrame(sizecomp).apply(lambda x: x[x>=args.squaresimilarity].count() - 1, axis=1) | |
| # append sizeprox summary to dataframe | |
| df = df.assign(sizeprox = sizematrix.values) | |
| # reorder dataframe for better printing | |
| df= df[['File', 'index', 'X', 'Y', 'width', 'height', 'WHratio', 'Area', 'square', 'child', 'blurriness', 'distprox', 'sizeprox']] | |
| ####### loosely filter for size and distance as well as square size relative to median | |
| minsqwidth = medianSqwidthpx * 0.80 | |
| maxsqwidth = medianSqwidthpx * 1.2 | |
| df = df[(df['distprox'] >= 5) & (df['sizeprox'] >= 5) & (df['width'] > minsqwidth) & (df['width'] < maxsqwidth)] | |
| # filter for proximity again to root out stragglers | |
| ###### find and count up squares that are within a given radius, more squares = more likelihood of them being the card | |
| # Median width of square time 2.5 gives proximity radius for searching for similar squares | |
| medianSqwidthpx = df["width"].median() | |
| #squares that are within 6 widths of the current square | |
| pixeldist = medianSqwidthpx*5 | |
| # computes euclidian distance matrix for the x and y contour centroids | |
| distmatrix = pd.DataFrame(squareform(pdist(df[['X', 'Y']]))) | |
| # add up distances that are less than ones have distance less than pixeldist pixels | |
| distmatrixflat = distmatrix.apply(lambda x: x[x<=pixeldist].count() - 1, axis=1) | |
| # | |
| # append distprox summary to dataframe | |
| df = df.assign(distprox = distmatrixflat.values) | |
| ####### filter results for distance proximity to other squares | |
| df = df[(df['distprox'] >= 4)] | |
| ###### Create composite bounding rectangle of found squares | |
| ###### Then transform the perspective | |
| # creates a completely black (zeros) mask equal in size to img | |
| mask = np.zeros(img.shape[:2], dtype="uint8") | |
| # fills in found squares in mask with white | |
| if len(df.index) > 1: | |
| for index in df['index']: | |
| cv2.drawContours(mask, contours, index, 255, -1) | |
| #save out mask file if debugging | |
| if args.debug: | |
| savefilename = '{0}mask.jpg'.format(filename) | |
| savefilecomplete = os.path.join(savemaskdir, savefilename) | |
| cv2.imwrite(savefilecomplete, mask, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) | |
| if cv2.countNonZero(mask) != 0: | |
| # creates a composite selection of found squares | |
| compositesquares = cv2.findNonZero(mask) | |
| # finds minimum area rectangle around squares | |
| rect = cv2.minAreaRect(compositesquares) | |
| box = cv2.boxPoints(rect) #extracts points from rectangle | |
| box = np.int0(box) #rounds points to exact pixel locations | |
| src = np.asarray(box, np.float32) #make points 32bit floats for further math | |
| #transform the card, expand area around card is 0 | |
| warped = four_point_transform(img, src, 0) | |
| # rotate 90 degrees if width is less than height (card is upright) | |
| cardheight, cardwidth = warped.shape[:2] | |
| if cardwidth < cardheight: | |
| warped = cv2.transpose(warped) | |
| warped = cv2.flip(warped, 1) | |
| # get parameters after rotation | |
| rotatedcardheight, rotatedcardwidth = warped.shape[:2] | |
| hwratio = float(rotatedcardheight)/float(rotatedcardwidth) | |
| # if only a partial card was found the ratio will be significantly out of whack | |
| # best to skip these for now and figure out why I can't find the squares later | |
| if hwratio > 0.6 and hwratio < 0.7: | |
| # creating scale as each card can be different sizes | |
| scale = float(rotatedcardwidth)/8.2 #card is 8.2 cm from square edge to opposite edge across | |
| fudgefactor = int(0.17 * scale) # 1.5 mm border around square in case of misalignment | |
| startcenter = int(0.6 * scale) # 0.6 cm in from top and left edge | |
| centerwidth = int(1.434 * scale) # square centers are ~1.5 cm apart | |
| # compute list of x position centers | |
| xpos = [] | |
| calc = startcenter | |
| for i in xrange(0,6): | |
| xpos.append(calc) | |
| calc = calc + centerwidth | |
| # compute list of y position centers | |
| ypos = [] | |
| calc = startcenter | |
| for i in xrange(0,4): | |
| ypos.append(calc) | |
| calc = calc + centerwidth | |
| # create tuples from combinations of x and y coordinates | |
| centers = list(product(xpos, ypos)) | |
| # sort the list of centers by y and then x | |
| centers = sorted(centers, key=lambda x: (x[1], x[0])) | |
| # set up arrays for storing color | |
| cardcolorR = [] # variable to store mean of R | |
| cardcolorG = [] # variable to store mean of G | |
| cardcolorB = [] # variable to store mean of B | |
| cardcolorRstddev = [] # variable to store stddev of R | |
| cardcolorGstddev = [] # variable to store stddev of G | |
| cardcolorBstddev = [] # variable to store stddev of B | |
| # loop through centers and take color data | |
| for c in centers: | |
| # creates a mask equal in size to img | |
| mask = np.zeros(warped.shape[:2], dtype="uint8") | |
| # draw and fill contour | |
| cv2.circle(mask, c, (startcenter-fudgefactor), 255, -1) | |
| # extract median BGR value | |
| mean, stddev = cv2.meanStdDev(warped, mask=mask)[:3] | |
| # append color data to mcolor array | |
| cardcolorR.append(int(mean[2])) | |
| cardcolorG.append(int(mean[1])) | |
| cardcolorB.append(int(mean[0])) | |
| cardcolorRstddev.append(int(stddev[2])) | |
| cardcolorGstddev.append(int(stddev[1])) | |
| cardcolorBstddev.append(int(stddev[0])) | |
| # check mirroring left and right middle squares regardless of orientation | |
| leftDark = cardcolorR[6] + cardcolorG[6] + cardcolorB[6] + cardcolorR[12] + cardcolorG[12] + cardcolorB[21] | |
| rightLight = cardcolorR[11] + cardcolorG[11] + cardcolorB[11] + cardcolorR[17] + cardcolorG[17] + cardcolorB[17] | |
| # need combination of left-right/up-down to get mirror and orientation | |
| # check card orientation: only two possibilities if not mirrored | |
| orangeR = cardcolorR[6] | |
| blueR = cardcolorR[12] | |
| yellowR = cardcolorR[11] | |
| lightblueR = cardcolorR[17] | |
| # card is not in expected orientation | |
| # testing both sides of the card in case something is covered or blotchy | |
| # if mirrored, this will be true and skipped | |
| if blueR > orangeR or lightblueR > yellowR: | |
| # reverse order of square | |
| cardcolorR = cardcolorR[::-1] | |
| cardcolorG = cardcolorG[::-1] | |
| cardcolorB = cardcolorB[::-1] | |
| cardcolorRstddev = cardcolorRstddev[::-1] | |
| cardcolorGstddev = cardcolorGstddev[::-1] | |
| cardcolorBstddev = cardcolorBstddev[::-1] | |
| # create dataframe with color results from transformed card | |
| colorcard = {'File' : filename, 'X' : [x[0] for x in centers], 'Y' : [x[1] for x in centers], 'R' : cardcolorR, 'G' : cardcolorG, 'B' : cardcolorB, 'Rsd' : cardcolorRstddev, 'Gsd' : cardcolorGstddev, 'Bsd' : cardcolorBstddev} | |
| colorcardDF = pd.DataFrame(colorcard) | |
| colorcardDF= colorcardDF[['File', 'X', 'Y', 'R', 'G', 'B', 'Rsd', 'Gsd', 'Bsd']] | |
| # bring in standard color values | |
| colorcardDF = colorcardDF.assign(xritePassportR = xritePassportR) | |
| colorcardDF = colorcardDF.assign(xritePassportG = xritePassportG) | |
| colorcardDF = colorcardDF.assign(xritePassportB = xritePassportB) | |
| #filter model data for bad SDs | |
| # bad SDs would indicate junk on the card squares | |
| filteredCCDF = colorcardDF[(colorcardDF['Rsd'] < 45) & (colorcardDF['Gsd'] < 45) & (colorcardDF['Bsd'] < 45)] | |
| # create a fitted model in one line | |
| lm = smf.ols(formula='xritePassportR ~ R', data=filteredCCDF).fit() | |
| redintercept = lm.params.Intercept | |
| redslope = lm.params.R | |
| # create a fitted model in one line | |
| lm = smf.ols(formula='xritePassportG ~ G', data=filteredCCDF).fit() | |
| greenintercept = lm.params.Intercept | |
| greenslope = lm.params.G | |
| # create a fitted model in one line | |
| lm = smf.ols(formula='xritePassportB ~ B', data=filteredCCDF).fit() | |
| blueintercept = lm.params.Intercept | |
| blueslope = lm.params.B | |
| ##### correct image for color imbalance | |
| # numpy referencing (row, col, ch) http://scikit-image.org/docs/dev/user_guide/numpy_images.html | |
| # correct red channel | |
| img[:,:,2] = cv2.add(cv2.multiply(img[:,:,2],np.array([redslope])),np.array([redintercept])) | |
| # correct green channel | |
| img[:,:,1] = cv2.add(cv2.multiply(img[:,:,1],np.array([greenslope])),np.array([greenintercept])) | |
| # correct blue channel | |
| img[:,:,0] = cv2.add(cv2.multiply(img[:,:,0],np.array([blueslope])),np.array([blueintercept])) | |
| # save out image | |
| if args.cardsquare > 0: # draw box around card | |
| cv2.drawContours(img,[box],0,(255,0,0), args.cardsquare) | |
| savefilename = '{0}colcor.jpg'.format(filename) | |
| savefilecomplete = os.path.join(savedir, savefilename) | |
| cv2.imwrite(savefilecomplete, img, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) | |
| # compute number of rows remaining in table, this is number of squares used in correction | |
| fCCDFrowcol = filteredCCDF.shape | |
| numsquares = fCCDFrowcol[0] | |
| # compute scale from median width, square is 1.2 cm across | |
| scale = medianSqwidthpx/1.2 | |
| ColorCorrected = "Success" | |
| outdata = [filename, ColorCorrected, numsquares, scale, redslope, redintercept, greenslope, greenintercept, blueslope, blueintercept] | |
| # if ratio of card is wrong, probably a truncated card | |
| else: | |
| ColorCorrected = "Fail Bad Card Ratio" | |
| numsquares, scale, redslope, redintercept, greenslope, greenintercept, blueslope, blueintercept = ("",)*8 | |
| #outdata = [filename, ColorCorrected, numsquares, scale, redslope, redintercept, greenslope, greenintercept, blueslope, blueintercept] | |
| else: # cv2.countNonZero(mask) == 0: | |
| ColorCorrected = "Fail No Mask" | |
| numsquares, scale, redslope, redintercept, greenslope, greenintercept, blueslope, blueintercept = ("",)*8 | |
| # save out calibration data and scale | |
| colorout = {'File' : filename, 'ColorCorrected' : ColorCorrected, 'numberofSquares4correction' : [numsquares], 'SetScalepx': [scale], 'RedSlope' : [redslope], 'RedIntercept' : [redintercept], 'GreenSlope' : [greenslope], 'GreenIntercept' : [greenintercept], 'BlueSlope' : [blueslope], 'BlueIntercept' : [blueintercept]} | |
| coloroutDF = pd.DataFrame(colorout) | |
| #reorder columns for prettier output | |
| coloroutDF= coloroutDF[['File','ColorCorrected','numberofSquares4correction','SetScalepx','RedSlope','RedIntercept','GreenSlope','GreenIntercept','BlueSlope','BlueIntercept']] | |
| # write out results | |
| if fileindex == 0: | |
| coloroutDF.to_csv(os.path.join(savedir, 'Results.csv'), mode = 'w', header=True) | |
| else: | |
| coloroutDF.to_csv(os.path.join(savedir, 'Results.csv'), mode = 'a', header=False) | |
| #write out extra tables and images if having issues using the --debug flag | |
| if args.debug: | |
| if fileindex == 0: | |
| df.to_csv(os.path.join(savedir, 'Contours.csv'), mode = 'w', header=True) | |
| else: | |
| df.to_csv(os.path.join(savedir, 'Contours.csv'), mode = 'a', header=False) | |
| try: | |
| filteredCCDF | |
| except NameError: | |
| print('No filtered CCDF') | |
| else: | |
| if fileindex == 0: | |
| filteredCCDF.to_csv(os.path.join(savedir, 'Squares.csv'), mode = 'w', header=True) | |
| else: | |
| filteredCCDF.to_csv(os.path.join(savedir, 'Squares.csv'), mode = 'a', header=False) | |
| # if only a partial card was found the ratio will be significantly out of whack | |
| # want to only print out those that fail ratio check | |
| if hwratio > 0.6 and hwratio < 0.7: | |
| # loop through centers and draw circles | |
| for row in filteredCCDF.itertuples(): | |
| cv2.circle(warped, (row.X, row.Y), (startcenter-fudgefactor), (0,0,255), 3) | |
| savefilename = '{0}colcard.jpg'.format(filename) | |
| savecolcardcomplete = os.path.join(savecolcardsdir, savefilename) | |
| cv2.imwrite(savecolcardcomplete, warped, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) | |
| # save out image with false color labeling for partially filtered contours (blue) and filtered contours (red) | |
| if args.cardsquare > 0 and ColorCorrected == 'Success': # draw box around card | |
| cv2.drawContours(img,[box],0,(255,0,0), args.cardsquare) | |
| for index in dfnoratio['index']: | |
| cv2.drawContours(img, contours, index, (255,0,0), -1) | |
| for index in df['index']: | |
| cv2.drawContours(img, contours, index, (0,0,255), -1) | |
| savefilename = '{0}colcor.jpg'.format(filename) | |
| savefilecomplete = os.path.join(savefaildir, savefilename) | |
| cv2.imwrite(savefilecomplete, img, [int(cv2.IMWRITE_JPEG_QUALITY), 90]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment