Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 1 | /*----------------------------------------------------------------------------*/ |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 2 | /* Copyright (c) FIRST 2014-2016. All Rights Reserved. */ |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 3 | /* Open Source Software - may be modified and shared by FRC teams. The code */ |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 4 | /* must be accompanied by the FIRST BSD license file in the root directory of */ |
| 5 | /* the project. */ |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 6 | /*----------------------------------------------------------------------------*/ |
| 7 | |
| 8 | #include "Vision/BinaryImage.h" |
| 9 | #include "WPIErrors.h" |
| 10 | #include <cstring> |
| 11 | |
| 12 | using namespace std; |
| 13 | |
| 14 | /** |
| 15 | * Get then number of particles for the image. |
| 16 | * @returns the number of particles found for the image. |
| 17 | */ |
| 18 | int BinaryImage::GetNumberParticles() { |
| 19 | int numParticles = 0; |
| 20 | int success = imaqCountParticles(m_imaqImage, 1, &numParticles); |
| 21 | wpi_setImaqErrorWithContext(success, "Error counting particles"); |
| 22 | return numParticles; |
| 23 | } |
| 24 | |
| 25 | /** |
| 26 | * Get a single particle analysis report. |
| 27 | * Get one (of possibly many) particle analysis reports for an image. |
| 28 | * @param particleNumber Which particle analysis report to return. |
| 29 | * @returns the selected particle analysis report |
| 30 | */ |
| 31 | ParticleAnalysisReport BinaryImage::GetParticleAnalysisReport( |
| 32 | int particleNumber) { |
| 33 | ParticleAnalysisReport par; |
| 34 | GetParticleAnalysisReport(particleNumber, &par); |
| 35 | return par; |
| 36 | } |
| 37 | |
| 38 | /** |
| 39 | * Get a single particle analysis report. |
| 40 | * Get one (of possibly many) particle analysis reports for an image. |
| 41 | * This version could be more efficient when copying many reports. |
| 42 | * @param particleNumber Which particle analysis report to return. |
| 43 | * @param par the selected particle analysis report |
| 44 | */ |
| 45 | void BinaryImage::GetParticleAnalysisReport(int particleNumber, |
| 46 | ParticleAnalysisReport *par) { |
| 47 | int success; |
| 48 | int numParticles = 0; |
| 49 | |
| 50 | success = imaqGetImageSize(m_imaqImage, &par->imageWidth, &par->imageHeight); |
| 51 | wpi_setImaqErrorWithContext(success, "Error getting image size"); |
| 52 | if (StatusIsFatal()) return; |
| 53 | |
| 54 | success = imaqCountParticles(m_imaqImage, 1, &numParticles); |
| 55 | wpi_setImaqErrorWithContext(success, "Error counting particles"); |
| 56 | if (StatusIsFatal()) return; |
| 57 | |
| 58 | if (particleNumber >= numParticles) { |
| 59 | wpi_setWPIErrorWithContext(ParameterOutOfRange, "particleNumber"); |
| 60 | return; |
| 61 | } |
| 62 | |
| 63 | par->particleIndex = particleNumber; |
| 64 | // Don't bother measuring the rest of the particle if one fails |
| 65 | bool good = ParticleMeasurement(particleNumber, IMAQ_MT_CENTER_OF_MASS_X, |
| 66 | &par->center_mass_x); |
| 67 | good = good && ParticleMeasurement(particleNumber, IMAQ_MT_CENTER_OF_MASS_Y, |
| 68 | &par->center_mass_y); |
| 69 | good = good && |
| 70 | ParticleMeasurement(particleNumber, IMAQ_MT_AREA, &par->particleArea); |
| 71 | good = good && ParticleMeasurement(particleNumber, IMAQ_MT_BOUNDING_RECT_TOP, |
| 72 | &par->boundingRect.top); |
| 73 | good = good && ParticleMeasurement(particleNumber, IMAQ_MT_BOUNDING_RECT_LEFT, |
| 74 | &par->boundingRect.left); |
| 75 | good = |
| 76 | good && ParticleMeasurement(particleNumber, IMAQ_MT_BOUNDING_RECT_HEIGHT, |
| 77 | &par->boundingRect.height); |
| 78 | good = |
| 79 | good && ParticleMeasurement(particleNumber, IMAQ_MT_BOUNDING_RECT_WIDTH, |
| 80 | &par->boundingRect.width); |
| 81 | good = good && ParticleMeasurement(particleNumber, IMAQ_MT_AREA_BY_IMAGE_AREA, |
| 82 | &par->particleToImagePercent); |
| 83 | good = good && ParticleMeasurement(particleNumber, |
| 84 | IMAQ_MT_AREA_BY_PARTICLE_AND_HOLES_AREA, |
| 85 | &par->particleQuality); |
| 86 | |
| 87 | if (good) { |
| 88 | /* normalized position (-1 to 1) */ |
| 89 | par->center_mass_x_normalized = |
| 90 | NormalizeFromRange(par->center_mass_x, par->imageWidth); |
| 91 | par->center_mass_y_normalized = |
| 92 | NormalizeFromRange(par->center_mass_y, par->imageHeight); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * Get an ordered vector of particles for the image. |
| 98 | * Create a vector of particle analysis reports sorted by size for an image. |
| 99 | * The vector contains the actual report structures. |
| 100 | * @returns a pointer to the vector of particle analysis reports. The caller |
| 101 | * must delete the |
| 102 | * vector when finished using it. |
| 103 | */ |
| 104 | vector<ParticleAnalysisReport> * |
| 105 | BinaryImage::GetOrderedParticleAnalysisReports() { |
| 106 | auto particles = new vector<ParticleAnalysisReport>; |
| 107 | int particleCount = GetNumberParticles(); |
| 108 | for (int particleIndex = 0; particleIndex < particleCount; particleIndex++) { |
| 109 | particles->push_back(GetParticleAnalysisReport(particleIndex)); |
| 110 | } |
| 111 | // TODO: This is pretty inefficient since each compare in the sort copies |
| 112 | // both reports being compared... do it manually instead... while we're |
| 113 | // at it, we should provide a version that allows a preallocated buffer of |
| 114 | // ParticleAnalysisReport structures |
| 115 | sort(particles->begin(), particles->end(), CompareParticleSizes); |
| 116 | return particles; |
| 117 | } |
| 118 | |
| 119 | /** |
| 120 | * Write a binary image to flash. |
| 121 | * Writes the binary image to flash on the cRIO for later inspection. |
| 122 | * @param fileName the name of the image file written to the flash. |
| 123 | */ |
| 124 | void BinaryImage::Write(const char *fileName) { |
| 125 | RGBValue colorTable[256]; |
| 126 | memset(colorTable, 0, sizeof(colorTable)); |
| 127 | colorTable[0].R = 0; |
| 128 | colorTable[1].R = 255; |
| 129 | colorTable[0].G = colorTable[1].G = 0; |
| 130 | colorTable[0].B = colorTable[1].B = 0; |
| 131 | colorTable[0].alpha = colorTable[1].alpha = 0; |
| 132 | imaqWriteFile(m_imaqImage, fileName, colorTable); |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Measure a single parameter for an image. |
| 137 | * Get the measurement for a single parameter about an image by calling the |
| 138 | * imaqMeasureParticle |
| 139 | * function for the selected parameter. |
| 140 | * @param particleNumber which particle in the set of particles |
| 141 | * @param whatToMeasure the imaq MeasurementType (what to measure) |
| 142 | * @param result the value of the measurement |
| 143 | * @returns false on failure, true on success |
| 144 | */ |
| 145 | bool BinaryImage::ParticleMeasurement(int particleNumber, |
| 146 | MeasurementType whatToMeasure, |
| 147 | int *result) { |
| 148 | double resultDouble; |
| 149 | bool success = |
| 150 | ParticleMeasurement(particleNumber, whatToMeasure, &resultDouble); |
| 151 | *result = (int)resultDouble; |
| 152 | return success; |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * Measure a single parameter for an image. |
| 157 | * Get the measurement for a single parameter about an image by calling the |
| 158 | * imaqMeasureParticle |
| 159 | * function for the selected parameter. |
| 160 | * @param particleNumber which particle in the set of particles |
| 161 | * @param whatToMeasure the imaq MeasurementType (what to measure) |
| 162 | * @param result the value of the measurement |
| 163 | * @returns true on failure, false on success |
| 164 | */ |
| 165 | bool BinaryImage::ParticleMeasurement(int particleNumber, |
| 166 | MeasurementType whatToMeasure, |
| 167 | double *result) { |
| 168 | int success; |
| 169 | success = imaqMeasureParticle(m_imaqImage, particleNumber, 0, whatToMeasure, |
| 170 | result); |
| 171 | wpi_setImaqErrorWithContext(success, "Error measuring particle"); |
| 172 | return !StatusIsFatal(); |
| 173 | } |
| 174 | |
| 175 | // Normalizes to [-1,1] |
| 176 | double BinaryImage::NormalizeFromRange(double position, int range) { |
| 177 | return (position * 2.0 / (double)range) - 1.0; |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * The compare helper function for sort. |
| 182 | * This function compares two particle analysis reports as a helper for the sort |
| 183 | * function. |
| 184 | * @param particle1 The first particle to compare |
| 185 | * @param particle2 the second particle to compare |
| 186 | * @returns true if particle1 is greater than particle2 |
| 187 | */ |
| 188 | bool BinaryImage::CompareParticleSizes(ParticleAnalysisReport particle1, |
| 189 | ParticleAnalysisReport particle2) { |
| 190 | // we want descending sort order |
| 191 | return particle1.particleToImagePercent > particle2.particleToImagePercent; |
| 192 | } |
| 193 | |
| 194 | BinaryImage *BinaryImage::RemoveSmallObjects(bool connectivity8, int erosions) { |
| 195 | auto result = new BinaryImage(); |
| 196 | int success = imaqSizeFilter(result->GetImaqImage(), m_imaqImage, |
| 197 | connectivity8, erosions, IMAQ_KEEP_LARGE, nullptr); |
| 198 | wpi_setImaqErrorWithContext(success, "Error in RemoveSmallObjects"); |
| 199 | return result; |
| 200 | } |
| 201 | |
| 202 | BinaryImage *BinaryImage::RemoveLargeObjects(bool connectivity8, int erosions) { |
| 203 | auto result = new BinaryImage(); |
| 204 | int success = imaqSizeFilter(result->GetImaqImage(), m_imaqImage, |
| 205 | connectivity8, erosions, IMAQ_KEEP_SMALL, nullptr); |
| 206 | wpi_setImaqErrorWithContext(success, "Error in RemoveLargeObjects"); |
| 207 | return result; |
| 208 | } |
| 209 | |
| 210 | BinaryImage *BinaryImage::ConvexHull(bool connectivity8) { |
| 211 | auto result = new BinaryImage(); |
| 212 | int success = |
| 213 | imaqConvexHull(result->GetImaqImage(), m_imaqImage, connectivity8); |
| 214 | wpi_setImaqErrorWithContext(success, "Error in convex hull operation"); |
| 215 | return result; |
| 216 | } |
| 217 | |
| 218 | BinaryImage *BinaryImage::ParticleFilter(ParticleFilterCriteria2 *criteria, |
| 219 | int criteriaCount) { |
| 220 | auto result = new BinaryImage(); |
| 221 | int numParticles; |
| 222 | ParticleFilterOptions2 filterOptions = {0, 0, 0, 1}; |
| 223 | int success = |
| 224 | imaqParticleFilter4(result->GetImaqImage(), m_imaqImage, criteria, |
| 225 | criteriaCount, &filterOptions, nullptr, &numParticles); |
| 226 | wpi_setImaqErrorWithContext(success, "Error in particle filter operation"); |
| 227 | return result; |
| 228 | } |