การระบุและบันทึกตัวเลขที่มีขนาดที่แน่นอนเป็นพิกเซล


152

ว่าฉันมีภาพขนาด 3841 x 7195 พิกเซล ฉันต้องการบันทึกเนื้อหาของภาพลงดิสก์ทำให้ภาพมีขนาดเท่าที่ฉันระบุเป็นพิกเซล

ไม่มีแกนไม่มีชื่อ เพียงแค่ภาพ ฉันไม่ได้เองดูแลเกี่ยวกับ DPIs ที่ผมเพียงต้องการที่จะระบุขนาดของภาพที่จะใช้เวลาในหน้าจอในดิสก์พิกเซล

ฉันได้อ่านหัวข้ออื่น ๆ และพวกเขาทั้งหมดดูเหมือนจะแปลงเป็นนิ้วแล้วระบุขนาดของรูปเป็นนิ้วและปรับ dpi ในบางวิธี ฉันต้องการหลีกเลี่ยงการสูญเสียความแม่นยำที่อาจเกิดขึ้นจากการแปลงพิกเซลเป็นนิ้ว

ฉันได้ลองกับ:

w = 7195
h = 3841
fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig(some_path, dpi=1)

โดยไม่มีโชค (Python บ่นว่าความกว้างและความสูงต้องต่ำกว่า 32768 (?))

จากทุกสิ่งที่ฉันได้เห็นmatplotlibต้องมีการระบุขนาดของรูปinchesและในdpiแต่ฉันสนใจเฉพาะพิกเซลที่รูปใช้ในดิสก์ ฉันจะทำสิ่งนี้ได้อย่างไร

ในการชี้แจง: ฉันกำลังมองหาวิธีที่จะทำเช่นนี้matplotlibและไม่ได้อยู่ในไลบรารีการประหยัดรูปภาพอื่น ๆ


ด้วย matplotlib คุณจะไม่สามารถกำหนดขนาดรูปเป็นนิ้วได้โดยตรง
Tiago

คำตอบ:


175

Matplotlib ไม่ทำงานกับพิกเซลโดยตรง แต่ใช้ขนาดและ DPI จริง หากคุณต้องการแสดงรูปที่มีขนาดพิกเซลที่แน่นอนคุณต้องรู้ DPI ของจอภาพ ตัวอย่างเช่นลิงค์นี้จะตรวจจับว่าคุณ

หากคุณมีภาพที่มีขนาด 3841x7195 พิกเซลมันไม่น่าเป็นไปได้ที่คุณจะตรวจสอบว่ามีขนาดใหญ่ดังนั้นคุณจะไม่สามารถแสดงรูปขนาดนั้นได้ (matplotlib ต้องการให้รูปพอดีกับหน้าจอถ้าคุณขอขนาด ใหญ่เกินไปมันจะย่อขนาดของหน้าจอ) ลองจินตนาการว่าคุณต้องการภาพ 800x800 พิกเซลเพียงตัวอย่าง ต่อไปนี้เป็นวิธีแสดงภาพขนาด 800x800 พิกเซลในมอนิเตอร์ของฉัน ( my_dpi=96):

plt.figure(figsize=(800/my_dpi, 800/my_dpi), dpi=my_dpi)

ดังนั้นคุณเพียงแค่แบ่งขนาดเป็นนิ้วโดย DPI ของคุณ

หากคุณต้องการที่จะบันทึกรูปขนาดที่เฉพาะเจาะจงแล้วมันเป็นเรื่องที่แตกต่างกัน DPI ของหน้าจอไม่สำคัญอีกต่อไป (เว้นแต่คุณจะขอตัวเลขที่ไม่พอดีกับหน้าจอ) โดยใช้ตัวอย่างเดียวกันของตัวเลข 800x800 พิกเซลเราสามารถบันทึกไว้ในความละเอียดที่แตกต่างกันโดยใช้คำหลักdpi savefigหากต้องการบันทึกในความละเอียดเดียวกันกับหน้าจอให้ใช้ dpi เดียวกัน:

plt.savefig('my_fig.png', dpi=my_dpi)

หากต้องการบันทึกเป็นภาพขนาด 8000x8000 พิกเซลให้ใช้ dpi ที่ใหญ่กว่า 10 เท่า:

plt.savefig('my_fig.png', dpi=my_dpi * 10)

โปรดทราบว่าแบ็กเอนด์ทั้งหมดไม่รองรับการตั้งค่า DPI ที่นี่ใช้แบ็กเอนด์ PNG แต่แบ็กเอนด์ PDF และ ps จะใช้งานขนาดแตกต่างกัน นอกจากนี้การเปลี่ยน DPI และขนาดก็จะส่งผลกระทบต่อสิ่งต่างๆเช่นแบบอักษร DPI ที่ใหญ่กว่าจะรักษาขนาดตัวอักษรและองค์ประกอบให้สัมพันธ์กัน แต่ถ้าคุณต้องการแบบอักษรขนาดเล็กสำหรับร่างที่ใหญ่ขึ้นคุณต้องเพิ่มขนาดทางกายภาพแทน DPI

กลับไปที่ตัวอย่างของคุณหากคุณต้องการบันทึกภาพด้วย 3841 x 7195 พิกเซลคุณสามารถทำสิ่งต่อไปนี้:

plt.figure(figsize=(3.841, 7.195), dpi=100)
( your code ...)
plt.savefig('myfig.png', dpi=1000)

โปรดทราบว่าฉันใช้ตัวเลข dpi 100 เพื่อให้พอดีกับหน้าจอส่วนใหญ่ แต่บันทึกด้วยdpi=1000เพื่อให้ได้ความละเอียดที่ต้องการ ในระบบของฉันนี่จะสร้าง png ที่มี 3840x7190 พิกเซล - ดูเหมือนว่า DPI ที่บันทึกไว้จะเล็กกว่าค่าที่เลือกเสมอ 0.02 พิกเซล / นิ้วซึ่งจะมีผลกระทบ (เล็ก) กับขนาดภาพใหญ่ บางอภิปรายมากกว่านี้ที่นี่


5
มันเป็นเรื่องง่ายที่จะจำไว้ว่าขนาดจอภาพ (และขนาดเบราว์เซอร์มาตรฐานและหน้าต่าง UI) จึงเป็นปกติที่ 96 dpi - ทวีคูณของ 96 ตัวเลขทันใดเช่น 1440 พิกเซลมีความหมาย (15 นิ้ว) เมื่อคิดเช่นนี้
Danny Staple

ไม่สามารถทำให้เรื่องนี้ผ่านfigsizeไปplt.figureได้ วิธีแก้คือทำตามคำตอบอื่น ๆ ที่แนะนำและหลังจากโทรหาโดยไม่ต้อง figsizeโทรแล้วfig.set_size_inches(w,h)
Trinidad

เอกสารสำหรับและfigure savefig
จัดการ

ลิงก์ไม่แสดงค่าที่ถูกต้องสำหรับ Apple Thunderbolt Display
Dmitry

ฉันชอบวิธีนี้ แต่ฉันมีคำเตือนเดียว ขนาดตัวอักษรตาชั่งผกผันเป็น dpi (ระบบของฉันคือ MacBook Pro, OS X) ดังนั้นสำหรับการพิมพ์แบบอินเทอร์แอกทีฟทำให้ dpi เป็นใหญ่ (เช่น 10 * my_dpi) ลดขนาดข้อความให้ใกล้เคียงกับการล่องหน
Prof Huster

16

สิ่งนี้ใช้งานได้สำหรับฉันตามรหัสของคุณสร้างภาพ 93Mb png ที่มีเสียงรบกวนสีและขนาดที่ต้องการ:

import matplotlib.pyplot as plt
import numpy

w = 7195
h = 3841

im_np = numpy.random.rand(h, w)

fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig('figure.png', dpi=1)

ฉันใช้เวอร์ชัน PIP ล่าสุดของไลบรารี Python 2.7 ใน Linux Mint 13

หวังว่าจะช่วย!


4
การตั้งค่า dpi ที่ต่ำมากจะหมายความว่าแบบอักษรจะมองเห็นได้ยากเว้นแต่จะใช้ขนาดแบบอักษรขนาดใหญ่มากอย่างชัดเจน
Tiago

1
มันน่าจะดีกว่าถ้าตั้งค่า dpi ที่สูงขึ้นและแบ่งขนาดนิ้วของคุณ (ซึ่งโดยพลการอยู่แล้ว) โดย dpi นั้น นอกเหนือจากนั้นการตั้งค่าของคุณจะผลิตพิกเซลแน่นอนสำหรับการสร้างพิกเซลขอขอบคุณ!
FrenchKeldar

ฉันกำลังพยายามใช้สิ่งนี้ก่อนบันทึกรูปภาพด้วยองค์ประกอบการลงจุด (วงกลม, เส้น, ... ) สิ่งนี้รบกวนสิ่งที่อยู่ภายในดังนั้นองค์ประกอบจึงแทบมองไม่เห็น
Gauthier

5

ตามการตอบรับที่ยอมรับโดย tiago นี่คือฟังก์ชันทั่วไปขนาดเล็กที่ส่งออกอาร์เรย์ numpy ไปยังรูปภาพที่มีความละเอียดเท่ากับอาร์เรย์:

import matplotlib.pyplot as plt
import numpy as np

def export_figure_matplotlib(arr, f_name, dpi=200, resize_fact=1, plt_show=False):
    """
    Export array as figure in original resolution
    :param arr: array of image to save in original resolution
    :param f_name: name of file where to save figure
    :param resize_fact: resize facter wrt shape of arr, in (0, np.infty)
    :param dpi: dpi of your screen
    :param plt_show: show plot or not
    """
    fig = plt.figure(frameon=False)
    fig.set_size_inches(arr.shape[1]/dpi, arr.shape[0]/dpi)
    ax = plt.Axes(fig, [0., 0., 1., 1.])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.imshow(arr)
    plt.savefig(f_name, dpi=(dpi * resize_fact))
    if plt_show:
        plt.show()
    else:
        plt.close()

ดังที่ได้กล่าวไว้ในคำตอบก่อนหน้าโดย tiago หน้าจอ DPI จำเป็นต้องพบก่อนซึ่งสามารถทำได้ที่นี่เช่นhttp://dpi.lv

ฉันได้เพิ่มข้อโต้แย้งเพิ่มเติมresize_factในฟังก์ชั่นที่คุณสามารถส่งออกภาพไปเป็น 50% (0.5) ของความละเอียดดั้งเดิมเป็นต้น


2

OP ต้องการเก็บข้อมูล 1: 1 พิกเซล ในฐานะนักดาราศาสตร์ที่ทำงานกับภาพวิทยาศาสตร์ฉันไม่อนุญาตให้มีการแก้ไขข้อมูลภาพใด ๆ เพราะจะทำให้เกิดสัญญาณรบกวนหรือข้อผิดพลาดที่ไม่รู้จักและคาดเดาไม่ได้ ตัวอย่างเช่นต่อไปนี้เป็นตัวอย่างข้อมูลจากภาพขนาด 480x480 ที่บันทึกผ่าน pyplot.savefig (): รายละเอียดของพิกเซลที่ matplotlib resampled เป็นคร่าว ๆ 2x2 แต่สังเกตเห็นคอลัมน์ 1x2 พิกเซล

คุณสามารถเห็นได้ว่าพิกเซลส่วนใหญ่นั้นเพิ่มเป็นสองเท่า (ดังนั้น 1x1 พิกเซลกลายเป็น 2x2) แต่บางคอลัมน์และแถวกลายเป็น 1x2 หรือ 2x1 ต่อพิกเซลซึ่งหมายความว่าข้อมูลวิทยาศาสตร์ดั้งเดิมมีการเปลี่ยนแปลง

ตามที่ Alka บอกเป็นนัยแล้ว plt.imsave () ซึ่งจะบรรลุสิ่งที่ OP ต้องการ สมมติว่าคุณมีข้อมูลรูปภาพที่จัดเก็บในอาร์เรย์ภาพแล้วคุณสามารถทำสิ่งที่ต้องการ

plt.imsave(fname='my_image.png', arr=im, cmap='gray_r', format='png')

โดยที่ชื่อไฟล์นั้นมีนามสกุล "png" ในตัวอย่างนี้ (แต่คุณยังต้องระบุรูปแบบที่มี format = 'png' ต่อไปเท่าที่ฉันจะบอกได้) อาร์เรย์ของรูปภาพคือ arr และเราเลือกโทนสีเทากลับหัว "gray_r" เป็นตารางสี ฉันมักจะเพิ่ม vmin และ vmax เพื่อระบุช่วงแบบไดนามิก แต่สิ่งเหล่านี้เป็นตัวเลือก

ผลลัพธ์สุดท้ายคือไฟล์ png ที่มีขนาดพิกเซลเท่ากันทุกประการกับอาร์เรย์ IM

หมายเหตุ: OP ไม่ได้ระบุแกน ฯลฯ ซึ่งเป็นวิธีการแก้ปัญหานี้อย่างแน่นอน หากมีใครต้องการเพิ่มขวานเห็บ ฯลฯ วิธีที่ฉันชอบคือทำในพล็อตแยกต่างหากประหยัดด้วย transparent = True (PNG หรือ PDF) จากนั้นซ้อนทับด้านหลังของภาพ สิ่งนี้รับประกันว่าคุณจะได้รักษาพิกเซลเดิมเอาไว้


ขอบคุณ ไม่กี่ปีมานี้ที่ฉันถามคำถามนี้ แต่จากสิ่งที่ฉันจำได้และเห็นในคำตอบของคุณสิ่งนี้จะทำงานได้ดีเมื่อคุณพยายามบันทึกอาร์เรย์ของข้อมูลเป็นรูปภาพ แต่ถ้าคุณต้องการบันทึกเป็นจริงคิดเอง (โดยไม่คำนึงถึงเนื้อหา) และยังคงควบคุมขนาดพิกเซลของไฟล์ภาพที่ได้อย่างแม่นยำ ?
Amelio Vazquez-Reina

-1

plt.imsave ทำงานให้ฉัน คุณสามารถค้นหาเอกสารได้ที่นี่: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.imsave.html

#file_path = directory address where the image will be stored along with file name and extension
#array = variable where the image is stored. I think for the original post this variable is im_np
plt.imsave(file_path, array)

โปรดเพิ่มโค้ดตัวอย่างที่แสดงพารามิเตอร์ที่คุณตั้งค่าและค่าที่แนะนำสำหรับกรณีการใช้โพสต์ดั้งเดิม
Sarah Messer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.