v2_ test_lab setup
[The-Artvertiser.git] / garfeild / lightcalib / lightmap.cpp
blobfeb6d2b4cdd3183ccdf6d0a0fa3d8a2037837d54
1 #include <stdio.h>
2 #include <iostream>
3 #include <fstream>
5 //also includes opengl if available
6 #include "lightmap.h"
7 #include <highgui.h>
10 #ifndef M_PI
11 #define M_PI 3.1415926535897932384626433832795
12 #endif
14 using namespace std;
16 LightMap::LightMap()
18 ARB=true;
19 initialized=false;
20 lightParams=0;
21 normals=0;
22 nbCam=0;
25 LightMap::LightMap(const LightMap &a)
27 ARB=a.ARB;
28 initialized=false;
29 if (a.map.getIm())
30 map.setImage(cvCloneImage(a.map.getIm()));
31 reflc.copy(a.reflc);
32 lightParams=0;
33 normals=0;
34 nbCam=0;
37 LightMap::~LightMap()
39 IplImage *im = map.getIm();
40 if(im) cvReleaseImage(&im);
41 if (lightParams) cvReleaseMat(&lightParams);
42 if (normals) delete normals;
45 void LightMap::clear()
47 IplImage* im = map.getIm();
48 if ( im ) cvReleaseImage( &im );
49 map.setImage(0);
51 if (normals) delete normals;
52 normals = 0;
54 if (lightParams)
55 cvReleaseMat(&lightParams);
56 lightParams=0;
60 bool LightMap::init(int nbCam, IplImage *model, float corners[4][2], int nx, int ny)
62 IplImage *im = map.getIm();
63 if(im) cvReleaseImage(&im);
64 map.setImage(cvCreateImage(cvSize(128,64), IPL_DEPTH_32F, model->nChannels));
65 cvSet(map.getIm(), cvScalarAll(.1));
66 if (model->nChannels == 3) {
67 im = map.getIm();
68 im->channelSeq[0]='B';
69 im->channelSeq[1]='G';
70 im->channelSeq[2]='R';
73 if (!model) return false;
75 reflc.genGrid(corners, nx, ny);
76 reflc.averageImage(model,0);
79 FILE *IRef= fopen("IRef.txt","w");
80 if (IRef) {
81 for (int i=0; i<reflc.nbTri; i++)
82 fprintf(IRef, "%.6f ", reflc.avg[i]);
83 fprintf(IRef, "\n");
84 fclose(IRef);
88 setCamNum(nbCam);
89 return reflc.avg != 0;
92 void LightMap::normal2uv(const float n[3], float uv[2]) {
93 float l = 1/sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
94 float n0 = n[0]*l;
95 float n1 = n[1]*l;
96 float n2 = n[2]*l;
97 uv[0] = n0*.25f +.25f + (n2>0 ? 0:.5f);
98 uv[1] = .5f+n1*.5f;
101 void LightMap::uv2normal(const float uv[2], float n[3]) {
102 n[0] = (uv[0]-(uv[0]>.5f ? .5f : 0)-.25f)*4;
103 n[1] = 2*uv[1]-1;
104 float len = n[0]*n[0] + n[1]*n[1];
105 if (len>=1) {
106 float f = 1/sqrt(len);
107 n[0]*=f;
108 n[1]*=f;
109 n[2]=(uv[0]>.5f ? -0.0f : 0.0f);
111 else n[2] = sqrt(1-len)*(uv[0]>.5f ? -1 : 1);
113 float l = 1/sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
114 n[0]*=l;
115 n[1]*=l;
116 n[2]*=l;
119 CvScalar LightMap::readMap(const float normal[3])
121 if (!isReady()) return cvScalarAll(1);
122 float uv[2];
123 normal2uv(normal, uv);
125 IplImage *im = map.getIm();
126 assert(im!=0);
127 int iu = cvRound(uv[0]*(double)im->width);
128 int iv = cvRound(uv[1]*(double)im->height);
129 if (iu<0) iu=0;
130 if (iv<0) iv=0;
131 if (iu>=im->width) iu = im->width-1;
132 if (iv>=im->height) iv = im->height-1;
133 return cvGet2D(im, iv, iu );
136 bool LightMap::addNormal(float normal[3], LightCollector &lc, int cam)
138 if (lightParams)
139 return addNormalLightMap(normal,lc,cam);
140 else
141 return addNormalCalib(normal,lc,cam);
144 const float *LightMap::getGain(int cam)
146 static const float ones[3] = {1, 1, 1};
147 if (!isReady()) return ones;
148 return &CV_MAT_ELEM(*lightParams, float, 2*cam, 0);
151 const float *LightMap::getBias(int cam)
153 static const float zeroes[3] = {0, 0, 0};
154 if (!isReady()) return zeroes;
155 return &CV_MAT_ELEM(*lightParams, float, 2*cam+1, 0);
158 bool LightMap::addNormalLightMap(float normal[3], LightCollector &lc, int cam)
160 if (lightParams==0) return false;
161 assert(map.getIm()!=0);
162 assert(cam < nbCam);
164 float l = 1/sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]);
165 normal[0]*=l;
166 normal[1]*=l;
167 normal[2]*=l;
169 float val[3];
170 if (lc.cmpWithRef(reflc, val, getGain(cam), getBias(cam)))
171 return updateLightMap(normal, val);
172 return false;
175 bool LightMap::updateLightMap(float normal[3], float *val)
177 if (val[0]<0) return false;
179 const int nc = map.getIm()->nChannels;
181 for (int y=0; y<map.getIm()->height; y++) {
182 float *line = (float *)(map.getIm()->imageData + y*map.getIm()->widthStep);
184 for (int x=0; x<map.getIm()->width; x++) {
185 float n[3];
186 float uv[2] = { float(x)/float(map.getIm()->width), float(y)/float(map.getIm()->height) };
187 uv2normal(uv, n);
188 float dot = n[0]*normal[0] + n[1]*normal[1] + n[2]*normal[2];
189 #if 0
190 if (dot>0) {
191 dot = acos(dot)*2/M_PI;
192 float s = .2;
193 float f = (1/sqrt(2*M_PI))*exp(-(1-dot)/(2*s*s));
194 line[x] = (1.0f-f)*line[x] + f*val;
196 #else
197 float min_dot=0;
198 if (dot>min_dot) {
199 //dot = pow(dot, 16)/2;
200 float angle = (float)(acos(dot)*2/M_PI);
201 //dot = (dot-min_dot)/(1-min_dot);
202 float s = .2f;
203 //float f = (1/sqrt(2*M_PI))*exp(-(1-dot)/(2*s*s));
204 //float f = (1/sqrt(2*M_PI))*exp(-(angle)/(2*s*s));
205 float f = exp(-(angle)/(2*s*s));
206 for (int c=0; c<nc; c++)
207 line[x*nc +c] = (1.0f-f)*line[x*nc + c] + f*val[c];
209 #endif
212 map.update();
213 return true;
216 #ifdef HAVE_GLEW
217 static void printShaderInfoLog(GLuint obj, const char *str, bool ARB)
219 GLint infologLength = 0;
220 GLint charsWritten = 0;
221 char *infoLog;
223 if (ARB) glGetObjectParameterivARB(obj, GL_INFO_LOG_LENGTH,&infologLength);
224 else glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
226 if (infologLength > 0)
228 infoLog = (char *)malloc(infologLength);
229 if (ARB) glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog);
230 else glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
231 if (strlen(infoLog)>0) {
232 if (str) printf("%s\n", str);
233 printf("%s\n",infoLog);
235 free(infoLog);
238 #endif
240 #ifdef HAVE_GLEW
241 static std::string readFile(const char * const file)
243 std::string ret = "";
244 FILE * fp = fopen(file, "rb");
245 if (!fp) return "";
246 fseek(fp, 0, SEEK_END);
247 int size = (int)ftell(fp);
248 fseek(fp, 0, SEEK_SET);
249 char * buf = new char[size + 1];
250 fread(buf, size, 1, fp);
251 fclose(fp);
252 buf[size] = 0;
253 ret = buf;
254 delete [] buf;
255 return ret;
258 static bool checkErrors(const char *file, int line, bool exitOnFailure=false)
260 GLenum error;
261 bool r=true;
262 while ((error = glGetError()) != GL_NO_ERROR) {
263 fprintf(stderr, "%s:%d: %s\n",
264 file, line,
265 (char *) gluErrorString(error));
266 if (exitOnFailure)
267 exit(-1);
268 r=false;
270 return r;
272 #endif
274 bool LightMap::initGL()
276 #ifdef HAVE_GLEW
277 if (initialized) return true;
278 glewInit();
279 initialized = true;
281 ARB = GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader;
282 if (!ARB && !glewIsSupported("GL_VERSION_2_0"))
284 printf("No GLSL support\n");
285 return false;
287 std::string ret;
288 const char * shaderProg;
290 bool ok=true;
291 ret = readFile("myvert.glsl");
292 shaderProg = ret.c_str();
294 if (ARB) g_vertShader = glCreateShaderObjectARB(GL_VERTEX_SHADER);
295 else g_vertShader = glCreateShader(GL_VERTEX_SHADER);
296 ok = ok && checkErrors(__FILE__,__LINE__);
298 if (ARB) glShaderSourceARB(g_vertShader, 1, &shaderProg, 0);
299 else glShaderSource(g_vertShader, 1, &shaderProg, 0);
300 printShaderInfoLog(g_vertShader, "Vertex shader myvert.glsl", ARB);
301 ok = ok && checkErrors(__FILE__,__LINE__);
303 if (ARB) glCompileShaderARB(g_vertShader);
304 else glCompileShader(g_vertShader);
306 printShaderInfoLog(g_vertShader, "Vertex shader myvert.glsl", ARB);
307 ok = ok && checkErrors(__FILE__,__LINE__);
309 ret = readFile("myfrag.glsl");
310 shaderProg = ret.c_str();
311 if (ARB) g_fragShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER);
312 else g_fragShader = glCreateShader(GL_FRAGMENT_SHADER);
314 if (ARB) glShaderSourceARB(g_fragShader, 1, &shaderProg, 0);
315 else glShaderSource(g_fragShader, 1, &shaderProg, 0);
317 if (ARB) glCompileShaderARB(g_fragShader);
318 else glCompileShader(g_fragShader);
320 printShaderInfoLog(g_fragShader, "Fragment shader myfrag.glsl", ARB);
321 ok = ok && checkErrors(__FILE__,__LINE__);
322 //if (!ok) return false;
324 if (ARB) g_shaderProgram = glCreateProgramObjectARB();
325 else g_shaderProgram = glCreateProgram();
327 if (ARB) glAttachObjectARB(g_shaderProgram, g_vertShader);
328 else glAttachShader(g_shaderProgram, g_vertShader);
330 if (ARB) glAttachObjectARB(g_shaderProgram, g_fragShader);
331 else glAttachShader(g_shaderProgram, g_fragShader);
333 if (ARB) glLinkProgramARB(g_shaderProgram);
334 else glLinkProgram(g_shaderProgram);
335 ok = ok && checkErrors(__FILE__,__LINE__);
337 if (ARB) glValidateProgramARB(g_shaderProgram);
338 else glValidateProgram(g_shaderProgram);
339 ok = ok && checkErrors(__FILE__,__LINE__);
340 printShaderInfoLog(g_shaderProgram, "glValidateProgram(g_shaderProgram)", ARB);
341 if (ok) {
342 cout << "GLSL shaders sucessfully initialized using "
343 << (ARB ? "ARB extensions" : "OpenGL 2.0") << ".\n";
344 } else
345 cout << "Failed to initialize GLSL shaders.\n";
346 return ok;
347 #else
348 return false;
349 #endif
352 void LightMap::enableShader(int cam, CvMat *obj2world)
354 #ifdef HAVE_GLEW
355 assert(cam < nbCam);
356 if (!initialized) {
357 if (!initGL()) return;
359 if (ARB) glUseProgramObjectARB(g_shaderProgram);
360 else glUseProgram(g_shaderProgram);
361 if (!checkErrors(__FILE__,__LINE__)) {
362 printShaderInfoLog(g_shaderProgram, "glUseProgram(g_shaderProgram)", ARB);
363 return;
366 GLuint shaderTexture, worldToObjectNormal, gain, bias;
367 if (ARB) {
368 shaderTexture = glGetUniformLocationARB(g_shaderProgram, "main_texture");
369 worldToObjectNormal = glGetUniformLocationARB(g_shaderProgram, "worldToObjectNormal");
370 gain = glGetUniformLocationARB(g_shaderProgram, "gain");
371 bias = glGetUniformLocationARB(g_shaderProgram, "bias");
372 } else {
373 shaderTexture = glGetUniformLocation(g_shaderProgram, "main_texture");
374 worldToObjectNormal = glGetUniformLocation(g_shaderProgram, "worldToObjectNormal");
375 gain = glGetUniformLocation(g_shaderProgram, "gain");
376 bias = glGetUniformLocation(g_shaderProgram, "bias");
378 checkErrors(__FILE__,__LINE__);
380 glActiveTexture(GL_TEXTURE0 + 0);
381 glBindTexture(GL_TEXTURE_2D, shaderTexture);
382 checkErrors(__FILE__,__LINE__);
384 if (ARB) glUniform1iARB(shaderTexture, 0);
385 else glUniform1i(shaderTexture, 0);
386 checkErrors(__FILE__,__LINE__);
388 // The sign change is required because we do not like OpenGL's Z axis direction.
389 // Thus, we flip all normals.
390 float mat[9];
391 for (int i=0; i<3; ++i)
392 for (int j=0; j<3; ++j)
393 mat[j*3 +i] = -(float)cvGet2D(obj2world, i, j).val[0];
395 float g[3],b[3];
396 for (int i=0;i<lightParams->cols; i++) {
397 if (lightParams) {
398 g[i] = 1/cvGet2D(lightParams, cam*2, i).val[0];
399 b[i] = g[i]*cvGet2D(lightParams, cam*2+1, i).val[0];
400 } else {
401 g[i] = 1;
402 b[i] = 0;
405 if (ARB) {
406 glUniform1iARB(shaderTexture, 0);
407 glUniformMatrix3fvARB(worldToObjectNormal, 1, GL_FALSE, mat);
408 glUniform4fARB(gain, g[2], g[1], g[0], 1);
409 glUniform4fARB(bias, b[2], b[1], b[0], 0);
410 } else {
411 glUniform1i(shaderTexture, 0);
412 glUniformMatrix3fv(worldToObjectNormal, 1, GL_FALSE, mat);
413 glUniform4f(gain, g[2], g[1], g[0], 1);
414 glUniform4f(bias, b[2], b[1], b[0], 0);
416 checkErrors(__FILE__,__LINE__);
417 map.loadTexture();
418 checkErrors(__FILE__,__LINE__);
419 #endif
422 void LightMap::disableShader()
424 #ifdef HAVE_GLEW
425 if (ARB) glUseProgramObjectARB(0);
426 else glUseProgram(0);
427 map.disableTexture();
428 #endif
431 void LightMap::setCamNum(int n)
433 obs.clear();
434 obs.reserve(300000);
436 if (normals) delete normals;
437 normals = new CvGrowMat(256,3, CV_32FC1);
438 normals->resize(0,3);
439 nbCam=n;
441 if (lightParams)
442 cvReleaseMat(&lightParams);
443 lightParams=0;
446 static void normalize(float normal[3])
448 float l = 1/sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]);
449 normal[0]*=l;
450 normal[1]*=l;
451 normal[2]*=l;
454 bool LightMap::addNormalCalib(float normal[3], LightCollector &lc, int cam)
456 assert(nbCam>0);
457 if (lc.avg==0 || reflc.avg==0) return false;
459 bool hasObs=false;
461 assert(lc.nbTri == reflc.nbTri);
463 int normIdx = normals->rows + nbCam*2;
464 bool newT=true;
465 float maxdot= .999;
467 normalize(normal);
469 // is the normal already in the database ?
470 for (int i=0; i<normals->rows; i++) {
471 float *n2 = (float *)CV_MAT_ELEM_PTR(*normals, i, 0);
472 float ndot = n2[0]*normal[0] +
473 n2[1]*normal[1] +
474 n2[2]*normal[2];
475 if (ndot > maxdot) {
476 newT=false;
477 maxdot = ndot;
478 normIdx = i + nbCam*2;
482 // if (newT) {
483 // obsMat->resize(obsMat->rows, obsMat->cols+1);
484 // }
486 for (int i=0; i<lc.nbTri; ++i) {
487 float *rv = reflc.avg+i*reflc.avgChannels;
488 float *v = lc.avg+i*lc.avgChannels;
489 bool ok=true;
490 for (int c=0; c<reflc.avgChannels; c++)
491 if (!(v[c]>5 && rv[c] >5 && v[c]<250 && rv[c] <250))
492 ok=false;
493 if (ok) {
494 hasObs = true;
496 Observation o;
497 o.camCol = cam*2;
498 o.normalCol = normIdx;
500 for (int c=0; c<reflc.avgChannels; c++) {
501 o.camVal[c] = - v[c]/255.0f;
502 o.normalVal[c] = rv[c]/255.0f;
504 obs.push_back(o);
508 if (newT&&hasObs){
509 // add the normal vector
510 normals->resize(normals->rows+1, normals->cols);
511 float *n = (float *)CV_MAT_ELEM_PTR(*normals, normals->rows-1,0);
512 n[0] = normal[0];
513 n[1] = normal[1];
514 n[2] = normal[2];
516 return true;
519 static bool saveMat(const CvMat *m, const char *fn)
521 ofstream f(fn);
523 if (!f.good()) return false;
525 for (int j=0; j<m->rows; j++) {
526 for (int i=0; i<m->cols; i++) {
527 double v = cvGet2D(m, j, i).val[0];
528 f << v;
529 if (i+1 < m->cols) f<<'\t';
531 f << endl;
533 f.close();
534 return true;
537 static CvGrowMat *loadMat(const char *fn)
539 ifstream f(fn);
541 if (!f.good()) return 0;
543 CvGrowMat *m = new CvGrowMat(128,3, CV_32FC1);
544 m->resize(1,1);
546 int nrow=0;
547 do {
548 char line[4096];
549 f.getline(line, 4095);
550 line[4095]=0;
551 if (!f.good()) break;
553 ++nrow;
555 int ncols=1;
556 int len = strlen(line);
557 char *last=line;
558 for (int i=0;i<len+1;i++) {
559 if (line[i]==' ' || line[i]=='\t' || line[i]==0) {
560 line[i] = 0;
561 float val;
562 if (sscanf(last, "%f", &val)==1) {
563 m->resize(nrow,max(ncols, m->cols));
564 cvSet2D(m, nrow-1, ncols-1, cvScalarAll(val));
565 ncols++;
567 last = line+i+1;
570 } while (f.good());
572 return m;
575 double LightMap::getObsMat(int i, int j, int c)
577 if (j == obs[i].camCol) return (double)obs[i].camVal[c];
578 if (j == (obs[i].camCol+1)) return 1;
579 if (j == obs[i].normalCol) return (double)obs[i].normalVal[c];
580 return 0;
583 double LightMap::getObsElem(const vector<Observation>::iterator &it, int i, int c)
585 if (it->camCol == i) return it->camVal[c];
586 if (it->camCol+1 == i) return 1;
587 if (it->normalCol == i) return it->normalVal[c];
588 return 0;
591 void LightMap::computeAtA(CvMat *AtA, int channel)
593 for (vector<Observation>::iterator it = obs.begin();
594 it!=obs.end(); ++it)
596 int idx[3] = {it->camCol, it->camCol+1, it->normalCol};
597 for (unsigned i=0; i<3; i++) {
598 for (unsigned j=0; j<3; j++) {
599 int i_ = idx[i];
600 int j_ = idx[j];
601 if (i_ <= j_) {
602 double v = getObsElem(it,i_, channel)*getObsElem(it,j_, channel);
603 if (v!=0) {
604 double d = v+cvGetReal2D(AtA, i_, j_);
605 cvSetReal2D(AtA, i_, j_, d);
606 cvSetReal2D(AtA, j_, i_, d);
613 for (int y=1;y<AtA->height; y++)
614 for (int x=0;x<y; x++) {
615 CV_MAT_ELEM(*AtA, float, y, x) = CV_MAT_ELEM(*AtA, float, x, y);
620 bool LightMap::save(const char *lightParamsFN, const char *normalsFN)
622 return saveMat(lightParams,lightParamsFN) &&
623 saveMat(normals, normalsFN);
626 bool LightMap::computeLightParams()
628 int obsMatCols=normals->rows+2*nbCam;
629 int sizes[] = {obs.size(), obsMatCols};
631 if (lightParams) cvReleaseMat(&lightParams);
632 lightParams = cvCreateMat(obsMatCols, reflc.avgChannels, CV_32FC1);
633 sizes[0] = sizes[1] = obsMatCols;
635 // create temporary matrices
636 CvMat *AtA = cvCreateMat(obsMatCols,obsMatCols, CV_32FC1);
637 CvMat *w = cvCreateMat(obsMatCols, 1, CV_32FC1);
638 CvMat *V = cvCreateMat(obsMatCols, obsMatCols, CV_32FC1);
640 for (int c=0; c<reflc.avgChannels; c++) {
641 cvSetZero(AtA);
642 computeAtA(AtA, c);
644 /* char atafn[64];
645 sprintf(atafn,"ata%d.mat",c);
646 saveMat(AtA, atafn);
649 cvSVD(AtA, w, 0, V, CV_SVD_MODIFY_A);
651 CvMat sub, lpc;
652 cvGetCol(V, &sub, V->cols-1);
653 cvGetCol(lightParams, &lpc, c);
654 cvScale(&sub, &lpc, 1.0/cvGet2D(&sub,0,0).val[0]);
657 std::cout << "Eigen values:";
658 for (int i=0; i<w->rows; ++i) {
659 float v = (float)cvGet2D(w, i, 0).val[0];
660 std::cout << " " << v;
662 std::cout << std::endl;
665 cvReleaseMat(&V);
666 cvReleaseMat(&AtA);
668 // construct light map
669 cvSet(map.getIm(), cvScalarAll(.1));
670 cvReleaseMat(&w);
673 std::cout << "Camera Values:\n";
674 for (int c=0; c<nbCam; ++c) {
675 float g = (float)cvGet2D(lightParams, c*2, 0).val[0];
676 float b = (float)cvGet2D(lightParams, c*2+1, 0).val[0];
677 std::cout <<" cam " << c << ": "<< g <<", " << b << std::endl;
680 buildMapFromSamples();
681 return true;
684 void LightMap::buildMapFromSamples()
686 for (int i=0; i<normals->rows; ++i) {
687 updateLightMap((float *)CV_MAT_ELEM_PTR(*normals, i, 0),
688 &CV_MAT_ELEM(*lightParams, float, i+2*nbCam, 0));
692 bool LightMap::load(const char *lightParamsFN, const char *normalsFN)
694 CvGrowMat *lp= loadMat(lightParamsFN);
695 if (!lp) return false;
696 lightParams = cvCreateMat(lp->rows, lp->cols, CV_32FC1);
697 cvCopy(lp, lightParams);
698 printf("Loaded lightParams: %dx%d.\n", lp->rows, lp->cols);
699 delete lp;
700 normals = loadMat(normalsFN);
701 int nlights = lightParams->rows - 2*nbCam;
702 if (!normals || ((normals->rows) < nlights)) {
703 if (normals) {
704 printf("Not enough normals. Expecting %d cameras. Found %d normals and %d parameters.\n",
705 nbCam, normals->rows, lightParams->rows);
707 cvReleaseMat(&lightParams);
708 return false;
710 if (normals->rows > nlights)
711 normals->resize(nlights,3);
714 std::cout << "Camera Values:\n";
715 for (int c=0; c<nbCam; ++c) {
716 float g = (float)cvGet2D(lightParams, c*2, 0).val[0];
717 float b = (float)cvGet2D(lightParams, c*2+1, 0).val[0];
718 std::cout <<" cam " << c << ": "<< g <<", " << b << std::endl;
722 buildMapFromSamples();
724 return true;
727 bool LightMap::saveImage(const char *filename)
729 IplImage *image = map.getIm();
730 if (image==0) return false;
731 IplImage *im = cvCreateImage(cvGetSize(image), IPL_DEPTH_8U,image->nChannels);
732 //double min=0, max=2;
733 //cvMinMaxLoc(image, &min, &max);
734 cvConvertScale(image, im, 128, 0);
735 int r = cvSaveImage(filename, im);
736 cvReleaseImage(&im);
737 return r!=0;