<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>NumPy &#8211; 開発記録</title>
	<atom:link href="https://www.kthksgy.com/tag/numpy/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.kthksgy.com</link>
	<description>開発メモです。現在レイアウトが一部崩れている箇所があります。</description>
	<lastBuildDate>Wed, 11 Nov 2020 04:34:09 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2.2</generator>
	<item>
		<title>numpy.ndarray.astype(numpy.uint8)の落とし穴【Python/Numpy】</title>
		<link>https://www.kthksgy.com/python/numpy-clip-astype/</link>
					<comments>https://www.kthksgy.com/python/numpy-clip-astype/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Sun, 03 May 2020 21:43:45 +0000</pubDate>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[NumPy]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=10</guid>

					<description><![CDATA[float64やint32のndarrayをuint8に型変換した時に自動的に近い境界値に丸めてくれると思っていたのですがそうではないようです。 OpenCVで画像を読み込んでNumPyで弄った後に符号無し8ビット整数に&#8230;]]></description>
										<content:encoded><![CDATA[<p><code>float64</code>や<code>int32</code>の<code>ndarray</code>を<code>uint8</code>に型変換した時に自動的に近い境界値に丸めてくれると思っていたのですがそうではないようです。<br />
<span id="more-10"></span></p>
<p>OpenCVで画像を読み込んでNumPyで弄った後に符号無し8ビット整数に変換して書き出そうとした時に躓いたのでメモ。</p>
<h2>概要</h2>
<p>元の型より精度が低い整数型への型変換<code>numpy.ndarray.astype(dtype)</code>は変換前に<code>numpy.ndarray.clip(min, max)</code>をすべきです。</p>
<p>例えば、<code>a.astype(np.uint8)</code>する時は以下のように<code>a.clip(0, 255)</code>で値を先に制限します。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; import numpy as np
&gt;&gt;&gt; a = np.array([-1, 0, 255, 256])
# こうではなくて
&gt;&gt;&gt; a.astype(np.uint8) 
array([255,   0, 255,   0], dtype=uint8)
# こうする
&gt;&gt;&gt; a.clip(0, 255).astype(np.uint8) 
array([  0,   0, 255, 255], dtype=uint8)
</code></pre>
<p>以下は詳細です。</p>
<h2>環境</h2>
<p>対話環境で確認しました。</p>
<pre><code class="language-python line-numbers">$ python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt; import numpy as np
&gt;&gt;&gt; np.__version__
'1.18.3'
</code></pre>
<h2>numpy.ndarray.astype(numpy.uint8)とは</h2>
<p>読んで字の如く、型変換のメソッドです。</p>
<p><code>numpy.ndarray</code>に対して型名を引数にして呼ぶと、配列が指定した型に変換されて返ってきます。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; a = np.zeros((32, 32, 3), dtype=np.float16)
&gt;&gt;&gt; a.dtype
dtype('float16')
&gt;&gt;&gt; a.astype(np.uint8).dtype
dtype('uint8')
</code></pre>
<h2>落とし穴</h2>
<p><code>uint8</code>では<code>0~255</code>の数値を表せますが、その範囲外の数値を変換した時に想像とは違う数値変換がされます。</p>
<h3>範囲内の場合</h3>
<p><code>0~255</code>の範囲内の場合は想像した通りの変換、つまり何も変換されません。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; a = np.array([0, 1, 10, 64, 150, 255])
&gt;&gt;&gt; a.astype(np.uint8)
array([  0,   1,  10,  64, 150, 255], dtype=uint8)
</code></pre>
<h3>範囲外の場合</h3>
<p>一方、範囲外では少なくとも想像とは違う結果になりました。変換は以下の通りです。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; b = np.array([256, 511, 512, 32767, 32768, -1, -128, -127, -32768, -32767])
&gt;&gt;&gt; b.astype(np.uint8)
array([  0, 255,   0, 255,   0, 255, 128, 129,   0,   1], dtype=uint8)
</code></pre>
<p>浮動小数点数からの変換は以下の通り。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; c = np.array([256.1, 511.3, 512.2, 32767.8, 32768.1, -1.2, -128.0, -127.9, -32768.5, -32767.4])
&gt;&gt;&gt; c.astype(np.uint8)
array([  0, 255,   0, 255,   0, 255, 128, 129,   0,   1], dtype=uint8)
</code></pre>
<p>想像では、<code>256 -&gt; 255</code>や<code>-1 -&gt; 0</code>のように<strong>近い方の整数に丸めて欲しかった</strong>のですが、どうやら実際は<strong>少数部分を切り捨てた後に十分なビット数で符号付き整数に変換した後に下位8ビットを取る</strong>変換をしているようです。</p>
<p>なので、<code>256 -&gt; 0000 0001 0000 0000 -&gt; 0000 0000 -&gt; 0</code>、<code>-1 -&gt; 1111 1111 -&gt; 255</code>、<code>-128 -&gt; 1000 0000 -&gt; 128</code>となっています。</p>
<h2>解決法</h2>
<p>意図しない変換がされる前に先に近い方の整数に丸める変換してしまいましょう。</p>
<p>近い方の整数に丸めるには、<code>numpy.clip(a, a_min, a_max)</code>か<code>numpy.ndarray.clip(min, max)</code>を使います。前者と後者の違いは呼び出し方だけです。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; b = np.array([256, 511, 512, 32767, 32768, -1, -128, -127, -32768, -32767])
# numpyから呼び出して引数に指定する
&gt;&gt;&gt; np.clip(b, 0, 255).astype(np.uint8)
array([255, 255, 255, 255, 255,   0,   0,   0,   0,   0], dtype=uint8)
# numpy.ndarrayから呼び出す
&gt;&gt;&gt; b.clip(0, 255).astype(np.uint8)
array([255, 255, 255, 255, 255,   0,   0,   0,   0,   0], dtype=uint8)
</code></pre>
<p>浮動小数点数に対しても想像通りの変換がされるようになりました。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; c = np.array([256.1, 511.3, 512.2, 32767.8, 32768.1, -1.2, -128.0, -127.9, -32768.5, -32767.4])
&gt;&gt;&gt; np.clip(c, 0, 255).astype(np.uint8) 
array([255, 255, 255, 255, 255,   0,   0,   0,   0,   0], dtype=uint8)
&gt;&gt;&gt; c.clip(0, 255).astype(np.uint8) 
array([255, 255, 255, 255, 255,   0,   0,   0,   0,   0], dtype=uint8)
</code></pre>
<p>警告も無しに気軽に変換してくれるので、諸々の事は勝手にやってくれると思っていたのですが、実は結構気に掛けねばならない事もあるようです。</p>
<p>ともかくこれで、OpenCVで出力した画像が白飛びしたりしなくなりました。めでたしめでたし。</p>
<h2>参考文献</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://numpy.org/doc/stable/reference/generated/numpy.ndarray.clip.html">numpy.ndarray.clip</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://numpy.org/doc/stable/reference/generated/numpy.clip.html">numpy.clip</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://numpy.org/doc/stable/reference/generated/numpy.ndarray.astype.html">numpy.ndarray.astype</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/python/numpy-clip-astype/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
