├── .gitignore └── AbstractLine ├── CoherentLine.cpp ├── CoherentLine.h ├── main.cpp ├── potrace_adaptor.h ├── potrace_loader.h └── thing.h /.gitignore: -------------------------------------------------------------------------------- 1 | /Build 2 | *.xcodeproj 3 | *.1 4 | -------------------------------------------------------------------------------- /AbstractLine/CoherentLine.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // CoherentLine.cpp 3 | // AbstractLine 4 | // 5 | // Created by Zhipeng Wu on 5/22/13. 6 | // Copyright (c) 2013 Zhipeng Wu. All rights reserved. 7 | // 8 | 9 | #include "CoherentLine.h" 10 | 11 | #define DISCRETE_FILTER_SIZE 2048 12 | #define LOWPASS_FILTR_LENGTH 10.00000f 13 | #define LINE_SQUARE_CLIP_MAX 100000.0f 14 | #define VECTOR_COMPONENT_MIN 0.050000f 15 | #define PI 3.1415926 16 | 17 | cv::RNG rng(12345); 18 | // Prepare 1-d gaussian template. 19 | static void GetGaussianWeights(float* weights, 20 | int neighbor, 21 | float sigma) { 22 | if ((NULL == weights) || (neighbor < 0)) 23 | return; 24 | float term1 = 1.0 / (sqrt(2.0 * PI) * sigma); 25 | float term2 = -1.0 / (2 * pow(sigma, 2)); 26 | weights[neighbor] = term1; 27 | float sum = weights[neighbor]; 28 | for (int i = 1; i <= neighbor; ++i) { 29 | weights[neighbor + i] = exp(pow(i, 2) * term2) * term1; 30 | weights[neighbor - i] = weights[neighbor + i]; 31 | sum += weights[neighbor + i] + weights[neighbor - i]; 32 | } 33 | // Normalization 34 | for (int j = 0; j < neighbor * 2 + 1; ++j) { 35 | weights[j] /= sum; 36 | } 37 | } 38 | 39 | // Prepare 1-d difference of gaussian template. 40 | static void GetDiffGaussianWeights(float* weights, 41 | int neighbor, 42 | float sigma_e, 43 | float sigma_r, 44 | float tau) { 45 | if ((NULL == weights) || (neighbor < 0)) 46 | return; 47 | float* gaussian_e = new float[neighbor * 2 + 1]; 48 | float* gaussian_r = new float[neighbor * 2 + 1]; 49 | GetGaussianWeights(gaussian_e, neighbor, sigma_e); 50 | GetGaussianWeights(gaussian_r, neighbor, sigma_r); 51 | float sum = 0; 52 | for (int i = 0; i < neighbor * 2 + 1; ++i) { 53 | weights[i] = gaussian_e[i] - tau * gaussian_r[i]; 54 | sum += weights[i]; 55 | } 56 | // Normalization 57 | for (int j = 0; j < neighbor * 2 + 1; ++j) { 58 | weights[j] /= sum; 59 | } 60 | delete[] gaussian_e; 61 | delete[] gaussian_r; 62 | } 63 | 64 | void CoherentLine::GetEdegTangentFlow() { 65 | // Step 1: Cclculate the structure tensor. 66 | cv::Mat st; // CV_32FC3 (E, G, F) 67 | CalcStructureTensor(&st); 68 | // Step 2: Gaussian blur the struct tensor. sst_sigma = 2.0 69 | float sigma_sst = 2; 70 | int gaussian_size = ceil(sigma_sst * 2) * 2 + 1; 71 | cv::GaussianBlur(st, st, cv::Size2i(gaussian_size, gaussian_size), sigma_sst); 72 | // Step 3: Extract etf_: CV_32FC3 (v2.x, v2.y, sqrt(lambda2). 73 | etf_ = cv::Mat::zeros(rows_, cols_, CV_32FC3); 74 | float E, G, F ,lambda1, v2x, v2y, v2; 75 | for (int r = 0; r < rows_; ++r) { 76 | for (int c = 0; c < cols_; ++c) { 77 | E = st.at(r, c)[0]; 78 | G = st.at(r, c)[1]; 79 | F = st.at(r, c)[2]; 80 | lambda1 = 0.5 * (E + G + sqrtf((G - E) * (G - E) + 4 * F * F)); 81 | v2x = E - lambda1; 82 | v2y = F; 83 | v2 = sqrtf(v2x * v2x + v2y * v2y); 84 | etf_.at(r, c)[0] = (0 == v2)? 0 : (v2x / v2); 85 | etf_.at(r, c)[1] = (0 == v2)? 0 : (v2y / v2); 86 | assert(E + G - lambda1 >= 0); 87 | etf_.at(r, c)[2] = sqrtf(E + G - lambda1); 88 | } 89 | } 90 | // What to show it? 91 | // VisualizeByLIC(etf_); 92 | // VisualizeByArrow(etf_); 93 | } 94 | 95 | void CoherentLine::GetDogEdge() { 96 | dog_edge_ = cv::Mat::zeros(rows_, cols_, CV_8UC1); 97 | float sigma_e = 1.0; 98 | float sigma_r = 1.6; 99 | float tau = 0.99; 100 | float phi = 2.0; 101 | int gaussian_size = ceilf(2.0 * sigma_r) * 2 + 1; 102 | cv::Mat blur_e, blur_r, gray; 103 | cv::bilateralFilter(gray_, gray, 5, 150, 150); 104 | gray.convertTo(gray, CV_32FC1); 105 | cv::GaussianBlur(gray, blur_e, cv::Size2i(gaussian_size, gaussian_size), sigma_e); 106 | cv::GaussianBlur(gray, blur_r, cv::Size2i(gaussian_size, gaussian_size), sigma_r); 107 | float diff; 108 | for (int r = 0; r < rows_; ++r) { 109 | for (int c = 0; c < cols_; ++c) { 110 | diff = blur_e.at(r, c) - tau * blur_r.at(r, c); 111 | if (diff > 0) { 112 | dog_edge_.at(r, c) = 255; 113 | } else { 114 | dog_edge_.at(r, c) = 0; //static_cast(255 * (1 + tanhf(diff * phi))); 115 | } 116 | } 117 | } 118 | // cv::imshow("dog_edge", dog_edge()); 119 | // cv::waitKey(); 120 | } 121 | 122 | void CoherentLine::GetFDogEdge() { 123 | if (etf_.empty()) { 124 | GetEdegTangentFlow(); 125 | cout << "EFT calculation finished." << endl; 126 | } 127 | fdog_edge_.create(rows_, cols_, CV_32FC1); 128 | fdog_edge_ = 255; 129 | cv::Mat f0 = cv::Mat::ones(rows_, cols_, CV_32FC1); 130 | cv::Mat f1 = cv::Mat::ones(rows_, cols_, CV_32FC1); 131 | // cv::Mat u0(rows_, cols_, CV_8UC1); 132 | cv::Mat u1 = cv::Mat::zeros(image_.size(), CV_8UC1); 133 | float sigma_e = 1.0; 134 | float sigma_r = 1.6; 135 | float sigma_m = 3.0; 136 | float tau = 0.99; 137 | // float phi = 2.0; 138 | int neighbor1 = static_cast(ceilf(2.0 * sigma_r)); 139 | float sin_theta, cos_theta; 140 | float* diff_gaussian_weights = new float[neighbor1 * 2 + 1]; 141 | float* sample_pixels1, *sample_pixels2; 142 | float sum_diff, sum_dev, sum_1; 143 | GetDiffGaussianWeights(diff_gaussian_weights, neighbor1, sigma_e, sigma_r, tau); 144 | int neighbor2 = ceilf(2.0 * sigma_m); 145 | float* gaussian_weights = new float[neighbor2 * 2 + 1]; 146 | GetGaussianWeights(gaussian_weights, neighbor2, sigma_m); 147 | cv::Mat gray; 148 | bgray_.copyTo(gray); 149 | 150 | // Step 1: do DoG along the gradient direction. 151 | for (int r = neighbor1; r < (rows_ - neighbor1); ++r) { 152 | for (int c = neighbor1; c < (cols_ - neighbor1); ++c) { 153 | // Get pixel gradient direction. 154 | cos_theta = etf_.at(r, c)[1]; 155 | sin_theta = -1 * etf_.at(r, c)[0]; 156 | sample_pixels1 = new float[neighbor1 * 2 + 1]; 157 | sample_pixels1[neighbor1] = static_cast(gray.at(r, c)); 158 | for (int k = 1; k <= neighbor1; ++k) { 159 | int r_offset = round(sin_theta * k); 160 | int c_offset = round(cos_theta * k); 161 | sample_pixels1[neighbor1 + k] = 162 | static_cast(gray.at(r + r_offset, c + c_offset)); 163 | sample_pixels1[neighbor1 - k] = 164 | static_cast(gray.at(r - r_offset, c - c_offset)); 165 | } 166 | // Calculate edge response. 167 | sum_diff = 0; 168 | sum_dev = 0; 169 | for (int k = 0; k < 2 * neighbor1 + 1; ++k) { 170 | sum_diff += sample_pixels1[k] * diff_gaussian_weights[k]; 171 | } 172 | f0.at(r, c) = sum_diff; 173 | delete[] sample_pixels1; 174 | } 175 | } 176 | cv::imshow("dog along tangent", f0); 177 | cv::waitKey(); 178 | 179 | // Step 2: do Gaussian blur along tangent direction. 180 | for (int r = neighbor2; r < (rows_ - neighbor2); ++r) { 181 | for (int c = neighbor2; c < (cols_ - neighbor2); ++c) { 182 | // Get pixel tangent direction. 183 | cos_theta = etf_.at(r, c)[0]; 184 | sin_theta = etf_.at(r, c)[1]; 185 | sample_pixels2 = new float[neighbor2 * 2 + 1]; 186 | sample_pixels2[neighbor2] = f0.at(r, c); 187 | for (int k = 1; k <= neighbor2; ++k) { 188 | int r_offset = round(sin_theta * k); 189 | int c_offset = round(cos_theta * k); 190 | sample_pixels2[neighbor2 + k] = f0.at(r + r_offset, c + c_offset); 191 | sample_pixels2[neighbor2 - k] = f0.at(r - r_offset, c - c_offset); 192 | } 193 | // Calculate edge response. 194 | sum_1 = 0; 195 | for (int k = 0; k < 2 * neighbor2 + 1; ++k) { 196 | sum_1 += sample_pixels2[k] * gaussian_weights[k]; 197 | } 198 | f1.at(r, c) = sum_1; 199 | if (f1.at(r, c) > 0) { 200 | u1.at(r,c) = 0; 201 | fdog_edge_.at(r, c) = 255; 202 | } else { 203 | u1.at(r,c) = 255; 204 | fdog_edge_.at(r, c) = 0; 205 | } 206 | delete[] sample_pixels2; 207 | } 208 | } 209 | // cv::imshow("fdog", fdog_edge_); 210 | // cv::waitKey(); 211 | // cv::imshow("edge", u1); 212 | // cv::waitKey(); 213 | // 214 | delete [] diff_gaussian_weights; 215 | delete [] gaussian_weights; 216 | // cv::imwrite("/Users/WU/Pictures/test_image/line/fdog1.bmp", fdog_edge_); 217 | 218 | 219 | // thin_edge_ = ZhangThinning(u1); 220 | // cv::imshow("thin", thin_edge_); 221 | // cv::waitKey(); 222 | 223 | // vector > contours; 224 | // vector hierarchy; 225 | // cv::findContours(u1, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0)); 226 | // cv::Mat drawing = cv::Mat::zeros(image_.size(), CV_8UC3); 227 | // for( int i = 0; i< contours.size(); i++ ) { 228 | // if (contours[i].size() < 20) 229 | // continue; 230 | // cout << contours[i].size() << endl; 231 | // cv::Scalar color = cv::Scalar(rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255)); 232 | // cv::drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, cv::Point()); 233 | // } 234 | 235 | // /// Show in a window 236 | // cv::namedWindow( "Contours", CV_WINDOW_AUTOSIZE ); 237 | // imshow( "Contours", drawing); 238 | // cv::waitKey(); 239 | } 240 | 241 | 242 | void CoherentLine::CalcStructureTensor(cv::Mat* st) { 243 | st->create(rows_, cols_, CV_32FC3); 244 | if (1 == image_.channels()) { 245 | // Gradient calculation. 246 | cv::Mat gx, gy; 247 | cv::Sobel(gray_, gx, CV_32F, 1, 0); 248 | cv::Sobel(gray_, gy, CV_32F, 0, 1); 249 | float dx, dy; 250 | for (int r = 0; r < rows_; ++r) { 251 | for (int c = 0; c < cols_; ++c) { 252 | dx = gx.at(r, c); 253 | dy = gy.at(r, c); 254 | st->at(r, c)[0] = dx * dx; // E 255 | st->at(r, c)[1] = dy * dy; // G 256 | st->at(r, c)[2] = dx * dy; // F 257 | } 258 | } 259 | return; 260 | } else if ((3 == image_.channels()) || (4 == image_.channels())) { 261 | // BGR color space Gradient calculation. 262 | vector bgr_chnnels; 263 | cv::split(bimage_, bgr_chnnels); 264 | cv::Mat gx[3], gy[3]; 265 | for (int k = 0; k < 3; ++k) { 266 | cv::Sobel(bgr_chnnels[k], gx[k], CV_32F, 1, 0); 267 | cv::Sobel(bgr_chnnels[k], gy[k], CV_32F, 0, 1); 268 | } 269 | cv::Vec3f fx, fy; 270 | for (int r = 0; r < rows_; ++r) { 271 | for (int c = 0; c < cols_; ++c) { 272 | fx = cv::Vec3f(gx[0].at(r, c), 273 | gx[1].at(r, c), 274 | gx[2].at(r, c)); 275 | fy = cv::Vec3f(gy[0].at(r, c), 276 | gy[1].at(r, c), 277 | gy[2].at(r, c)); 278 | st->at(r, c)[0] = fx.dot(fx); // E 279 | st->at(r, c)[1] = fy.dot(fy); // G 280 | st->at(r, c)[2] = fx.dot(fy); // F 281 | } 282 | } 283 | } else { 284 | return; 285 | } 286 | } 287 | 288 | // Visualize a vector field by using LIC (Linear Integral Convolution). 289 | void CoherentLine::VisualizeByLIC(const cv::Mat& vf) { 290 | assert(vf.channels() >= 2); 291 | vector vector_field; 292 | cv::split(vf, vector_field); 293 | cv::Mat white_noise(rows_, cols_, CV_8UC1); 294 | cv::Mat show_field(rows_, cols_, CV_8UC1); 295 | for (int r = 0; r < rows_; ++r) { 296 | for (int c = 0; c < cols_; ++c) { 297 | int n = rand(); 298 | n = ((n & 0xff) + ((n & 0xff00) >> 8 )) & 0xff; 299 | white_noise.at(r, c) = static_cast(n); 300 | } 301 | } 302 | float p_LUT0[DISCRETE_FILTER_SIZE], p_LUT1[DISCRETE_FILTER_SIZE]; 303 | for (int i = 0; i < DISCRETE_FILTER_SIZE; ++i) { 304 | p_LUT0[i] = p_LUT1[i] = i; 305 | } 306 | 307 | // Do LIC. 308 | float krnlen = LOWPASS_FILTR_LENGTH; 309 | int advDir; ///ADVection DIRection (0: positive; 1: negative) 310 | int advcts; ///number of ADVeCTion stepS per direction (a step counter) 311 | int ADVCTS = int(krnlen * 3); ///MAXIMUM number of advection steps per direction to break dead loops 312 | 313 | float vctr_x; ///x-component of the VeCToR at the forefront point 314 | float vctr_y; ///y-component of the VeCToR at the forefront point 315 | float clp0_x; ///x-coordinate of CLiP point 0 (current) 316 | float clp0_y; ///y-coordinate of CLiP point 0 (current) 317 | float clp1_x; ///x-coordinate of CLiP point 1 (next ) 318 | float clp1_y; ///y-coordinate of CLiP point 1 (next ) 319 | float samp_x; ///x-coordinate of the SAMPle in the current pixel 320 | float samp_y; ///y-coordinate of the SAMPle in the current pixel 321 | float tmpLen; ///TeMPorary LENgth of a trial clipped-segment 322 | float segLen; ///SEGment LENgth 323 | float curLen; ///CURrent LENgth of the streamline 324 | float prvLen; ///PReVious LENgth of the streamline 325 | float W_ACUM; ///ACcuMulated Weight from the seed to the current streamline forefront 326 | float texVal; ///TEXture VALue 327 | float smpWgt; ///WeiGhT of the current SaMPle 328 | float t_acum[2]; ///two ACcUMulated composite Textures for the two directions, perspectively 329 | float w_acum[2]; ///two ACcUMulated Weighting values for the two directions, perspectively 330 | float* wgtLUT = NULL; ///WeiGhT Look Up Table pointing to the target filter LUT 331 | float len2ID = (DISCRETE_FILTER_SIZE - 1) / krnlen; ///map a curve LENgth TO an ID in the LUT 332 | 333 | for (int r = 0; r < rows_; ++r) { 334 | for (int c = 0; c < cols_; ++c) { 335 | ///init the composite texture accumulators and the weight accumulators/// 336 | t_acum[0] = t_acum[1] = w_acum[0] = w_acum[1] = 0.0f; 337 | ///for either advection direction/// 338 | for(advDir = 0; advDir < 2; advDir ++) { 339 | advcts = 0; 340 | curLen = 0.0f; 341 | clp0_x = c + 0.5f; 342 | clp0_y = r + 0.5f; 343 | ///access the target filter LUT/// 344 | wgtLUT = (advDir == 0) ? p_LUT0 : p_LUT1; 345 | ///until the streamline is advected long enough or a tightly spiralling center / focus is encountered/// 346 | while (curLen < krnlen && advcts < ADVCTS) { 347 | ///access the vector at the sample/// 348 | vctr_x = vector_field[0].at(r, c); 349 | vctr_y = vector_field[1].at(r, c); 350 | ///in case of a critical point/// 351 | if (vctr_x == 0.0f && vctr_y == 0.0f) { 352 | t_acum[advDir] = (advcts == 0) ? 0.0f : t_acum[advDir]; ///this line is indeed unnecessary 353 | w_acum[advDir] = (advcts == 0) ? 1.0f : w_acum[advDir]; 354 | break; 355 | } 356 | ///negate the vector for the backward-advection case/// 357 | vctr_x = (advDir == 0) ? vctr_x : -vctr_x; 358 | vctr_y = (advDir == 0) ? vctr_y : -vctr_y; 359 | // clip the segment against the pixel boundaries 360 | // --- find the shorter from the two clipped segments. 361 | // replace all if-statements whenever possible as 362 | // they might affect the computational speed. 363 | segLen = LINE_SQUARE_CLIP_MAX; 364 | segLen = (vctr_x < -VECTOR_COMPONENT_MIN) ? 365 | (int(clp0_x) - clp0_x ) / vctr_x : segLen; 366 | segLen = (vctr_x > VECTOR_COMPONENT_MIN) ? 367 | (int(int(clp0_x) + 1.5f) - clp0_x) / vctr_x : segLen; 368 | segLen = (vctr_y < -VECTOR_COMPONENT_MIN) ? 369 | (((tmpLen = (int(clp0_y) - clp0_y) / vctr_y) < segLen) ? tmpLen : segLen) : segLen; 370 | segLen = (vctr_y > VECTOR_COMPONENT_MIN) ? 371 | (((tmpLen = (int(int(clp0_y) + 1.5f) - clp0_y) / vctr_y) < segLen) ? tmpLen : segLen) : segLen; 372 | ///update the curve-length measurers/// 373 | prvLen = curLen; 374 | curLen+= segLen; 375 | segLen+= 0.0004f; 376 | ///check if the filter has reached either end/// 377 | segLen = (curLen > krnlen) ? ( (curLen = krnlen) - prvLen ) : segLen; 378 | ///obtain the next clip point/// 379 | clp1_x = clp0_x + vctr_x * segLen; 380 | clp1_y = clp0_y + vctr_y * segLen; 381 | ///obtain the middle point of the segment as the texture-contributing sample/// 382 | samp_x = (clp0_x + clp1_x) * 0.5f; 383 | samp_y = (clp0_y + clp1_y) * 0.5f; 384 | ///obtain the texture value of the sample/// 385 | texVal = static_cast(white_noise.at(samp_y, samp_x)); 386 | ///update the accumulated weight and the accumulated composite texture (texture x weight)/// 387 | W_ACUM = wgtLUT[int(curLen * len2ID)]; 388 | smpWgt = W_ACUM - w_acum[advDir]; 389 | w_acum[advDir] = W_ACUM; 390 | t_acum[advDir] += texVal * smpWgt; 391 | ///update the step counter and the "current" clip point/// 392 | advcts ++; 393 | clp0_x = clp1_x; 394 | clp0_y = clp1_y; 395 | ///check if the streamline has gone beyond the flow field/// 396 | if (clp0_x < 0.0f || clp0_x >= cols_ || clp0_y < 0.0f || clp0_y >= rows_) 397 | break; 398 | } 399 | } 400 | ///normalize the accumulated composite texture/// 401 | texVal = (t_acum[0] + t_acum[1]) / (w_acum[0] + w_acum[1]); 402 | ///clamp the texture value against the displayable intensity range [0, 255] 403 | texVal = (texVal < 0.0f) ? 0.0f : texVal; 404 | texVal = (texVal > 255.0f) ? 255.0f : texVal; 405 | show_field.at(r, c) = static_cast(texVal); 406 | } 407 | } 408 | 409 | cv::imshow("Visualized Field", show_field); 410 | cv::waitKey(); 411 | } 412 | 413 | void CoherentLine::VisualizeByArrow(const cv::Mat &vf) { 414 | cv::Mat show_filed = image_.clone(); 415 | float angle, dx, dy, mag; 416 | cv::Point2d p, q; 417 | for (int r = 0; r < rows_; r += 10) { 418 | for (int c = 0; c < cols_; c += 10) { 419 | dx = vf.at(r, c)[0]; 420 | dy = vf.at(r, c)[1]; 421 | mag = vf.at(r, c)[2]; 422 | if (mag > 0) { 423 | if (fabs(dx) < 0.0000001) 424 | angle = PI / 2; 425 | else 426 | angle = atan2f(dy, dx); 427 | p = cv::Point2d(c, r); 428 | q = cv::Point2d(c - (int)(0.5 * mag * cos(angle)), 429 | r - (int)(0.5 * mag * sin(angle))); 430 | cv::line(show_filed, p, q, cv::Scalar(0, 0, 255)); 431 | p.x = (int) (q.x + 0.1 * mag * cos(angle + PI / 5)); 432 | p.y = (int) (q.y + 0.1 * mag * sin(angle + PI / 5)); 433 | cv::line(show_filed, p, q, cv::Scalar(0, 0, 255)); 434 | p.x = (int) (q.x + 0.1 * mag * cos(angle - PI / 5)); 435 | p.y = (int) (q.y + 0.1 * mag * sin(angle - PI / 5)); 436 | cv::line(show_filed, p, q, cv::Scalar(0, 0, 255)); 437 | } 438 | } 439 | } 440 | cv::imshow("Visualized Field", show_filed); 441 | cv::waitKey(); 442 | } 443 | 444 | void CoherentLine::GetCannyEdge() { 445 | cv::Canny(gray_, canny_edge_, 60, 120); 446 | canny_edge_ = 255 - canny_edge_; 447 | cv::imshow("Canny", canny_edge_); 448 | cv::waitKey(); 449 | } 450 | -------------------------------------------------------------------------------- /AbstractLine/CoherentLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // CoherentLine.h 3 | // AbstractLine 4 | // 5 | // Created by Zhipeng Wu on 5/22/13. 6 | // Copyright (c) 2013 Zhipeng Wu. All rights reserved. 7 | // 8 | // Kyprianidis, J. E., & Döllner, J. (2008). 9 | // Image Abstraction by Structure Adaptive Filtering. 10 | // In: Proc. EG UK Theory and Practice of Computer Graphics, pp. 51–58. 11 | // 12 | // H. Kang, S. Lee, C. Chui. 13 | // "Coherent Line Drawing". 14 | // Proc. ACM Symposium on Non-photorealistic Animation and Rendering, 15 | // pp. 43-50, San Diego, CA, August, 2007. 16 | 17 | #ifndef __AbstractLine__CoherentLine__ 18 | #define __AbstractLine__CoherentLine__ 19 | 20 | #include "thing.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | using std::map; 38 | using std::pair; 39 | using std::string; 40 | using std::vector; 41 | using std::cout; 42 | using std::endl; 43 | 44 | class CoherentLine { 45 | public: 46 | CoherentLine(const string& img_path) { 47 | srand (static_cast(time(NULL))); 48 | image_ = cv::imread(img_path, 1); 49 | cv::cvtColor(image_, gray_, CV_BGR2GRAY); 50 | rows_ = gray_.rows; 51 | cols_ = gray_.cols; 52 | cv::bilateralFilter(image_, bimage_, 6, 150, 150); 53 | cv::cvtColor(bimage_, bgray_, CV_BGR2GRAY); 54 | cout << "CoherentLine object constructed." << endl; 55 | } 56 | CoherentLine(const cv::Mat& image) { 57 | srand (static_cast(time(NULL))); 58 | image_ = image.clone(); 59 | cv::cvtColor(image_, gray_, CV_BGR2GRAY); 60 | rows_ = gray_.rows; 61 | cols_ = gray_.cols; 62 | cv::bilateralFilter(image_, bimage_, 6, 150, 150); 63 | cv::cvtColor(bimage_, bgray_, CV_BGR2GRAY); 64 | cout << "CoherentLine object constructed." << endl; 65 | } 66 | // Accessors: 67 | const int rows() const { 68 | return rows_; 69 | } 70 | const int cols() const { 71 | return cols_; 72 | } 73 | const cv::Mat& image() const { 74 | return image_; 75 | } 76 | const cv::Mat& gray() const { 77 | return gray_; 78 | } 79 | const cv::Mat& etf() { 80 | if (etf_.empty()) 81 | GetEdegTangentFlow(); 82 | return etf_; 83 | } 84 | const cv::Mat& dog_edge() { 85 | if (dog_edge_.empty()) 86 | GetDogEdge(); 87 | return dog_edge_; 88 | } 89 | const cv::Mat& fdog_edge() { 90 | if (fdog_edge_.empty()) 91 | GetFDogEdge(); 92 | return fdog_edge_; 93 | } 94 | const cv::Mat& canny_edge() { 95 | if (canny_edge_.empty()) 96 | GetCannyEdge(); 97 | return canny_edge_; 98 | } 99 | 100 | private: 101 | 102 | // Members: 103 | int cols_; 104 | int rows_; 105 | cv::Mat image_; // input image. 106 | cv::Mat gray_; // gray-scale image. 107 | cv::Mat bimage_; // bilateral blured input image. 108 | cv::Mat bgray_; // bilateral blured gray-scale image. 109 | cv::Mat etf_; // edge tangent flow. 110 | cv::Mat dog_edge_; // edge response by using DoG operation. 111 | cv::Mat fdog_edge_; // blur dog edge with edge tangent flow. 112 | cv::Mat canny_edge_; // canny edge detection. 113 | cv::Mat thin_edge_; // edge thinning result. 114 | 115 | // Functions: 116 | void GetEdegTangentFlow(); 117 | void GetDogEdge(); 118 | void GetStepEdge(); 119 | void GetFDogEdge(); 120 | void CalcStructureTensor(cv::Mat* st); 121 | void VisualizeByLIC(const cv::Mat& vf); 122 | void VisualizeByArrow(const cv::Mat& vf); 123 | void GetCannyEdge(); 124 | 125 | 126 | 127 | // disallow evil copy and assign. 128 | CoherentLine(const CoherentLine&); 129 | void operator= (const CoherentLine&); 130 | }; 131 | 132 | 133 | 134 | #endif /* defined(__AbstractLine__CoherentLine__) */ 135 | -------------------------------------------------------------------------------- /AbstractLine/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // main.cpp 3 | // AbstractLine 4 | // 5 | // Created by Zhipeng Wu on 5/22/13. 6 | // Copyright (c) 2013 Zhipeng Wu. All rights reserved. 7 | // 8 | 9 | #include 10 | #include "CoherentLine.h" 11 | #include "potrace_adaptor.h" 12 | #include "potrace_loader.h" 13 | 14 | int main(int argc, const char * argv[]) 15 | { 16 | 17 | // insert code here... 18 | // string img_file = "/Users/WU/Pictures/others/lena.jpg"; 19 | string img_file = "/Users/WU/Pictures/others/meguro.jpg"; 20 | string svg_file = "/Users/WU/Pictures/others/meguro.svg"; 21 | string line_file = "/Users/WU/Pictures/others/meguro_line.jpg"; 22 | CoherentLine cl(img_file); 23 | cv::Mat dog_edge = cl.dog_edge(); 24 | cv::imshow("dog", dog_edge); 25 | cv::waitKey(); 26 | cv::Mat fdog_edge = cl.fdog_edge(); 27 | cv::imshow("fdog", fdog_edge); 28 | cv::waitKey(); 29 | cv::imwrite(line_file, fdog_edge); 30 | ExecPotraceAndSaveSVG(fdog_edge, svg_file); 31 | cv::Mat vec_edge = ExecPotrace(fdog_edge); 32 | cv::imshow("vectorization", vec_edge); 33 | cv::waitKey(); 34 | cv::Mat canvas(fdog_edge.size(), CV_8UC3); 35 | canvas = cv::Scalar::all(255); 36 | potrace_state_t* my_state = Raster2Vector(fdog_edge); 37 | ShowPath(my_state->plist, &canvas); 38 | potrace_state_free(my_state); 39 | cv::waitKey(); 40 | return 0; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /AbstractLine/potrace_adaptor.h: -------------------------------------------------------------------------------- 1 | // 2 | // potrace_adaptor.h 3 | // potrace_test 4 | // 5 | // Created by Zhipeng Wu on 5/29/13. 6 | // Copyright (c) 2013 Zhipeng Wu. All rights reserved. 7 | // 8 | 9 | #ifndef potrace_test_potrace_adaptor_h 10 | #define potrace_test_potrace_adaptor_h 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using std::string; 18 | using std::cout; 19 | using std::endl; 20 | using std::system; 21 | 22 | const string kTempFolder = "/var/folders/_m/bhc8k8f971dbd_qkl27bh3yw0000gn/T/"; 23 | 24 | static inline string GetTitle(const string& filename) { 25 | size_t found1 = filename.find_last_of("/"); 26 | size_t found2 = filename.find_last_of("."); 27 | if ((string::npos == found1) || (string::npos == found1)) { 28 | cout << "File title parsing error..." << endl; 29 | return ""; 30 | } 31 | return filename.substr(found1 + 1, found2 - found1 - 1); 32 | } 33 | 34 | // Given an input image path, return the vectorized version. 35 | static inline cv::Mat ExecPotrace(const string& file_path) { 36 | string file_title = GetTitle(file_path); 37 | string bmp_path = kTempFolder + file_title + ".bmp"; 38 | string pgm_path = kTempFolder + file_title + ".pgm"; 39 | cv::Mat input_img = cv::imread(file_path, 0); 40 | if (input_img.empty()) { 41 | cout << "Input image error..."; 42 | return cv::Mat(); 43 | } 44 | cv::imwrite(bmp_path, input_img); 45 | string potrace_arg = "/usr/local/bin/potrace -g -z minority -t 10.000000 -a 1.300000 -O \ 46 | 0.200000 -u 10.000000 -k 0.500000 -o" + pgm_path + " " + bmp_path; 47 | system(potrace_arg.c_str()); 48 | cv::Mat output_img = cv::imread(pgm_path, 0); 49 | return output_img; 50 | } 51 | 52 | // Given an input image, return the vectorized version. 53 | static inline cv::Mat ExecPotrace(const cv::Mat& input_img) { 54 | string bmp_path = kTempFolder + "temp_potrace.bmp"; 55 | string pgm_path = kTempFolder + "temp_potrace.pgm"; 56 | cv::imwrite(bmp_path, input_img); 57 | string potrace_arg = "/usr/local/bin/potrace -g -z minority -t 10.000000 -a 1.300000 -O \ 58 | 0.200000 -u 10.000000 -k 0.500000 -o" + pgm_path + " " + bmp_path; 59 | system(potrace_arg.c_str()); 60 | cv::Mat output_img = cv::imread(pgm_path, 0); 61 | return output_img; 62 | } 63 | 64 | // Given an input image, save the vectorized version (.svg). 65 | static inline void ExecPotraceAndSaveSVG(const cv::Mat& input_img, 66 | const string& svg_path) { 67 | string bmp_path = kTempFolder + "temp_potrace.bmp"; 68 | cv::imwrite(bmp_path, input_img); 69 | string potrace_arg = "/usr/local/bin/potrace -s -z minority -t 10.000000 -a 1.300000 -O \ 70 | 0.200000 -u 10.000000 -k 0.500000 -o" + svg_path + " " + bmp_path; 71 | system(potrace_arg.c_str()); 72 | } 73 | 74 | // Given an input image path, save the vectorized version (.svg). 75 | static inline void ExecPotraceAndSaveSVG(const string& file_path, 76 | const string& svg_path) { 77 | string file_title = GetTitle(file_path); 78 | string bmp_path = kTempFolder + file_title + ".bmp"; 79 | cv::Mat input_img = cv::imread(file_path, 0); 80 | if (input_img.empty()) { 81 | cout << "Input image error..."; 82 | return; 83 | } 84 | cv::imwrite(bmp_path, input_img); 85 | string potrace_arg = "/usr/local/bin/potrace -s -z minority -t 10.000000 -a 1.300000 -O \ 86 | 0.200000 -u 10.000000 -k 0.500000 -o" + svg_path + " " + bmp_path; 87 | system(potrace_arg.c_str()); 88 | } 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /AbstractLine/potrace_loader.h: -------------------------------------------------------------------------------- 1 | // 2 | // potrace_loader.h 3 | // potrace_test 4 | // 5 | // Created by Zhipeng Wu on 5/29/13. 6 | // Copyright (c) 2013 Zhipeng Wu. All rights reserved. 7 | // 8 | 9 | #ifndef potrace_test_potrace_loader_h 10 | #define potrace_test_potrace_loader_h 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | using std::string; 20 | using std::cout; 21 | using std::endl; 22 | using std::system; 23 | using std::vector; 24 | 25 | const int kPrecision = 100; 26 | int counter = 0; 27 | cv::Scalar pen = CV_RGB(0, 0, 0); 28 | 29 | /* macros for writing individual bitmap pixels */ 30 | #define BM_WORDSIZE ((int)sizeof(potrace_word)) 31 | #define BM_WORDBITS (8*BM_WORDSIZE) 32 | #define BM_HIBIT (((potrace_word)1)<<(BM_WORDBITS-1)) 33 | #define bm_scanline(bm, y) ((bm)->map + (y)*(bm)->dy) 34 | #define bm_index(bm, x, y) (&bm_scanline(bm, y)[(x)/BM_WORDBITS]) 35 | #define bm_mask(x) (BM_HIBIT >> ((x) & (BM_WORDBITS-1))) 36 | #define bm_range(x, a) ((int)(x) >= 0 && (int)(x) < (a)) 37 | #define bm_safe(bm, x, y) (bm_range(x, (bm)->w) && bm_range(y, (bm)->h)) 38 | #define BM_USET(bm, x, y) (*bm_index(bm, x, y) |= bm_mask(x)) 39 | #define BM_UCLR(bm, x, y) (*bm_index(bm, x, y) &= ~bm_mask(x)) 40 | #define BM_UPUT(bm, x, y, b) ((b) ? BM_USET(bm, x, y) : BM_UCLR(bm, x, y)) 41 | #define BM_PUT(bm, x, y, b) (bm_safe(bm, x, y) ? BM_UPUT(bm, x, y, b) : 0) 42 | 43 | /* return new un-initialized bitmap. NULL with errno on error */ 44 | static potrace_bitmap_t *bm_new(int w, int h) { 45 | potrace_bitmap_t *bm; 46 | int dy = (w + BM_WORDBITS - 1) / BM_WORDBITS; 47 | 48 | bm = (potrace_bitmap_t *) malloc(sizeof(potrace_bitmap_t)); 49 | if (!bm) { 50 | return NULL; 51 | } 52 | bm->w = w; 53 | bm->h = h; 54 | bm->dy = dy; 55 | bm->map = (potrace_word *) malloc(dy * h * BM_WORDSIZE); 56 | if (!bm->map) { 57 | free(bm); 58 | return NULL; 59 | } 60 | return bm; 61 | } 62 | 63 | /* free a bitmap */ 64 | static void bm_free(potrace_bitmap_t *bm) { 65 | if (bm != NULL) { 66 | free(bm->map); 67 | } 68 | free(bm); 69 | } 70 | 71 | // Given an input image, return the tracing result in potrace_state_t. 72 | static potrace_state_t* Raster2Vector(const cv::Mat& input_img) { 73 | cv::Mat mat_img; 74 | if (input_img.channels() != 1) { 75 | cv::cvtColor(input_img, mat_img, CV_BGR2GRAY); 76 | } else { 77 | input_img.copyTo(mat_img); 78 | } 79 | mat_img.convertTo(mat_img, CV_8UC1); 80 | 81 | // Step 1: load image content to potrace_bitmap_t. 82 | potrace_bitmap_t* bitmap_img; 83 | bitmap_img = bm_new(mat_img.cols, mat_img.cols); 84 | for(int j = 0; j < bitmap_img->dy * bitmap_img->h; ++j) 85 | bitmap_img->map[j] = 0; 86 | // set the pixel values. 87 | for(int y = mat_img.rows - 1; y >= 0; --y){ 88 | // for(int j = 0 - 1; j < mat_img.rows; ++j){ 89 | for(int x = 0; x < mat_img.cols; ++x){ 90 | // We treat white as background and black as foreground. 91 | if (mat_img.at(y, x) < 128) { 92 | BM_PUT(bitmap_img, x, y, 1); 93 | } 94 | } 95 | } 96 | 97 | // Step 2: calculate potrace_state_t and return. 98 | static potrace_param_t *parameters = potrace_param_default(); 99 | parameters->opticurve = 1; 100 | parameters->opttolerance = 0.2; 101 | parameters->turnpolicy = POTRACE_TURNPOLICY_MINORITY; 102 | parameters->turdsize = 10; 103 | parameters->alphamax = 1; 104 | //freed when program terminates (potrace_state_free) 105 | potrace_state_t* states = new potrace_state_t(); 106 | states = potrace_trace(parameters, bitmap_img); 107 | if (!states || states->status != POTRACE_STATUS_OK) { 108 | cout << "Error in tracing..." << endl; 109 | } 110 | 111 | bm_free(bitmap_img); 112 | potrace_param_free(parameters); 113 | return states; 114 | } 115 | 116 | 117 | // Given an image path, return the tracing result in potrace_state_t. 118 | static inline potrace_state_t* Raster2Vector(const string& file_path) { 119 | cv::Mat input_img = cv::imread(file_path, 0); 120 | if (input_img.empty()) { 121 | cout << "Input image error..."; 122 | return NULL; 123 | } 124 | return Raster2Vector(input_img); 125 | } 126 | 127 | static inline cv::Point PointAdd(const cv::Point& p, const cv::Point& q) { 128 | return cv::Point(p.x + q.x, p.y + q.y); 129 | } 130 | static inline cv::Point PointTimes(float c, const cv::Point& p) { 131 | return cv::Point(p.x * c, p.y * c); 132 | } 133 | static inline cv::Point Bernstein(float u, 134 | const cv::Point& p0, 135 | const cv::Point& p1, 136 | const cv::Point& p2, 137 | const cv::Point& p3) { 138 | cv::Point a, b, c, d, r; 139 | 140 | a = PointTimes(pow(u,3), p0); 141 | b = PointTimes(3 * pow(u,2) * (1-u), p1); 142 | c = PointTimes(3 * u * pow((1-u), 2), p2); 143 | d = PointTimes(pow((1-u), 3), p3); 144 | return PointAdd(PointAdd(a, b), PointAdd(c, d)); 145 | } 146 | 147 | static void DrawBezier(cv::Mat* canvas, 148 | const cv::Point a, 149 | const cv::Point u, 150 | const cv::Point w, 151 | const cv::Point b, 152 | const cv::Scalar& pen, 153 | vector* contour) { 154 | cv::Point pre, now; 155 | pre = a; 156 | for (int i = 0; i <= kPrecision; ++i) { 157 | contour->push_back(pre); 158 | float t = 1 - static_cast(i) / kPrecision; 159 | now = Bernstein(t, a, u, w, b); 160 | cv::line(*canvas, pre, now, pen); 161 | pre = now; 162 | } 163 | } 164 | // Plot curve on the canvas. 165 | static void PlotCurve(cv::Mat* canvas, 166 | const potrace_curve_t& curve, 167 | const cv::Scalar& pen, 168 | vector* contour) { 169 | if (NULL == canvas) 170 | return; 171 | cv::Point a, u, w, b, begin; 172 | begin = cv::Point(curve.c[curve.n - 1][2].x, 173 | curve.c[curve.n - 1][2].y); 174 | for (int i = 0; i < curve.n; ++i) { 175 | // Satrt point. 176 | a = i ? cv::Point(curve.c[i - 1][2].x, curve.c[i - 1][2].y) : begin; 177 | // Control point 1. 178 | u = cv::Point(curve.c[i][0].x, curve.c[i][0].y); 179 | // Control point 2. 180 | w = cv::Point(curve.c[i][1].x, curve.c[i][1].y); 181 | // End point. 182 | b = cv::Point(curve.c[i][2].x, curve.c[i][2].y); 183 | cv::circle(*canvas, a, 2, cv::Scalar(0, 0, 255), -1); 184 | cv::circle(*canvas, w, 2, cv::Scalar(0, 255, 0), -1); 185 | cv::circle(*canvas, b, 2, cv::Scalar(0, 0, 255), -1); 186 | 187 | if (POTRACE_CURVETO == curve.tag[i]) { 188 | cv::circle(*canvas, u, 2, cv::Scalar(0, 255, 0), -1); 189 | DrawBezier(canvas, a, u, w, b, pen, contour); 190 | 191 | } else if (POTRACE_CORNER == curve.tag[i]) { 192 | contour->push_back(a); 193 | contour->push_back(w); 194 | cv::line(*canvas, a, w, pen, 1); 195 | cv::line(*canvas, w, b, pen, 1); 196 | } 197 | cv::imshow("canvas", *canvas); 198 | cv::waitKey(100); 199 | } 200 | contour->push_back(begin); 201 | } 202 | 203 | static inline void ShowPath(potrace_path_t* my_path, cv::Mat* canvas) { 204 | potrace_path_t *p; 205 | for (p = my_path; p; p = p->sibling) { 206 | // Show details. 207 | cout << "path: " << counter++ << ",\tarea: " 208 | << p->area << ",\tsign: " << (char)p->sign 209 | << ",\tcurve: " << p->curve.n << endl; 210 | // Draw curve. 211 | vector > contours; 212 | vector contour; 213 | PlotCurve(canvas, p->curve, pen, &contour); 214 | // Fill region. 215 | contours.push_back(contour); 216 | if (my_path->sign == '+') { 217 | cv::drawContours(*canvas, contours, -1, cv::Scalar(0, 0, 0), -1); 218 | } else { 219 | cv::drawContours(*canvas, contours, -1, cv::Scalar(255, 255, 255), -1); 220 | } 221 | ShowPath(p->childlist, canvas); 222 | cv::imshow("canvas", *canvas); 223 | } 224 | } 225 | 226 | #endif 227 | -------------------------------------------------------------------------------- /AbstractLine/thing.h: -------------------------------------------------------------------------------- 1 | // 2 | // thing.h 3 | // AbstractLine 4 | // 5 | // Created by Zhipeng Wu on 5/28/13. 6 | // Copyright (c) 2013 Zhipeng Wu. All rights reserved. 7 | // 8 | // http://opencv-code.com/quick-tips/implementation-of-thinning-algorithm-in-opencv/ 9 | // http://opencv-code.com/quick-tips/implementation-of-guo-hall-thinning-algorithm/ 10 | 11 | 12 | #ifndef AbstractLine_thing_h 13 | #define AbstractLine_thing_h 14 | 15 | #include 16 | 17 | // Pixels: 18 | // 1: foregrounf 19 | // 0: background 20 | static cv::Mat ZhangThinningIteration (const cv::Mat& im, int iter) { 21 | assert(CV_8UC1 == im.type()); 22 | cv::Mat marker = cv::Mat::zeros(im.size(), CV_8UC1); 23 | for (int i = 1; i < im.rows-1; i++) { 24 | for (int j = 1; j < im.cols-1; j++) { 25 | uchar p2 = im.at(i-1, j); 26 | uchar p3 = im.at(i-1, j+1); 27 | uchar p4 = im.at(i, j+1); 28 | uchar p5 = im.at(i+1, j+1); 29 | uchar p6 = im.at(i+1, j); 30 | uchar p7 = im.at(i+1, j-1); 31 | uchar p8 = im.at(i, j-1); 32 | uchar p9 = im.at(i-1, j-1); 33 | 34 | int A = (p2 == 0 && p3 == 1) + (p3 == 0 && p4 == 1) + 35 | (p4 == 0 && p5 == 1) + (p5 == 0 && p6 == 1) + 36 | (p6 == 0 && p7 == 1) + (p7 == 0 && p8 == 1) + 37 | (p8 == 0 && p9 == 1) + (p9 == 0 && p2 == 1); 38 | int B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9; 39 | int m1 = iter == 0 ? (p2 * p4 * p6) : (p2 * p4 * p8); 40 | int m2 = iter == 0 ? (p4 * p6 * p8) : (p2 * p6 * p8); 41 | 42 | if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0) 43 | marker.at(i,j) = 1; 44 | } 45 | } 46 | return im & ~marker; 47 | } 48 | 49 | static cv::Mat ZhangThinning(const cv::Mat& im) { 50 | cv::Mat prev = cv::Mat::zeros(im.size(), CV_8UC1); 51 | if (im.channels() != 1) 52 | return prev; 53 | cv::Mat img, temp, diff; 54 | im.convertTo(img, CV_8UC1); 55 | img /= 255; 56 | do { 57 | temp = ZhangThinningIteration(img, 0); 58 | img = ZhangThinningIteration(temp, 1); 59 | cv::absdiff(img, prev, diff); 60 | img.copyTo(prev); 61 | } while (cv::countNonZero(diff) > 0); 62 | return img * 255; 63 | } 64 | 65 | 66 | #endif 67 | --------------------------------------------------------------------------------