tag:blogger.com,1999:blog-66613473364438233132024-02-20T22:37:45.085-08:00A perfectly squared circleA place where I post about code, programming, computer science.
--
Billets divers à propos de programmation et d'informatique.Anonymoushttp://www.blogger.com/profile/04274985400485796581noreply@blogger.comBlogger7125tag:blogger.com,1999:blog-6661347336443823313.post-17929213804756827702018-02-06T07:36:00.000-08:002018-02-06T07:37:23.986-08:00<h2>
Unity shader development on a tiny tablet, in the metro...</h2>
<br />
I am spending quite some time each day on public transportation, for my work/home commute.<br />
<br />
There are several ways to use that time purposefully when you are a developer :<br />
- read blogposts about code or gamedev (if you have a wireless connection on your phone/tablet. If not, go <a href="https://www.instapaper.com/" target="_blank">Instapaper</a> or <a href="https://getpocket.com/" target="_blank">Pocket</a>)<br />
- read scientific PDF printed on paper (easier to deal with cross/back references for graphs and bibliography than following them on a screen. Especially if you scribble some BRDFs in the margins ;)<br />
- develop on a tablet...<br />
<br />
That last option is very tempting. I'm standing up nearly the whole travel up and commuting several times during the travel, so a laptop/netbook isn't a viable option. So the tablet is the way to go.<br />
<br />
<h3>
<b>Shaders </b></h3>
<br />
I like to dabble with shaders in my free time. With code dedicated to graphical programming, you have a rapid feedback on what your are creating. Furthermore, (simple) shaders are somewhat limited in scope, so the end result can be displayed in a few sessions of code.<br />
While Cg/HLSL/GLSL can be quite verbose, shaders and material are well suited to the visual programming paradigm : curves drawn across boxes, carrying the flow of the computation to the end result (usually displayed onscreen).
It's already the usual workflow for technical artists that want to edit their materials and their favorite DCC tool (3DS Max/Maya/Blender...).<br />
<br />
Unity has a few assets dedicated to visual programming for shaders. <a href="https://assetstore.unity.com/packages/tools/visual-scripting/shader-forge-14147" target="_blank">Shaderforge</a> and <a href="https://assetstore.unity.com/packages/tools/visual-scripting/amplify-shader-editor-68570" target="_blank">Amplify Shader Editor</a> are the more well known currently. I own a license for both, but Shaderforge support seems to go downhill while ASE is on the rise.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCmEzrAd3QxBgnb71q-04WI8HOBIDww6-tp1Ar8zHnHzhpghTwlOLQaQQmQWzZ0fyNZrMSxOlS63PlHOqO52RZPkbtKnboNZMj00tkAy7bRwylx5Jk2jBhQJfhHO41YzpAJwmRntK44tc/s1600/unity_ase_screenshot.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="407" data-original-width="700" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCmEzrAd3QxBgnb71q-04WI8HOBIDww6-tp1Ar8zHnHzhpghTwlOLQaQQmQWzZ0fyNZrMSxOlS63PlHOqO52RZPkbtKnboNZMj00tkAy7bRwylx5Jk2jBhQJfhHO41YzpAJwmRntK44tc/s400/unity_ase_screenshot.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><i>Amplify Shader Editor in Unity : illustrative shadergraph</i></td></tr>
</tbody></table>
<h3>
<br /><b>The hardware</b></h3>
<br />
After settling on the app, with need a machine to let it run.<br />
I was looking for the cheapest Windows 8/Windows 10 tablet, with at least 2 GB of RAM and the more 'powerful' CPU available (everything is relative), while staying under 100$, on which I could install Unity.
I managed to find a Teclast X80 Pro for around 50$.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwGae2ci4O-RnL2GhUbzvCnhFNY9onEqhEzvxJHiX7EJoqyPrXIhSbBzABRGPabSZSDINgT6LVDESnUpFZnXv5YxumJrPWmT89Hs9007nmfhgJWi4JijT9fRPguubnxoBdDpWf-1mZwa8/s1600/teclastx80_front.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="352" data-original-width="341" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwGae2ci4O-RnL2GhUbzvCnhFNY9onEqhEzvxJHiX7EJoqyPrXIhSbBzABRGPabSZSDINgT6LVDESnUpFZnXv5YxumJrPWmT89Hs9007nmfhgJWi4JijT9fRPguubnxoBdDpWf-1mZwa8/s200/teclastx80_front.jpg" width="193" /></a></div>
<br />
<br />
While having read mixed reviews on its mediocre wifi and its lack of support of high density micro-SD cards, everything else, hardware-based, seemed aligned with the price point.<br />
<br />
Since the tablet has two OS (Windows and Android) installed on it, there isn't much space left for your own apps and data. I'm planning to mostly code shaders with it, so it won't be too much of a nuisance.<br />
<br />
I installed a 32 GB micro SD card. I tested with a 64 GB one, but it wasn't properly recognized. It's something I need to clear out later. For the moment, I stick with a 'small' one, I don't want any data to be trashed out by a malfunctioning controller or bad driver.<br />
<h3>
<br /><b>The Wifi </b></h3>
<br />
The wifi is indeed bad. I have a router with whitelisting enabled for the MAC addresses allowed on my network. Although everything was properly set up, the tablet wouldn't access the wifi. I though 2 minutes about using a cheap USB wifi key (with all its dangling cables beauty...).
However, after spending a few minutes with the windows peripheral manager, I noticed that some settings of the X80's wifi card were odd.
After switching the <b>Antenna Diversity </b>to <i>Enabled</i>, <b>the wifi was on and fully operational</b>!
I don't know in what measure it's specific to my model (or what's done from the factory for every tablet). If it doesn't work for you, you still have the wifi USB key solution.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy1sosN5FRyyVEk3TiBJUT_T6Sw0KvUAAtDUXFNTlny6EqSx_RfxY9eRQzbKA28I9gEHlJ4fkmHU3gUS70jPfgwa05kCvgwJSKX4BY6JUZ-t4Ja9UNd0iDJf2HSRVhlYFUeBPVMPK4Dd4/s1600/antenna.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="974" data-original-width="1182" height="264" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy1sosN5FRyyVEk3TiBJUT_T6Sw0KvUAAtDUXFNTlny6EqSx_RfxY9eRQzbKA28I9gEHlJ4fkmHU3gUS70jPfgwa05kCvgwJSKX4BY6JUZ-t4Ja9UNd0iDJf2HSRVhlYFUeBPVMPK4Dd4/s320/antenna.png" width="320" /></a></div>
<br />
<h3>
<b>Unity, ASE & interactions</b></h3>
<br />
I installed Unity 2017.1f on it, and started the IDE. <br />
I created a new Unity project on the SD card, and imported the Amplify Shader Editor (ASE) in it. <br />
While it's a great way to code shaders on the go, you will still encounter a few quirks when using using this setup.<br />
For example, ASE is slow when clicking on the 'compile shader' button (but that was also true for me on my regular desktop machine...)<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFqFeWQZJvg2cUlr51qX1h0T2BQ_WGOQW_-Z0TDHgrCZha3QTKiDqm1v6okGAejAXMSef9rveOBDkxOB-wPHWgs7aOLv7uOecg4eUGKYUAyf1kMqtI1BTITki2VKKB9lwt4tPV9SYUw5U/s1600/ASE_Unity_gray.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="425" data-original-width="710" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFqFeWQZJvg2cUlr51qX1h0T2BQ_WGOQW_-Z0TDHgrCZha3QTKiDqm1v6okGAejAXMSef9rveOBDkxOB-wPHWgs7aOLv7uOecg4eUGKYUAyf1kMqtI1BTITki2VKKB9lwt4tPV9SYUw5U/s320/ASE_Unity_gray.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">ASE opened in Unity, on a Teclast X80 Pro</td></tr>
</tbody></table>
Other problems can be solved with a bit of creativity :<br />
<br />
<b></b><br />
<ul><b>
<li><b>The keyboard</b></li>
</b></ul>
<b>
</b><br />
<ol>
</ol>
The click isn't precise on those small screens so you'll need the keyboard really soon to use the various keyboard shortcuts... <br />
<br />
However : the touch keyboard on windows 10 isn't complete. A few very needed key are unavailable (among them : F2, the quickest way to rename a file, isn't there. No 'Ctrl' nor 'Alt' nor 'Maj'...) <br />
If you use this keyboard, don't forget to split it in the center, you will see what you are working on!<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpeOQaKUDMsQGaV5qOteCuGj31pBfzDXC6XN585RuOGuW4O9krl16_vjhwSbRNibZW5O7zXJ6601S77w2eqrnIW-vvZBQI25m7FXiW20osv7kmtPthyphenhyphenzC1dqPqkNI-_P9q_H-vJzErhHE/s1600/touch_keyboard.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="126" data-original-width="394" height="126" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpeOQaKUDMsQGaV5qOteCuGj31pBfzDXC6XN585RuOGuW4O9krl16_vjhwSbRNibZW5O7zXJ6601S77w2eqrnIW-vvZBQI25m7FXiW20osv7kmtPthyphenhyphenzC1dqPqkNI-_P9q_H-vJzErhHE/s400/touch_keyboard.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><i>The splitted touch keyboard</i></td></tr>
</tbody></table>
<br />
You still have access to the 'Visual Keyboard' program, located in the Start menu. It's less beautiful and responsive than the Win10 on-screen keyboard, but you'll have access to all the keys. You won't be able to have opened, at the same time, both keyboards. You need to close one to see the other.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTux9ywuPbD-ZdihlOavs3HokvxHRVb5TdPBCrLNNXN101pn8fE9UYo1x3c4Wj_2DXTeDDcLN8Dix1xXqiMnohMEaEKoDOKRi97JWllfXfY887cdu61vplx9bON7t2c8Er6KUHsGUVtY0/s1600/visual_keyboard.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="229" data-original-width="600" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTux9ywuPbD-ZdihlOavs3HokvxHRVb5TdPBCrLNNXN101pn8fE9UYo1x3c4Wj_2DXTeDDcLN8Dix1xXqiMnohMEaEKoDOKRi97JWllfXfY887cdu61vplx9bON7t2c8Er6KUHsGUVtY0/s400/visual_keyboard.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><i>Visual keyboard, more complete</i></td></tr>
</tbody></table>
<ul>
<li><b> Zoom</b></li>
</ul>
<ol>
</ol>
The pinch gesture to zoom is properly registered by Windows, but not by ASE. It is then currently difficult to move around the shader editor.<br />
You need to move around the ASE canvas, you need to click-and-hold a moment to use the 'right mouse button', and then, without lifting the finger, you can move it around.<br />
To draw a curve between two slots, you need to click once on the starting slot and very quickly click-and-draw the curve to the receiving slot.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1Qy_QTfKIfSlm2pU-xE2bqdV-5fg0_7XLkVRPmuzyeR-QwLEKj8HTv49JY3vta7WjpASEBgllRFowSdczvftiAzKVdyXZBx1458A7PC9SOuawKsw-Sb7pnIQA95vYtg8skQg6SdPqCnA/s1600/curve.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="319" data-original-width="615" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1Qy_QTfKIfSlm2pU-xE2bqdV-5fg0_7XLkVRPmuzyeR-QwLEKj8HTv49JY3vta7WjpASEBgllRFowSdczvftiAzKVdyXZBx1458A7PC9SOuawKsw-Sb7pnIQA95vYtg8skQg6SdPqCnA/s320/curve.png" width="320" /></a></div>
<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFi7Zpf4eUdjeFs1GxdA7cQzvhT_9mBjdmZyd1PdjSTYmuoaE30MOCz7hMEQpR7wPJuktSWFXB_lmwX-IJMRs06H9SQG20KwrNesfiBofVQQmDgX5bMRqLr3Fw1ycCwUqYlVYih438FCU/s1600/andromouse_touch.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" data-original-height="221" data-original-width="125" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFi7Zpf4eUdjeFs1GxdA7cQzvhT_9mBjdmZyd1PdjSTYmuoaE30MOCz7hMEQpR7wPJuktSWFXB_lmwX-IJMRs06H9SQG20KwrNesfiBofVQQmDgX5bMRqLr3Fw1ycCwUqYlVYih438FCU/s1600/andromouse_touch.jpg" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><i>Andromouse touchpad emulator</i></td></tr>
</tbody></table>
If a mouse is really needed, I modded a bit the setup. I downloaded Andromouse server (<a href="http://andromouse.com/">http://andromouse.com/</a>) on the X80 and installed the app on my phone (<a href="https://play.google.com/store/apps/details?id=com.iandrobot.andromouse.free&hl=fr">https://play.google.com/store/apps/details?id=com.iandrobot.andromouse.free&hl=fr</a>). After pairing the two via Bluetooth, I can move the mouse by using the phone as a touchpad. If you are in a situation where you would need to scroll when in front of a desktop computer, you can use the right side of andromouse client app for that (and zoom in and out of an ASE graph!).
The soft keyboard provided by the app is also useful at times, but doesn't provide a 'print screen' key!<br />
<br />
<br />
Another option is to install the TouchMouse Pointer for windows (<a href="http://www.lovesummertrue.com/touchmousepointer/en-us/">http://www.lovesummertrue.com/touchmousepointer/en-us/</a>) app and dock it on the right of the screen.
You will have access to a 'fake' touchpad, for precise pointing, and a scroll area to emulate the mouse wheel. You have now the capacity to use complex gestures combining hold and scroll, for example!<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihGMtA7Di6GLDSKSLbmshfkaMtIyEp1iSGXUGOn1Uh-TgzRIRMxABMdVnMmDa0mEBJelLnMPBRh0eyB39G-_OQ2yttG6e-gDH6wHotoC-7p7naS3hr4y7XVm6yHOkKaw2pGzJ8OZdxVhs/s1600/touchmouse_pointer.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="295" data-original-width="488" height="193" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihGMtA7Di6GLDSKSLbmshfkaMtIyEp1iSGXUGOn1Uh-TgzRIRMxABMdVnMmDa0mEBJelLnMPBRh0eyB39G-_OQ2yttG6e-gDH6wHotoC-7p7naS3hr4y7XVm6yHOkKaw2pGzJ8OZdxVhs/s320/touchmouse_pointer.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><i>TouchMouse Pointer, docked on right side of the screen</i></td></tr>
</tbody></table>
<br />
<h3>
<b>Conclusion</b></h3>
<br />
To conclude, I'm pleasantly surprised by the experience! I can try shaders on the go, at a very low cost. This small tablet won't be able to showcase all techniques available in shaders, but it's a good way to prototype something.<br />
Don't forget to set up some kind of Git/Github/Gitlab repo, to exchange data with this tablet and your main machine !<br />
<h3>
<br /><b>What's next </b></h3>
<b><br /></b>
This setup isn't constrained to shader development. Another thing to try would be 'flow programming' (Bolt, Flow Canvas or the likes) on the X80. Since it's not very convenient to code on a soft keyboard for a long time, it would be interesting to see if at least some basic app prototyping, to bootstrap a project, is possible with those visual programming tools.<br />
<br />
It's a long post now, I'll then reserve another future post to showcase a few shader developped with this setup, while commuting !Anonymoushttp://www.blogger.com/profile/04274985400485796581noreply@blogger.com0tag:blogger.com,1999:blog-6661347336443823313.post-5896386983307502312014-07-18T05:21:00.000-07:002014-07-18T05:21:05.814-07:00Concatenate images with OpenCV<h2>
Concatenate images with OpenCV</h2>
The PDF file format has several advantages. When generating a PDF file, you have the certainty that it will be displayed the same way on every platform. On print, it's going to be the same everywhere.<br />
<br />
Meanwhile, the PDF format isn't easily editable. The choices made by the file viewer can also be counter productive in some specific scenarios (when displaying full paged images from comics for example, the reoslution of the image has nothing to do with the resolution of the PDF and the display, meaning that you can have some pixelization occuring...)<br />
Scientific publications in PDF (or those same comics) embed sometimes infographies, charts and pictures that you may need to reuse.<br />
<br />
The <i>pdfimages </i>tool can extract JPEG images embedded inside a PDF file (doing it also for other image file format that we aren't going to use here). You can find a precompiled binary for Win32 there : <a href="http://www.foolabs.com/xpdf/download.html">http://www.foolabs.com/xpdf/download.html</a>. You can choose to keep only the <b>pdfimages.exe</b> file and delete everything else. When it's saving the images, the tool doesn't 'reinterpret' them, which is important for JPEG images : there's no loss of quality when doing a new compression/decompression cycle.<br />
<br />
The syntax to extract the files in JPEG is <b>-j</b> :<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
pdfimages.exe -j "nom_du_fichier_pdf" 0<br />
<br /></div>
<br />
To keep the numerotation of the file the same as in the file, youneed to provide a prefix to the saved files. You can use what you want obviously. Here, I use '<b>0</b>'.<br />
<h4>
OpenCV to the rescue</h4>
Some tools split the image when embedding them inside the PDF. You can't see it onscreen but when the images are dumped from the PDF, they are extracting in several pieces.<br />
<br />
I coded a little tool with OpenCV that takes all the images of a folder and concatenate them in logical 'pages'. OpenCV is overkill for a work like that, but by using it you can later add some effects (for example) on the images.<br />
<br />
To read the folders and files in a multiplatform way (for a future port on Linux, for example), I use a simple header : <b>tinydir.h</b> (by <i>Cong Xu</i> and <i>Baudoin Feildel</i>, from here : <a href="https://github.com/cxong/tinydir">https://github.com/cxong/tinydir</a>).<br />
<br />
To handle different cases of image splitting, the tool accept as a parameter the number of successive images to reassemble. Forexample, if you want to concatenate the images 5 by 5, here's the commandline :<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
OpenCVConcat.exe 5<br />
<br /></div>
<br />
By default, the tool is going to save the pictures sequentially from 000.jpg to 999.jpg (you can edit the code if you need to go over that).<br />
<br />
For the moement, the tool works only with JPEG (with the .jpg extension). You need to add the checks for other file formats if you need to concatenate PNG or others.<br />
<br />
The code isn't completely clean, but it's providing a good codebase to start with. For exemple, the proper way to handle memory would be to free the temporary images in RAM, handled by the vector. However the tool is fast and is only made to run for a short (it shoudln't stay in memory for too long). The memory segment allocated for the program is going to be properly freed at the end of the execution by the OS.<br />
<br />
The code compile with OpenCV 2.4.9 and Visual Studio 2012, but it should works with any OpenCV install above 2.0+ and a fairly recent VS install (something above VS 2005). The linked libraries are declared as <b>#pragma comment(lib,""</b>) at the head of the main cpp file. You need to change those lines to match your installation.(This way of declaring the libraries is helpful when dealing with different version of OpenCV installed concurrently on the system...).<br />
<br />
The code is available on GitHub : <a href="https://github.com/Pseudopode/OpenCVConcat">https://github.com/Pseudopode/OpenCVConcat</a>, with an archive containing the compiled binary. The same archive is available here : <a href="https://dl.dropboxusercontent.com/u/1412774/Blog/BlogSpot/OpenCV_Concat/OpenCVConcat.1.0.7z">https://dl.dropboxusercontent.com/u/1412774/Blog/BlogSpot/OpenCV_Concat/OpenCVConcat.1.0.7z</a>.Anonymoushttp://www.blogger.com/profile/04274985400485796581noreply@blogger.com0tag:blogger.com,1999:blog-6661347336443823313.post-46410201149205566762014-07-18T05:06:00.002-07:002014-07-18T05:21:43.879-07:00Concatenation d'images avec OpenCV<h2>
Concatenation d'images avec OpenCV </h2>
<h3>
pdfimages.exe</h3>
<br />
Le format PDF présente plusieurs avantages : en générant un fichier PDF vous avez la garantie qu'il sera affichée de la même manière sur toutes les plateformes. A l'impression le rendu sera identique partout.<br />
<br />
En contrepartie, le format PDF n'est pas fait pour être édité facilement. De plus les choix d'affichages effectués par le lecteur de fichier peuvent se révéler mauvais dans certains cas (notamment lors de l'affichage d'images A4 pleine page dans le cas de bande dessinées ou de comics par exemple).<br />
Les publications scientifiques en PDF (ou ces même comics) embarquent souvent des infographies que vous voudriez peut-être récupérer.<br />
<br />
L'outil <i>pdfimages </i>permet d'extraire les images JPG embarquées dans un fichier PDF (ainsi que d'autres formats qui ne nous intéressent pas ici). Pour Win32, vous pouvez trouver l'outil précompilé là : <a href="http://www.foolabs.com/xpdf/download.html" target="_blank">http://www.foolabs.com/xpdf/download.html</a> . Vous pouvez ne garder que <b>pdfimages.exe</b> présent dans l'archive.<br />
Lorsqu'il enregistre les images, l'outil ne les "réinterprète" pas, ce qui est important pour les images JPEG : il n'y a pas de perte de qualité avec une nouvelle compression/décompression.<br />
<br />
La syntaxe pour extraire les fichiers au format JPEG est <b>-j</b> :<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
pdfimages.exe -j "nom_du_fichier_pdf" 0<br />
<br /></div>
<br />
Afin de garder la numérotation des images identique à celle du fichier, il faut donner un préfixe au fichiers qui seront sauvegardé. Vous pouvez utiliser ce que vous voulez, évidemment. Ici, j'utilise '<b>0</b>'.<b> </b><br />
<h4>
<b>OpenCV à la rescousse</b> </h4>
Certains outils découpent les images lors de l'intégration des fichiers dans le fichier PDF. Cela ne se voit pas à l'écran lors de la visualisation du PDF, mais lors de la sauvegarde des images elles seront extraites en plusieurs morceaux.<br />
<br />
J'ai programmé un petit outil avec OpenCV qui récupère toutes les images d'un dossier et les réassemble en pages logiques. OpenCV est surdimensionné pour une tâche de ce type, mais son utilisation permet d'appliquer d'éventuels traitement ultérieurs sur les images.<br />
<br />
Afin de pouvoir lire facilement les dossiers et les fichiers de manière multiplateforme (en prévision d'un portage éventuel sous Linux, par exemple), j'utilise un simple fichier d'en-tête, <b>tinydir.h</b> (par <i>Cong Xu</i> et <i>Baudouin Feildel</i>, venant d'ici <a href="https://github.com/cxong/tinydir">https://github.com/cxong/tinydir</a>).<br />
Afin de gérer plusieurs cas de découpe d'image, l'outil accepte en paramètre le nombre d'images successives à assembler. Par exemple, si vous désirer réunir les images 5 par 5, voici la ligne de commande :<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
OpenCVConcat.exe 5<br />
<br /></div>
<br />
Par défaut, l'outil va sauvegarder les images séquentiellement en partant de 000.jpg, jusqu'à 999.jpg<br />
<br />
Pour l'instant, l'outil n'assemble que les fichiers .jpg . Il faut rajouter la vérification pour les autres formats de fichiers si vous voulez assembler des PNG ou autres.<br />
<br />
Le code n'est pas complètement propre, mais cela donne une bonne base pour commencer. Par exemple, il faudrait pour la gestion des images temporaires en RAM désallouer proprement les images stockées dans le vecteur. Malgré tout, l'outil tournant rapidement et n'étant pas destiné à tourner en mémoire vive, le segment mémoire associé sera libéré proprement par l'OS à la fin de l'exécution.<br />
<br />
Le code compile avec OpenCV 2.4.9 et Visual Studio 2012, mais cela devrait fonctionner avec n'importe quelle installation d'OpenCV en branche 2.0+ et un VS récent (certainement depuis VS2005). Les bibliothèques à lier par le compilateur sont déclarées en<b> #pragma comment(lib,"")</b>, il faut changer ces lignes pour qu'elles correspondent à votre installation. (Cette manière de déclarer les librairies permet de changer facilement de version lorsque vous utilisez plusieurs version d'OpenCV en parallèle sur la même machine).<br />
<br />
Le code est disponible sur GitHub : <a href="https://github.com/Pseudopode/OpenCVConcat">https://github.com/Pseudopode/OpenCVConcat</a>, ainsi qu'une archive contenant l'exécutable et ses DLLs. Cette même archive est disponible également ici : <a href="https://dl.dropboxusercontent.com/u/1412774/Blog/BlogSpot/OpenCV_Concat/OpenCVConcat.1.0.7z">https://dl.dropboxusercontent.com/u/1412774/Blog/BlogSpot/OpenCV_Concat/OpenCVConcat.1.0.7z</a>.Anonymoushttp://www.blogger.com/profile/04274985400485796581noreply@blogger.com0tag:blogger.com,1999:blog-6661347336443823313.post-61171663010468845002012-12-03T08:40:00.003-08:002012-12-03T08:40:26.365-08:00Pseudocolorisation en javascript<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<a href="http://podeplace.blogspot.fr/2012/11/pseudocouleurs-avec-opencv.html" target="_blank">Dans le dernier billet</a>, j'ai parlé de pseudo-colorisation. L'exemple proposé était écrit en C, avec OpenCV.<br />
Je vais maintenant vous détailler une solution pour faire de même en javascript, dans un navigateur.<br />
<br />
<i>(Si l'explication est trop longue, vous pouvez aller directement à la démonstration, à la fin de ce billet).</i><br />
Pour résumer :<i><br /></i><br />
1)<i> </i>charger une image dans un <canvas><br />
2) charger une colormap dans un <canvas><br />
3) appliquer l'algorithme de recolorisation (rechercher la valeur de l'image en niveaux de gris dans la colormap)<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCk9tNl5UycEYTdI5WXayNEYM0UnIWdUFmkBI7MRHdq9W0VZi6LTFTTjZCpohyphenhyphencX4OGZtD7p6mfEoVpctH2USLwFMIn7iZBFiaGFfq2JK60gAgVsQMhnPLRf1L1SUPR6Ga6l5rnMisRrE/s1600/HTML5-logo.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCk9tNl5UycEYTdI5WXayNEYM0UnIWdUFmkBI7MRHdq9W0VZi6LTFTTjZCpohyphenhyphencX4OGZtD7p6mfEoVpctH2USLwFMIn7iZBFiaGFfq2JK60gAgVsQMhnPLRf1L1SUPR6Ga6l5rnMisRrE/s1600/HTML5-logo.png" /></a></div>
<br />
<h3>
Javascript & l'objet <canvas></h3>
En "HTML5", quoi que veuille réellement impliquer le nom aujourd'hui, si vous avez un navigateur récent (Opéra, Chrome, Firefox, Safari...), vous pouvez travailler avec un élément bien particulier : le <canvas>.<br />
Un canvas 2D contient tous les pixels d'une image rastérisée (bitmap), tout en étant un élément d'une page web. Le bénéfice d'un <canvas> sur un <img> vient de la possibilité d'accéder à tous les pixels qui composent le canvas (l'élément <img> est lui strictement passif, il ne sert qu'à afficher une image).<br />
<br />
A sa création, le Javascript était lent. Maintenant, les navigateurs grand public utilisent tous, sous une forme ou l'autre, différentes améliorations et accélérations (machine virtuelle de type JIT, fonctions inline, etc...). Cela permet d'effectuer des calculs uniquement possibles sur machine fixe jusque là.<br />
<br />
Le code javascript présenté ici va implémenter l'algorithme codé avec OpenCV dans le billet précédent.<br />
<br />
<h4>
Représentation en mémoire des images couleurs</h4>
<br />
Vous avez besoin du tableau de pixels de l'image et du tableau de pixels en provenance de la colormap (une unique colonne de 256 pixels de haut).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDFr705gS1DeKUdcSVMviN836b6bqlSjHWWcn0uebaxdTmia5Niu_TI-u4iwu1V_zJXlLWVuqqoALq5WISUvMk00AHKawNxEwmUMmSbidPwWrpTZLNafI2zjMBEEx6UIEUd_ugp7jRUBw/s1600/colormap3.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDFr705gS1DeKUdcSVMviN836b6bqlSjHWWcn0uebaxdTmia5Niu_TI-u4iwu1V_zJXlLWVuqqoALq5WISUvMk00AHKawNxEwmUMmSbidPwWrpTZLNafI2zjMBEEx6UIEUd_ugp7jRUBw/s400/colormap3.png" width="1" /></a></div>
<br />
Ensuite, il vous suffit d'itérer sur tous les pixels de l'image d'origine, récupérer la luminosité des pixels, chercher dans la colormap le triplet R,G,B associé à cette valeur, et l'écrire au final dans le tableau d'origine (vous pouvez trouver un schéma pour illustrer ce fonctionnement dans le billet précédent : <a href="http://podeplace.blogspot.fr/2012/11/pseudocouleurs-avec-opencv.html" target="_blank">http://podeplace.blogspot.fr/2012/11/pseudocouleurs-avec-opencv.html</a>)<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCw-3F4YzsAwtuaerals5I1QKb_73ABkSeZ6je7S2IEVnv0QZtSsFoGupjUdsEa2syvZPfn0-F8F0x3XFsKpQkzM34GFph6_G8WC1p6Paa3QdS7g1RnYTfIOd3Yqb06xduCk8915aqCmo/s1600/IplImageRGB.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="195" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCw-3F4YzsAwtuaerals5I1QKb_73ABkSeZ6je7S2IEVnv0QZtSsFoGupjUdsEa2syvZPfn0-F8F0x3XFsKpQkzM34GFph6_G8WC1p6Paa3QdS7g1RnYTfIOd3Yqb06xduCk8915aqCmo/s400/IplImageRGB.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Image Couleur vue par OpenCV : 3 plans de couleurs (Rouge, Vert, Bleu)</td></tr>
</tbody></table>
OpenCV travaille avec deux concepts d'image : couleur et niveaux de gris. Une image en niveaux de gris utilise un seul plan de couleur (l'intensité du gris), mais les images couleurs ont trois plans de couleurs (rouge, vert, bleu). Ainsi, la bibliothèque peut gérer la mémoire plus efficacement.<br />
<br />
<br />
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyCzqrfLDD-gtLJxM42mND27JiceEsm2KNS5m4inamye8nY1KIlx8_W50ps1HmjS0GbG46gQlv7_QW5UmTRgbFCkKH9YB8WWpT4owfPmWs76acxzjg3VgefFgGQG9mf3iAtaWfn4928dc/s1600/IplImage_Gray.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyCzqrfLDD-gtLJxM42mND27JiceEsm2KNS5m4inamye8nY1KIlx8_W50ps1HmjS0GbG46gQlv7_QW5UmTRgbFCkKH9YB8WWpT4owfPmWs76acxzjg3VgefFgGQG9mf3iAtaWfn4928dc/s400/IplImage_Gray.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Image en niveaux de gris : OpenCV ne voit qu'un seul plan de couleur en mémoire</td></tr>
</tbody></table>
<div style="text-align: left;">
</div>
<br />
Par contre, l'élément <canvas> ne connaît qu'un seul type d'image : 4 plans de couleurs - rouge, vert, bleu, alpha (pour la transparence).<br />
Cela signifie que pour travailler avec. une "vraie" image en noir et blanc, nous devons stocker la valeur en niveaux de gris dans un des plans de couleurs, et la recopier dans les deux autres canaux (en laissant le canal de transparence complètement opaque).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkToFfshkUvffw7D60zkHoNldBqt0IeUz5edhlZK95SDEpy0ctlyueZy37bgo6JCS9PNTp2vQ5t3_2SMLH5vwXvXdpx3Irc_bpgzdB0CYRaUucphUw9Ng97LP8_rsZe8k_GYdWZ8r8Pxs/s1600/html5_canvas_lena_inmemory.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="204" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkToFfshkUvffw7D60zkHoNldBqt0IeUz5edhlZK95SDEpy0ctlyueZy37bgo6JCS9PNTp2vQ5t3_2SMLH5vwXvXdpx3Irc_bpgzdB0CYRaUucphUw9Ng97LP8_rsZe8k_GYdWZ8r8Pxs/s640/html5_canvas_lena_inmemory.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Image en mémoire pour un <canvas> : 4 pixels de couleurs consécutifs (Rouge, Vert, Bleu, Alpha) pour chaque pixel coloré</td></tr>
</tbody></table>
Nous ne pouvons pas charger une image JPEG ou PNG directement dans le canvas, car nous ne savons pas vraiment quels sont les choix effectués par le logiciel qui a servi a sauvegarder ces images. On ne sait pas si le logiciel a choisi de ne stocker qu'un seul plan de couleur (en niveaux de gris) ou plusieurs plans (même pour une image noire & blanche !). De plus, certains formats de fichiers n'autorisent pas le choix du nombre de plans de couleurs lors de l'enregistrement.<br />
Dès lors, nous devons partir d'un canvas dans lequel une image a été enregistrée, et convertir ce ces couleurs en niveaux de gris, au sein de l'objet <canvas>.<br />
<br />
<h3>
Conversion d'un canvas couleurs en niveau de gris</h3>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRiTqTLV8bg4LURqprd1iPx6E02oF7sdkyvZ7c8EEft_i9bYIdI0hUcwDpnF0qdwvpZ0XR-MJUAKw0Ap-A_1Nz4aTLuguwSYyHxQIf_-Ad7TV2evcUuuJB3ILTNuWvGWi6lBBoSzcmea8/s1600/169_graymix.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img alt="" border="0" height="186" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRiTqTLV8bg4LURqprd1iPx6E02oF7sdkyvZ7c8EEft_i9bYIdI0hUcwDpnF0qdwvpZ0XR-MJUAKw0Ap-A_1Nz4aTLuguwSYyHxQIf_-Ad7TV2evcUuuJB3ILTNuWvGWi6lBBoSzcmea8/s200/169_graymix.png" title="Mélange RGB vers gris, moyenne simple" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Mélange RGB vers Gris par simple moyenne</td></tr>
</tbody></table>
<br />
<br />
Le processus de conversion d'une couleur RGB peut être aussi simple que prendre la moyenne des 3 canaux de couleurs, divisée par 3. Dans ce cas, le niveau de gris obtenu n'est pas la meilleure approximation possible du gris correspondant à la couleur d'origine. L’œil humain est plus sensible au vert, et moins sensible au bleu ( ceci étant du aux fonctionnement des cellules de la rétine),. Nous pouvons alors attribuer un poids à chaque canal, de manière à ce qu'il contribue plus ou moins au. niveau de gris final.<br />
<br />
Vous pouvez trouver en ligne plusieurs méthodes pour définir ce mélange des trois valeurs RGB. Par exemple, avec le format PAL/NTSC (utilisé par la télévision analogique), le niveau de luminance est défini comme ceci :<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9hyphenhyphenqQUVijd5RQ-O2NcPd8zZykBf_AXfv7DJMHkp-_kC9dCPEKuEx537WYyWSAG89zWF5o3BDSofUh0Jz7jeGwoooPnB7JOM8unopXWAPM4UPfeQubGBBuOCdaoBz66SMlLgkTHDup0jg/s1600/174_graymix.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="158" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9hyphenhyphenqQUVijd5RQ-O2NcPd8zZykBf_AXfv7DJMHkp-_kC9dCPEKuEx537WYyWSAG89zWF5o3BDSofUh0Jz7jeGwoooPnB7JOM8unopXWAPM4UPfeQubGBBuOCdaoBz66SMlLgkTHDup0jg/s200/174_graymix.png" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Mélange RGB vers Gris (plus proche de la perception humaine)</td></tr>
</tbody></table>
<br />
<b><span style="color: #073763;">lum = 0.299 * rouge + 0.587 * vert + 0.114 * bleu</span></b><br />
<br />
La luminance est définie dans la norme CIE 1931 comme suit :<br />
<br />
<span style="color: #073763;"><b>Y = 0.2126 * rouge + 0.7152 * vert + 0.0722 * bleu</b></span><br />
<br />
<br />
(Les valeurs utilisées par <i>OpenCV </i>sont <b>Rouge : 0.212671, Vert : 0.715160, et Bleu : 0.072169</b>).<br />
<br />
Comme pour d'autres exemples accessibles sur internet, j'ai utilisé les valeurs suivantes :<br />
<br />
<span style="background-color: #cccccc;"><b> luminosité = 0.34 * rouge + 0.5 * vert + 0.16 * bleu </b></span><br />
<br />
Tous ces modèles prennent en compte la sensibilité de l’œil humain pour le vert.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgw-oCWbf3BGtvJsvGhNviOzBn4vyzZ9ssVvt7MO2ftbDwWg5mb8et2OEgHbvZiLL0FpaOe2PefXZVO5dibb-BfVASLooGiVgLXLKbfNj4zJ0GuRb84LqOLb89gHio_81i1Gnk1hz8PXfc/s1600/169-174.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="95" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgw-oCWbf3BGtvJsvGhNviOzBn4vyzZ9ssVvt7MO2ftbDwWg5mb8et2OEgHbvZiLL0FpaOe2PefXZVO5dibb-BfVASLooGiVgLXLKbfNj4zJ0GuRb84LqOLb89gHio_81i1Gnk1hz8PXfc/s200/169-174.png" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Différence entre gris obtenu par moyenne et gris obtenu par le modèle physiologique précédent.</td></tr>
</tbody></table>
La différence entre les valeurs de gris calculée par les deux méthodes est subtile mais suffisante pour être remarquée.<br />
<br />
<h4>
L'algorithme en javascript</h4>
<br />
Dans notre exemple, nous pouvons transformer l'image en niveaux de gris au chargement de la page, et travailler ensuite avec cette image modifiée pour lui appliquer notre colormap.<br />
<br />
Quand vous travaillez avec un <i>canvas </i><b>cvs</b> et son <i>contexte </i>associé <b>ctx</b>, vous pouvez récupérer un tableau d'informations spécifiques à cette image via<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
<b> var myImageData = ctx.getImageData(0, 0, cvs.width, cvs.height);<br /> </b>
</div>
<br />
Cette variable contient un tableau de données , contenant les pixels de couleurs, dans l'ordre suivant : <b>[<span style="color: red;">rouge</span>, <span style="color: #274e13;">vert</span>, <span style="color: blue;">bleu</span>, <span style="color: #666666;">alpha</span>, <span style="color: red;">rouge</span>, <span style="color: #274e13;">vert<span style="color: black;">,</span> </span><span style="color: blue;">bleu</span>, <span style="color: #666666;">alpha</span></b>,...<b>]</b>.<br />
<br />
En javascript, pour éviter de traverser le DOM, qui est une opération lente (lors de l'accès de <i><b>myImageData.data</b></i>, vous pouvez détacher le tableau de pixels en tant que nouvelle variable :<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
<b> var dataSrc = myImageData.data;<br /> </b>
</div>
<br />
<br />
et accéder ce qui est contenu dans <b>dataSrc</b>, pour un accès plus rapide.<br />
<br />
Ce tableau étant à une dimension, vous pouvez accéder aux différents pixels du tableau avec une boucle comme celle-ci :<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
<b>for(var y = 0; y < height; y++){ <br />
for(var x = 0; x < width; x++){<br />
index = (x + y * width) * 4;<br />
dataSrc[index+0] = ROUGE;<br />
dataSrc[index+1] = VERT;<br />
dataSrc[index+2] = BLEU;
</b><br />
<div style="color: #444444;">
<b> //dataSrc[index+3] = ALPHA; //pas de modifications</b></div>
}<br />
}</div>
<br />
Ici, pas besoin de changer la valeur alpha. Le <span style="background-color: #cccccc;"><b> *4 </b></span> permet d'atteindre le pixel suivant en mémoire (car il faut sauter dessus des 4 valeurs RGBA).<br />
<br />
Si vous voulez appliquer une conversion en niveaux de gris sur cette image, vous pouvez maintenant écrire la nouvelle valeur de gris dans les canaux rouge, vert et bleu, pour des raisons d'affichage (attention, l'image sera 3 fois plus lumineuse que la vraie conversion RGB->Gris, bien que ce ne soit le cas que lors de l'affichage sur un écran d'ordinateur).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4AJtxIxyfxaaLG22LJW-EE3YrCR9uLlo6ASPuFye9z5kArP6b8N-Uuz-0uPXM1UmJetxJ8JU80bdqYdHxprjfiZ8GfNHKuy4Zbsq4hOv86J-ziVX2GCZkgR7hGviwVAEO7H9JdBBbeFc/s1600/gray2canvas.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4AJtxIxyfxaaLG22LJW-EE3YrCR9uLlo6ASPuFye9z5kArP6b8N-Uuz-0uPXM1UmJetxJ8JU80bdqYdHxprjfiZ8GfNHKuy4Zbsq4hOv86J-ziVX2GCZkgR7hGviwVAEO7H9JdBBbeFc/s320/gray2canvas.png" width="265" /></a></div>
<br />
<br />
Ne vous inquiétez pas, quand vous allez travailler avec ces pixels à l'étape suivante, pour appliquer la colormap, vous allez utiliser la valeur de gris d'un seul des canaux, puisque c'est la même valeur sur les 3.<br />
Si nous avions choisi de ne stocker la valeur de gris que dans un seul canal, le rouge par exemple, l'image affichée serait seulement en teintes de rouge (et cela peut paraître un peu étrange de considérer une image intégralement en niveau de rouge, vert ou bleu comme une image "en niveaux de gris").<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYPziqxkX4EkxRNaldXdpc4JBhbUfpehmF89cLS8B_GLbi9ZdePV26bHKQW9AkQAvld-0Yr1gY-HB1ZImFjeq39TfOPbcAAOx8tRXT27DZOf1wAq-lnLsd3A2S9QZSoYMzP1mehNxNnwQ/s1600/gray2canvas2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYPziqxkX4EkxRNaldXdpc4JBhbUfpehmF89cLS8B_GLbi9ZdePV26bHKQW9AkQAvld-0Yr1gY-HB1ZImFjeq39TfOPbcAAOx8tRXT27DZOf1wAq-lnLsd3A2S9QZSoYMzP1mehNxNnwQ/s320/gray2canvas2.png" width="267" /></a></div>
<br />
Ce genre d'accès aux pixels est la base de toutes les opérations et filtres sur les images en javascript (et plus généralement dans tous les langages de programmation quand on traite avec des images).<br />
<br />
Quand toutes les modifications sont effectuées, n'oubliez pas d'écrire ces pixels à nouveau dans l'image d'origine !<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
myImageData.data = dataRes; <span style="color: #666666;">//ré-attacher la variable</span><br />
ctx.putImageData(myImageData,0,0); <span style="color: #666666;">//l'écrire dans le canvas</span></div>
<br />
Dans l'exemple fourni, nous chargeons une image JPEG classique, la transformons en niveaux de gris au clic d'un bouton, et la recolorisons quand l'utilisateur clique sur une des colormaps. Vous devez recharger la page si vous voulez utiliser une autre couleur, vous redéclencher la conversion couleurs -> niveaux de gris d'origine. Si vous ne le faites pas, vous allez appliquer différentes colormaps successives sur une image déjà modifiée, ce qui va amener une saturation colorée de l'image à la fin.
<br />
<br />
<h4>
Démonstration javascript :</h4>
<iframe height="600px" src="https://dl.dropbox.com/u/1412774/02%20-%2012-11-2012/index.html" width="650px"></iframe><br />
<br />
Lien pour le projet sur GitHub : <a href="https://github.com/Pseudopode/javascript_pseudocolors" target="_blank">https://github.com/Pseudopode/javascript_pseudocolors</a> et <a href="https://dl.dropbox.com/u/1412774/Blog/BlogSpot/Javascript_pseudocolors/pseudocolors_javascript.zip" target="_blank">l'archive .zip du projet</a>.Anonymoushttp://www.blogger.com/profile/04274985400485796581noreply@blogger.com1tag:blogger.com,1999:blog-6661347336443823313.post-58189765502893968972012-12-03T08:40:00.001-08:002012-12-03T08:40:21.306-08:00Pseudocolorisation with Javascript<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<a href="http://podeplace.blogspot.fr/2012/11/opencv-pseudocolors.html" target="_blank">In the last post</a>, I spoke about pseudo-colorization. That particular example was done in C, with OpenCV.<br />
I'm going to show you now how to do the same thing in Javascript, inside a browser.<br />
<br />
<i>(If the explanation is too long, you can scroll down directly to the demo, at the end of this post).</i><br />
To sum it up :<br />
1) grayscale an image in a <canvas><br />
2) load a colormap in another <canvas><br />
3) apply recolor algorithm (lookup from the grayscaled image in the colormap)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCk9tNl5UycEYTdI5WXayNEYM0UnIWdUFmkBI7MRHdq9W0VZi6LTFTTjZCpohyphenhyphencX4OGZtD7p6mfEoVpctH2USLwFMIn7iZBFiaGFfq2JK60gAgVsQMhnPLRf1L1SUPR6Ga6l5rnMisRrE/s1600/HTML5-logo.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCk9tNl5UycEYTdI5WXayNEYM0UnIWdUFmkBI7MRHdq9W0VZi6LTFTTjZCpohyphenhyphencX4OGZtD7p6mfEoVpctH2USLwFMIn7iZBFiaGFfq2JK60gAgVsQMhnPLRf1L1SUPR6Ga6l5rnMisRrE/s1600/HTML5-logo.png" /></a></div>
<br />
<h3>
Javascript & the <canvas> object</h3>
In "HTML5" (whatever the name really implies), with a recent browser (Opera, Chrome, Firefox, Safari...), you can work with a special element : the <canvas>.<br />
A 2D canvas hold all the pixels of a raster image, as an element inside a webpage. The benefits of using a <canvas> object instead an <img> is the ability to access all the pixels of the image stored inside (the <img> is strictly passive, it can just display an image).<br />
<br />
At the beginning, Javascript was slow. But now, you the common browsers are using some optimized JIT Javascript VM, allowing you to do some tasks previously only doable on desktop.<br />
Image processing tasks are now possible on a (somewhat) recent computer, inside a web app.
<br />
The javascript code shwon here is going to implement the OpenCV algorithm in the previous post.<br />
<br />
<h4>
Representing color images in memory</h4>
<br />
First, you retrieve an array of all the pixels of the image, you then get a second array, the array of the pixels from the colormap (a pixel wide column of 256 pixels).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDFr705gS1DeKUdcSVMviN836b6bqlSjHWWcn0uebaxdTmia5Niu_TI-u4iwu1V_zJXlLWVuqqoALq5WISUvMk00AHKawNxEwmUMmSbidPwWrpTZLNafI2zjMBEEx6UIEUd_ugp7jRUBw/s1600/colormap3.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDFr705gS1DeKUdcSVMviN836b6bqlSjHWWcn0uebaxdTmia5Niu_TI-u4iwu1V_zJXlLWVuqqoALq5WISUvMk00AHKawNxEwmUMmSbidPwWrpTZLNafI2zjMBEEx6UIEUd_ugp7jRUBw/s400/colormap3.png" width="1" /></a></div>
<br />
After that, it's simply a matter of iterating over all the pixels of the image, getting the luminosity level, seeking in the colormap the R,G,B triplet associated to that value, and writing it back in the array (you can find a descriptive image in the previous post : <a href="http://podeplace.blogspot.fr/2012/11/opencv-pseudocolors.html" target="_blank">http://podeplace.blogspot.fr/2012/11/opencv-pseudocolors.html</a>)<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCw-3F4YzsAwtuaerals5I1QKb_73ABkSeZ6je7S2IEVnv0QZtSsFoGupjUdsEa2syvZPfn0-F8F0x3XFsKpQkzM34GFph6_G8WC1p6Paa3QdS7g1RnYTfIOd3Yqb06xduCk8915aqCmo/s1600/IplImageRGB.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="195" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCw-3F4YzsAwtuaerals5I1QKb_73ABkSeZ6je7S2IEVnv0QZtSsFoGupjUdsEa2syvZPfn0-F8F0x3XFsKpQkzM34GFph6_G8WC1p6Paa3QdS7g1RnYTfIOd3Yqb06xduCk8915aqCmo/s400/IplImageRGB.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Color Image as seen by OpenCV : 3 colorplanes (Red, Green, Blue)</td></tr>
</tbody></table>
OpenCV has two different image concept : grayscale and color. A grayscale image has only one colorplane (the gray intensity), but color images have three colorplanes (one for each Red, Green & Blue). This way, the library can handle memory more efficiently.<br />
<br />
<br />
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyCzqrfLDD-gtLJxM42mND27JiceEsm2KNS5m4inamye8nY1KIlx8_W50ps1HmjS0GbG46gQlv7_QW5UmTRgbFCkKH9YB8WWpT4owfPmWs76acxzjg3VgefFgGQG9mf3iAtaWfn4928dc/s1600/IplImage_Gray.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="166" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyCzqrfLDD-gtLJxM42mND27JiceEsm2KNS5m4inamye8nY1KIlx8_W50ps1HmjS0GbG46gQlv7_QW5UmTRgbFCkKH9YB8WWpT4owfPmWs76acxzjg3VgefFgGQG9mf3iAtaWfn4928dc/s400/IplImage_Gray.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Grayscale Image : OpenCV is using only one colorplane in memory</td></tr>
</tbody></table>
<div style="text-align: left;">
</div>
<br />
The pixel array of the <canvas> element, however, only knows 4 colorplanes images. Red, green, blue, and Alpha (for transparency). <br />
That means that if we want to work with a "true" grayscale image, we need to calculate the grayscale value in one colorplane and copy that colorplane over in the two other color channel (leaving the transparency channel fully opaque).
<br />
</canvas><br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkToFfshkUvffw7D60zkHoNldBqt0IeUz5edhlZK95SDEpy0ctlyueZy37bgo6JCS9PNTp2vQ5t3_2SMLH5vwXvXdpx3Irc_bpgzdB0CYRaUucphUw9Ng97LP8_rsZe8k_GYdWZ8r8Pxs/s1600/html5_canvas_lena_inmemory.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="204" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkToFfshkUvffw7D60zkHoNldBqt0IeUz5edhlZK95SDEpy0ctlyueZy37bgo6JCS9PNTp2vQ5t3_2SMLH5vwXvXdpx3Irc_bpgzdB0CYRaUucphUw9Ng97LP8_rsZe8k_GYdWZ8r8Pxs/s640/html5_canvas_lena_inmemory.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Image en mémoire pour un <canvas> : 4 pixels de couleurs consécutifs (Rouge, Vert, Bleu, Alpha) pour chaque pixel coloré</td></tr>
</tbody></table>
We can't load a grayscale image in JPEG or PNG inside the canvas, since we don't really know how the software, used to save that image, choose to store the grayscale value inside. We don't know if the software choose to save only one colorplane (grayscale) or several planes (even for a black & white image !). Furthermore, some file formats doesn't allow the choice for the number of colorplanes when saving files. We then need to start with a canvas on which an image has been saved, and convert those colors in grayscale, inside the <canvas> object.<br />
<br />
<br />
<h3>
Convert a color canvas to grayscale </h3>
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRiTqTLV8bg4LURqprd1iPx6E02oF7sdkyvZ7c8EEft_i9bYIdI0hUcwDpnF0qdwvpZ0XR-MJUAKw0Ap-A_1Nz4aTLuguwSYyHxQIf_-Ad7TV2evcUuuJB3ILTNuWvGWi6lBBoSzcmea8/s1600/169_graymix.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img alt="" border="0" height="186" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRiTqTLV8bg4LURqprd1iPx6E02oF7sdkyvZ7c8EEft_i9bYIdI0hUcwDpnF0qdwvpZ0XR-MJUAKw0Ap-A_1Nz4aTLuguwSYyHxQIf_-Ad7TV2evcUuuJB3ILTNuWvGWi6lBBoSzcmea8/s200/169_graymix.png" title="Mélange RGB vers gris, moyenne simple" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">RGB to Gray Mix, via averaging</td></tr>
</tbody></table>
The grayscale conversion process of an RGB color can be as simple as the mean of the 3 color channels, divided by 3. But the resulting grayscale isn't the best match for the original color. Since the human eye is the most sensitive to green, and less sensitive to blue (due to the biological innards of the retina cells), we can weight each channel to contribute more or less to the final luminosity level.<br />
<br />
You can find online several values to define the blending of the three values. For example, in the PAL/NTSC format(used by the analog TV), the luminance channel is defined as is :<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9hyphenhyphenqQUVijd5RQ-O2NcPd8zZykBf_AXfv7DJMHkp-_kC9dCPEKuEx537WYyWSAG89zWF5o3BDSofUh0Jz7jeGwoooPnB7JOM8unopXWAPM4UPfeQubGBBuOCdaoBz66SMlLgkTHDup0jg/s1600/174_graymix.png" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="158" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9hyphenhyphenqQUVijd5RQ-O2NcPd8zZykBf_AXfv7DJMHkp-_kC9dCPEKuEx537WYyWSAG89zWF5o3BDSofUh0Jz7jeGwoooPnB7JOM8unopXWAPM4UPfeQubGBBuOCdaoBz66SMlLgkTHDup0jg/s200/174_graymix.png" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">RGB to Gray mix (closer to human perception)</td></tr>
</tbody></table>
<br />
<b><span style="color: #073763;">lum = 0.299 * red + 0.587 * green + 0.114 * blue</span></b><br />
<br />
The CIE 1931 luminance is defined as :<br />
<br />
<span style="color: #073763;"><b>Y = 0.2126 * red + 0.7152 * green + 0.0722 * blue</b></span><br />
<br />
(The values used by OpenCV are <b>Red : 0.212671</b>, <b>Green : 0.715160</b>, and <b>Blue : 0.072169</b>).<br />
<br />
As some other Javascript example over the web, I choose the following values :<br />
<br />
<span style="background-color: #cccccc;"><b> brightness = 0.34 * red + 0.5 * green + 0.16 * blue </b></span><br />
<br />
All those models take into account the human sensitivity for green.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgw-oCWbf3BGtvJsvGhNviOzBn4vyzZ9ssVvt7MO2ftbDwWg5mb8et2OEgHbvZiLL0FpaOe2PefXZVO5dibb-BfVASLooGiVgLXLKbfNj4zJ0GuRb84LqOLb89gHio_81i1Gnk1hz8PXfc/s1600/169-174.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="95" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgw-oCWbf3BGtvJsvGhNviOzBn4vyzZ9ssVvt7MO2ftbDwWg5mb8et2OEgHbvZiLL0FpaOe2PefXZVO5dibb-BfVASLooGiVgLXLKbfNj4zJ0GuRb84LqOLb89gHio_81i1Gnk1hz8PXfc/s200/169-174.png" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Difference between the gray from averaging and gray from the previous physiological model.</td></tr>
</tbody></table>
The difference between the gray values calculated by the two methods is small but noticeable.<br />
<br />
<h4>
Algorithm in javascript</h4>
In our example, we can now grayscale the image at startup, and then work with that image to apply the colormap.<br />
<br />
When working with a <i>canvas </i><b>cvs</b> and its associated <i>context</i> <b>ctx</b>, you can retrieve the array of specific image data via <br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
<b> var myImageData = ctx.getImageData(0, 0, cvs.width, cvs.height);<br /> </b>
</div>
<br />
Inside that variable, there's a data array, storing the colored pixels in the following order [<b><span style="color: red;">red</span></b>, <span style="color: #274e13;"><b>green</b></span>, <b><span style="color: blue;">blue</span></b>, <span style="color: #666666;"><b>alpha</b></span>, <b><span style="color: red;">red</span></b>, <b><span style="color: #274e13;">green</span></b>, <span style="color: blue;"><b>blue</b></span>, <span style="color: #666666;"><b>alpha</b></span>,...].<br />
<br />
In javascript, to avoid a slow DOM traversal when accessing the <i><b>myImageData.data</b></i>, you can detach that pixel array as a new var<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
<b> var dataSrc = myImageData.data;<br /> </b>
</div>
<br />
and access what's inside that <b>dataSrc</b>, for a quicker access.<br />
<br />
Since that array is a flat 1-dimensional one, you can access the various pixel with a regular loop like that :<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
<b>for(var y = 0; y < height; y++){ <br />
for(var x = 0; x < width; x++){<br />
index = (x + y * width) * 4;<br />
dataSrc[index+0] = ROUGE;<br />
dataSrc[index+1] = VERT;<br />
dataSrc[index+2] = BLEU;
</b><br />
<div style="color: #444444;">
<b> //dataSrc[index+3] = ALPHA; //no modifications</b></div>
}<br />
}
</div>
<br />
Don't change the alpha value here. The <span style="background-color: #cccccc;"><b> *4 </b></span> is there because to get to the next pixel in memory, you need to jump over 4 values (RGBA).<br />
<br />
If you want to apply a grayscale conversion on that image, you can write back the new luminosity value in all the red, green and blue channel, for display purpose (however, the image is going to be three time more luminous than the true RGB->Grayscale conversion, although only on a computer display). <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4AJtxIxyfxaaLG22LJW-EE3YrCR9uLlo6ASPuFye9z5kArP6b8N-Uuz-0uPXM1UmJetxJ8JU80bdqYdHxprjfiZ8GfNHKuy4Zbsq4hOv86J-ziVX2GCZkgR7hGviwVAEO7H9JdBBbeFc/s1600/gray2canvas.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4AJtxIxyfxaaLG22LJW-EE3YrCR9uLlo6ASPuFye9z5kArP6b8N-Uuz-0uPXM1UmJetxJ8JU80bdqYdHxprjfiZ8GfNHKuy4Zbsq4hOv86J-ziVX2GCZkgR7hGviwVAEO7H9JdBBbeFc/s320/gray2canvas.png" width="265" /></a></div>
<br />
<br />
Don't worry, when you work with that pixel array on the next stage, to apply the colormap, you are going to use the luminosity value of only one channel, since it's the same value on all three (and that's why, although the displayed image is not the "true" grayscaled one, you are going to have the true colored image in the end).<br />
If we choose to only store the grayscale value in one channel, the red one for example, the displayed image would be only in tint of red (and it feels a bit strange to consider a red-only, or blue-only or green-only image as "grayscale").<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYPziqxkX4EkxRNaldXdpc4JBhbUfpehmF89cLS8B_GLbi9ZdePV26bHKQW9AkQAvld-0Yr1gY-HB1ZImFjeq39TfOPbcAAOx8tRXT27DZOf1wAq-lnLsd3A2S9QZSoYMzP1mehNxNnwQ/s1600/gray2canvas2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYPziqxkX4EkxRNaldXdpc4JBhbUfpehmF89cLS8B_GLbi9ZdePV26bHKQW9AkQAvld-0Yr1gY-HB1ZImFjeq39TfOPbcAAOx8tRXT27DZOf1wAq-lnLsd3A2S9QZSoYMzP1mehNxNnwQ/s320/gray2canvas2.png" width="267" /></a></div>
<br />
That kind of pixel access is the basis to all image operations and filters in Javascript (and more generally in all general purpose language when dealing with images).<br />
After all the modifications are done, don't forget to write back those pixel in the image !<br />
<br />
<div style="background-color: #cccccc; font-family: Courier;">
<br />
myImageData.data = dataRes; <span style="color: #666666;">//reattache the variable</span><br />
ctx.putImageData(myImageData,0,0); <span style="color: #666666;">//write it back in the canvas</span></div>
<br />
<br />
In the provided example, we load a regular JPEG picture, grayscale it on the click of a button, and then recolor it when the user click on one colormap. You need to reload the page if you want to use another colormap, to trigger the proper color -> grayscale conversion. If not, you are going to apply various colormap on already modified pictures, leading to some unpleasant effects in the end.<br />
<br />
<h4>
Demonstration in javascript :</h4>
<iframe height="600px" src="https://dl.dropbox.com/u/1412774/02%20-%2012-11-2012/index.html" width="650px"></iframe><br />
<br />
Link of the project on GitHub : <a href="https://github.com/Pseudopode/javascript_pseudocolors" target="_blank">https://github.com/Pseudopode/javascript_pseudocolors</a> and <a href="https://dl.dropbox.com/u/1412774/Blog/BlogSpot/Javascript_pseudocolors/pseudocolors_javascript.zip" target="_blank">the .zip archive of the project</a>.Anonymoushttp://www.blogger.com/profile/04274985400485796581noreply@blogger.com0tag:blogger.com,1999:blog-6661347336443823313.post-21774725085478077132012-11-11T07:24:00.001-08:002012-11-15T03:12:12.837-08:00Pseudocouleurs avec OpenCV<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsPGSbVgPQIA-0VwDfeON1832HLswtACbgC0lZbHZyw0eZQEajUSKaGMhDbzTu_FOUOzkNn2smFomsD-8EdILEz8aaD_Bw4K8gym35xNbU85NIOldVYwW2kDnZeaZ1xqpsbAfD-LOzDdA/s1600/moon05.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="La Lune en niveaux de gris (photo)" border="0" height="193" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsPGSbVgPQIA-0VwDfeON1832HLswtACbgC0lZbHZyw0eZQEajUSKaGMhDbzTu_FOUOzkNn2smFomsD-8EdILEz8aaD_Bw4K8gym35xNbU85NIOldVYwW2kDnZeaZ1xqpsbAfD-LOzDdA/s200/moon05.jpg" title="La Lune en niveaux de gris" width="200" /></a></div>
<br />
Les fausses couleurs, ou <b>pseudo-couleurs</b>, sont utilisées pour rajouter une gamme de couleurs à une image en niveaux de gris.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO62OlNg2riI38ypSH0MxG7XO-Dl1RFXGrzwMQZclarJ9BacJEkCDmL8KwjxSDqJLQEApn28v0PaiTv1XRAARnzEAv2UNl2FeQq_cJthGj8QpznkSZ3gnFZeNiERWw9IeL4mlJM7LPI0A/s1600/moon_pseudocolor.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="La Lune en pseudo-couleurs, colorisée avec OpenCV" border="0" height="193" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO62OlNg2riI38ypSH0MxG7XO-Dl1RFXGrzwMQZclarJ9BacJEkCDmL8KwjxSDqJLQEApn28v0PaiTv1XRAARnzEAv2UNl2FeQq_cJthGj8QpznkSZ3gnFZeNiERWw9IeL4mlJM7LPI0A/s200/moon_pseudocolor.jpg" title="La Lune en pseudo-couleurs" width="200" /></a></div>
<br />
Le processus est différent d'une <i>recolorisation</i>, où vous essayez de retrouver les couleurs les plus plausibles sur une vieille pellicule cinéma ou un cliché, pris lorsque seuls les systèmes noirs et blancs étaient disponibles facilement. La recolorisation est une étape mathématique complexe, alors que la pseudocolorisation est très simple : vous mettez en correspondance chaque intensité spécifique de chaque pixel de l'image d'origine avec un triplet de valeurs RGB provenant d'une échelle de couleurs fournie au préalable.<br />
<br />
La technique est largement utilisée en photographie aérienne, en imagerie thermique et dans d'autres domaines. L'adjonction d'un nouveau canal d'information par l'utilisation de la couleur peut apporter une nouvelle lecture de l'information.<br />
<br />
Différentes techniques sont possibles pour le choix du triplet des valeurs RGB.<br />
<br />
Dans la suite de l'article, les exemples de code sont plus spécifiquement liés à l'utilisation d'OpenCV.<br />
<a href="http://blog.damiles.com/?p=244" target="_blank">Ici</a>, l'auteur utilise 3 sinusoides pour récupérer trois valeurs d'intensité différentes, une pour chaque canal. C'est rapide, mais ce n'est pas toujours facile de savoir à quelle couleur correspond telle ou telle intensité de blanc.<br />
<br />
Je propose ici une mise en correspondance directe de la valeur d'intensité du pixel en niveau de gris à une couleur spécifique, choisie dans une table des couleurs. Cette table de couleurs est fournie en tant qu'image additionnelle.<br />
L'intensité de blanc est dans l'espace [0..255] (image 8 bits), et la couleur est représentée comme un triplet classique <b>R([0..255])</b>,<b>G([0..255])</b>,<b>B([0..255])</b>.<br />
<br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCVsqO_WP-Cy7dFyORnxxYSSXre4pPrEHLyDTBYTrx5eafNECkJSz_YZOxcz-NWkXVDCvHtmz3LYGqgyIUxd57G_KX1L4LPiI-djHR76qB8ozPZmcM2o8X_38VX1irw4VwG5HoFo7bOZQ/s1600/schema.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Explication du processus de recolorisation" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCVsqO_WP-Cy7dFyORnxxYSSXre4pPrEHLyDTBYTrx5eafNECkJSz_YZOxcz-NWkXVDCvHtmz3LYGqgyIUxd57G_KX1L4LPiI-djHR76qB8ozPZmcM2o8X_38VX1irw4VwG5HoFo7bOZQ/s1600/schema.png" title="Explication du processus de recolorisation" /></a></div>
<br />
Vous pouvez également utiliser les fonctions LUT d'OpenCV pour construire une table de lookup, mais je voulais essayer quelque chose de plus "manuel".<br />
<br />
L'idée est très simple : vous tracez un gradient de couleur sur une image de 255 pixels de haut, et d'un pixel de large, qui sera notre <b>colormap</b>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR0hr557fZMPe3AGzjFQdPALh6UVRhxeJQVbBsd3Nf-dDB8HWzKuehDkB5IaaAxtoi4ZB6IUSQBdP0-WTpmsByz9dJsAgewmXBOP8oJThYgBIhFwq-tVqlLyCyNIqtF-R4Fb3QoYze8YM/s1600/colors.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR0hr557fZMPe3AGzjFQdPALh6UVRhxeJQVbBsd3Nf-dDB8HWzKuehDkB5IaaAxtoi4ZB6IUSQBdP0-WTpmsByz9dJsAgewmXBOP8oJThYgBIhFwq-tVqlLyCyNIqtF-R4Fb3QoYze8YM/s200/colors.jpg" width="20" /></a></div>
<br />
Sur l'image en niveaux de gris, vous prenez la valeur de chaque pixel, et vous regardez dans la colormap à la coordonnée (<i>0</i>,<i>valeur_d_intensité</i>). (En fait, vous devez faire l'opération trois fois, une pour chaque canal R,G et B).<br />
Les 3 valeurs stockées à ces coordonnées sont celles du pixel nouvellement coloré !<br />
<br />
<br />
<br />
J'ai élargi un peu l'image de la colormap en largeur, c'est ainsi plus facile de contrôler les couleurs. Seule la première colonne de pixels de l'image est utilisée par le code.<br />
<br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3yZEhefNWNWwt6YCGKjRCqaR3sxVoxE-BQx8p9xr4qZxIz6Cne3Ls-IJYg_N1OZoW-0w82DTHwCMsB49TgZ5rsly44D6iHE1jct2ONcnNr9-IrYKDWttoZfYEcPpvY0HTGSjwgKdgtfY/s1600/baboon.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3yZEhefNWNWwt6YCGKjRCqaR3sxVoxE-BQx8p9xr4qZxIz6Cne3Ls-IJYg_N1OZoW-0w82DTHwCMsB49TgZ5rsly44D6iHE1jct2ONcnNr9-IrYKDWttoZfYEcPpvY0HTGSjwgKdgtfY/s200/baboon.jpg" width="200" /></a></div>
(Dans l'archive fournie comme exemple, la colormap est un fichier JPEG. Pour obtenir les meilleurs résultats, utilisez un format de fichier non-destructif, comme le PNG ou le BMP).<br />
<br />
Avec OpenCV, vous pouvez utiliser <i>cvSplit()</i> pour obtenir trois images différentes, une par canal de couleur de l'image originale.<br />
<br />
<br />
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQXfkSoikXFlrhm5EVaJ_CvTO3EXFSYjzMihFv72O-5KBEB8hchoMhOnTHfi_PozE4vwCnh8fBjodDmDk5nxC8UgFBc5Jdk4TtkkWkhZt-sqLOxSbd_koOdyb3PQFN8-kvcyBZOAiuFYo/s1600/baboon_pseudocolor.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQXfkSoikXFlrhm5EVaJ_CvTO3EXFSYjzMihFv72O-5KBEB8hchoMhOnTHfi_PozE4vwCnh8fBjodDmDk5nxC8UgFBc5Jdk4TtkkWkhZt-sqLOxSbd_koOdyb3PQFN8-kvcyBZOAiuFYo/s200/baboon_pseudocolor.jpg" width="200" /></a> Vous pouvez trouver ici un exemple Visual Studio assez rapide, qui utilise OpenCV, pour coloriser une image en niveaux de gris passée en paramètre sur la ligne de commande, à l'aide d'une colormap. Vous devez également fournir le nom de la colormap. Le programme écrit l'image couleur résultant dans une image bitmap.<br />
<br />
Sur l'exemple du babouin, vous pouvez vous rendre compte que les gris de l'image originale sont concentrés autour d'une couleur moyenne (proche de 128). Il n'y a pas beaucoup de gris très foncés ou très clairs dans l'image, l'image finale est donc principalement jaune, la couleur moyenne de la colormap. Pour obtenir de meilleurs résultats, vous pouvez effectuer quelques manipulation sur l'image d'origine (comme une équalisation de l'histogramme, pour couvrir de manière plus uniforme les couleurs du noir vers le blanc).<br />
<br />
<br />
<a href="https://dl.dropbox.com/u/1412774/Blog/BlogSpot/OpenCV_Pseudocolors/False_colors_OpenCV.zip" target="_blank">Voici un lien vers l'archive contenant le code</a>, vous pouvez également parcourir le code sur <a href="https://github.com/Pseudopode/OpenCV_pseudocolors" target="_blank">GitHub</a>.<br />
<br />
J'ai utilisé la syntaxe C d'OpenCV. L'exemple se compile avec Visual Studio 2005 et plus récent (normalement).<br />
<br />
<span style="font-size: x-small;">(A propos, l'image de la Lune est dans le domain public, l'image du babouin provient du répertoire d'OpenCV).</span>Anonymoushttp://www.blogger.com/profile/04274985400485796581noreply@blogger.com0tag:blogger.com,1999:blog-6661347336443823313.post-30223882753475210762012-11-11T07:24:00.000-08:002012-11-11T07:24:00.089-08:00OpenCV Pseudocolors<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsPGSbVgPQIA-0VwDfeON1832HLswtACbgC0lZbHZyw0eZQEajUSKaGMhDbzTu_FOUOzkNn2smFomsD-8EdILEz8aaD_Bw4K8gym35xNbU85NIOldVYwW2kDnZeaZ1xqpsbAfD-LOzDdA/s1600/moon05.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Grayscale Moon" border="0" height="193" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsPGSbVgPQIA-0VwDfeON1832HLswtACbgC0lZbHZyw0eZQEajUSKaGMhDbzTu_FOUOzkNn2smFomsD-8EdILEz8aaD_Bw4K8gym35xNbU85NIOldVYwW2kDnZeaZ1xqpsbAfD-LOzDdA/s200/moon05.jpg" title="Grayscale Moon" width="200" /></a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
False colors, or <b>pseudocolors</b>, is the method you use when you want to augment grayscale pictures with predetermined colors. </div>
<div style="text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO62OlNg2riI38ypSH0MxG7XO-Dl1RFXGrzwMQZclarJ9BacJEkCDmL8KwjxSDqJLQEApn28v0PaiTv1XRAARnzEAv2UNl2FeQq_cJthGj8QpznkSZ3gnFZeNiERWw9IeL4mlJM7LPI0A/s1600/moon_pseudocolor.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Pseudocolored Moon with OpenCV" border="0" height="193" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO62OlNg2riI38ypSH0MxG7XO-Dl1RFXGrzwMQZclarJ9BacJEkCDmL8KwjxSDqJLQEApn28v0PaiTv1XRAARnzEAv2UNl2FeQq_cJthGj8QpznkSZ3gnFZeNiERWw9IeL4mlJM7LPI0A/s200/moon_pseudocolor.jpg" title="Pseudocolored Moon" width="200" /></a></div>
<div style="text-align: justify;">
<br />
It is very different from <i>recolorization</i>, where you try to retrieve the plausible true colors of an old picture or movie, shot when only black & white recording systems where widely available. Recolorization is somewhat mathematically tricky, whereas pseudocoloring is very straightforward : you map every specific pixel intensity (the "whiteness" value of the pixel) of the original image to the triplet of RGB values from a given color scale.<br />
It is widely used in aerial photography, in thermal imagery and other field where you can provide that new channel of information to literally shed a new light on the data (pun intended...).<br />
<br />
You can use different technique to choose the triplet of RGB values.</div>
<div style="text-align: justify;">
<br />
From now, I'm going to give some code more specifically linked to OpenCV.<br />
<a href="http://blog.damiles.com/?p=244" target="_blank">Here</a>, for example, you can see the use of sine functions to retrieve three different values for each channel. It's quick, but it's not always easy to know which white intensity is going to match which color.<br />
<br />
What I'm providing here is a straight mapping from pixel value in the grayscale range to a specific color, chosen from a color table. That color table is provided as an image.<br />
The white intensity value is in the range 0 to 255 (8 bit depth image) and the color is represented as a classic <b>R([0..255])</b>,<b>G([0..255])</b>,<b>B([0..255])</b> triplet.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCVsqO_WP-Cy7dFyORnxxYSSXre4pPrEHLyDTBYTrx5eafNECkJSz_YZOxcz-NWkXVDCvHtmz3LYGqgyIUxd57G_KX1L4LPiI-djHR76qB8ozPZmcM2o8X_38VX1irw4VwG5HoFo7bOZQ/s1600/schema.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Recolorization process explanation" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCVsqO_WP-Cy7dFyORnxxYSSXre4pPrEHLyDTBYTrx5eafNECkJSz_YZOxcz-NWkXVDCvHtmz3LYGqgyIUxd57G_KX1L4LPiI-djHR76qB8ozPZmcM2o8X_38VX1irw4VwG5HoFo7bOZQ/s1600/schema.png" title="Recolorization process explanation" /></a></div>
<br />
<br />
You can use LUT functions in OpenCV to construct LookUp Tables, but I wanted to try something more handcrafted.<br />
<br />
The idea is really simple : you draw a color gradient on an image of 255 pixels height, and with a width of 1 pixel, which is going to be your <b>colormap</b>. </div>
<div style="text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR0hr557fZMPe3AGzjFQdPALh6UVRhxeJQVbBsd3Nf-dDB8HWzKuehDkB5IaaAxtoi4ZB6IUSQBdP0-WTpmsByz9dJsAgewmXBOP8oJThYgBIhFwq-tVqlLyCyNIqtF-R4Fb3QoYze8YM/s1600/colors.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="Colormap used by OpenCV" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhR0hr557fZMPe3AGzjFQdPALh6UVRhxeJQVbBsd3Nf-dDB8HWzKuehDkB5IaaAxtoi4ZB6IUSQBdP0-WTpmsByz9dJsAgewmXBOP8oJThYgBIhFwq-tVqlLyCyNIqtF-R4Fb3QoYze8YM/s200/colors.jpg" title="Colormap" width="20" /></a></div>
<div style="text-align: justify;">
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
From the grayscale image, you take the value of each and every pixel, and you search in the colormap at the coordinates (<i>0</i>,<i>intensity_value</i>) (in fact, you must do that three times, one per color channel R,G and B). <br />
The 3 values stored at those coordinates are your new colored pixel value !</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
I stretched a bit the width of the colormap, it's easier to visually check the colors, but only the first column of the image is really used by the code. </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3yZEhefNWNWwt6YCGKjRCqaR3sxVoxE-BQx8p9xr4qZxIz6Cne3Ls-IJYg_N1OZoW-0w82DTHwCMsB49TgZ5rsly44D6iHE1jct2ONcnNr9-IrYKDWttoZfYEcPpvY0HTGSjwgKdgtfY/s1600/baboon.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="Grayscale Baboon" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3yZEhefNWNWwt6YCGKjRCqaR3sxVoxE-BQx8p9xr4qZxIz6Cne3Ls-IJYg_N1OZoW-0w82DTHwCMsB49TgZ5rsly44D6iHE1jct2ONcnNr9-IrYKDWttoZfYEcPpvY0HTGSjwgKdgtfY/s200/baboon.jpg" title="Grayscale Baboon" width="200" /></a></div>
<div style="text-align: justify;">
(In the provided example archive, the colormap is a JPEG file. For the best result, you need to use a non-destructive file format, like PNG or BMP).<br />
<br />
In OpenCV, you use <i>cvSplit()</i> to get three different images as color channel from the original.<br />
<br />
I provide a quick Visual Studio example, using OpenCV, to color a grayscale picture given on the command line to a new color mapped picture. You must also provide the name of the colormap. The program writes the produced colored image as a bitmap.</div>
<div style="text-align: justify;">
</div>
<div style="text-align: justify;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQXfkSoikXFlrhm5EVaJ_CvTO3EXFSYjzMihFv72O-5KBEB8hchoMhOnTHfi_PozE4vwCnh8fBjodDmDk5nxC8UgFBc5Jdk4TtkkWkhZt-sqLOxSbd_koOdyb3PQFN8-kvcyBZOAiuFYo/s1600/baboon_pseudocolor.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="Pseudocolored Baboon" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQXfkSoikXFlrhm5EVaJ_CvTO3EXFSYjzMihFv72O-5KBEB8hchoMhOnTHfi_PozE4vwCnh8fBjodDmDk5nxC8UgFBc5Jdk4TtkkWkhZt-sqLOxSbd_koOdyb3PQFN8-kvcyBZOAiuFYo/s200/baboon_pseudocolor.jpg" title="Pseudocolored Baboon" width="200" /></a></div>
<div style="text-align: justify;">
</div>
<div style="text-align: justify;">
</div>
<div style="text-align: justify;">
</div>
<div style="text-align: justify;">
<br />
On the baboon example, you can see that since the grays in the original image are centered around a midpoint (close to 128). There isn't a lot of very dark or very bright gray in the image, so the resulting picture is mostly yellow, the middle color of the color map. It can be interesting to do some small image enhancement on the original before using the recoloring (something like Histogram Stretching, to cover more evenly the range from black to white).<br />
<br />
<a href="https://dl.dropbox.com/u/1412774/Blog/BlogSpot/OpenCV_Pseudocolors/False_colors_OpenCV.zip" target="_blank">Here's a link to the code archive</a>, y<span id="goog_334996978">ou can also browse the code on <a href="https://github.com/Pseudopode/OpenCV_pseudocolors" target="_blank">GitHub</a>.</span><span id="goog_334996979"></span><br />
I used OpenCV's C syntax. The example can be compiled from Visual Studio 2005, and should work with more recent builds. </div>
<div style="text-align: justify;">
<br />
<span style="font-size: x-small;">(And by the way, the moon picture is public domain, the baboon picture is coming from the OpenCV install directory).</span></div>
Anonymoushttp://www.blogger.com/profile/04274985400485796581noreply@blogger.com2