Amplitude Modulated (AM) Digital Halftoning

Amplitude Modulated (AM) Digital Halftoning

Key Steps

  1. Generate a threshold array

    • Typically small, such as 8×8 , 12×12 , or 16×16
    • Contains arranged threshold values used to modulate dot growth
  2. Tile the threshold array over the image

    • Repeat it across the entire image so each input pixel is matched to a threshold
    • The array acts like a stencil for comparing tones
  3. Compare each image pixel to the matching threshold value

    • For each pixel:
      Output={255if Intensity>Threshold0otherwise \text{Output} = \begin{cases} 255 & \text{if } \text{Intensity} > \text{Threshold} \\ 0 & \text{otherwise} \end{cases} Output={2550if Intensity>Thresholdotherwise
    • This produces a binary result per pixel: "on" (255) or "off" (0)
  4. Resulting pixels form clustered dots

    • Dot size grows with tone intensity
    • Dark tones → more "on" pixels in a tile; light tones → fewer

Note:

In AM halftoning , threshold values are usually arranged to produce round, square, or elliptical dot shapes and are reused across the image to maintain a regular grid structure.


detail

Steps to Build a Threshold Matrix

It's a small grid (like 8×8 or 16×16) containing values from 0 to 255 that define the dot shape and growth order. When tiled across an image, it tells each pixel when to turn black based on its tone.

🔹 1. Choose Cell Size & Screen Parameters

  • Pick cell size (e.g., 8×8 pixels)
  • Select screen frequency (dots/inch)
  • Choose a screen angle (dot orientation)

🔹 2. Assign Normalized Coordinates

Inside the cell:

  • Each pixel gets (x, y) ranging from −1 to +1

  • You rotate the coordinates to match the screen angle:

    math 复制代码
    x′ = x * cos(θ) − y * sin(θ)  
    y′ = x * sin(θ) + y * cos(θ)

🔹 3. Apply Spot Function

Choose a cost function to shape the dot---examples:

  1. Circle: x′2+y′2x'^2 + y'^2x′2+y′2
  2. Line: x′x'x′
  3. Square : max⁡(x′,y′)\max(x', y')max(x′,y′)
  4. Diamond: ∣x∣+∣y′∣|x|+ |y'|∣x∣+∣y′∣

This gives each pixel a cost, which defines when it should turn black as tone increases.

🔹 4. Histogram Equalization

  • Sort the cost values from low to high
  • Map them linearly to threshold values from 0--255
  • Ties (same cost) can be broken by angle to center or manually

🔹 5. Assemble the Matrix

You now have a full threshold matrix that:

  • Encodes dot shape
  • Responds smoothly to tone
  • Can be tiled across any grayscale image for halftoning

Would you like help building one programmatically or testing how different spot functions affect the pattern? I can simulate a couple matrix styles to show their real-world effect!

AM Halftoning Scenario Setup

Given a grayscale image: 300×300 pixels , and a printer with 600 DPI resolution, we want to convert the image into a halftone while:

  • Maintaining physical print size
  • Adapting to printer grid
  • Computing halftone cell size via screen frequency

Step 1: Image DPI and Physical Size

If the image is created at 300 DPI:

  • Physical size = \\frac{300 \\text{ pixels}}{300 \\text{ DPI}} = 1 \\text{ inch}

  • The printer uses 600 DPI , so to preserve the same printed size, we need to resample the image to:

    Resampled size=Original Pixel Width×Printer DPIImage DPI=300×600300=600 pixels \text{Resampled size} = \text{Original Pixel Width} × \frac{\text{Printer DPI}}{\text{Image DPI}} = 300 × \frac{600}{300} = 600 \text{ pixels} Resampled size=Original Pixel Width×Image DPIPrinter DPI=300×300600=600 pixels

Final resampled image: 600 × 600 pixels


Step 2: Screen Frequency and Cell Size

Screen frequency controls how many dot clusters (halftone cells) are printed per inch.

Let's say:

  • Screen frequency (LPI) = 60 lines/inch
  • Printer resolution (DPI) = 600 dots/inch

Then the cell size in pixels is:

Halftone Cell Size=Printer DPIScreen Frequency=60060=10 pixels \text{Halftone Cell Size} = \frac{\text{Printer DPI}}{\text{Screen Frequency}} = \frac{600}{60} = 10 \text{ pixels} Halftone Cell Size=Screen FrequencyPrinter DPI=60600=10 pixels

So each halftone cell covers:

  • 10 × 10 device pixels

The final image will have:

  • 60010=60 halftone cells per row or column \frac{600}{10} = 60 \text{ halftone cells per row or column} 10600=60 halftone cells per row or column
    Resulting in 60 × 60 dots forming the halftone pattern across the printed image

📌 Summary Table

Image DPI Printer DPI Screen Frequency (LPI) Resampled Size Cell Size (pixels) Dots Across Image
300 600 60 600 × 600 px 10 × 10 px 60 × 60

🛠️ Step-by-Step Guide (with Code)

⚙️ 1. Resample Image to Match DPI

Let's assume your screen frequency is 60 lines/inch → each halftone cell is:

python 复制代码
cell_size = int(600 / 60)  # = 10 pixels per cell

Now, convert your image to match printer resolution (600×600):

python 复制代码
import cv2

image = cv2.imread("input_image.jpg", cv2.IMREAD_GRAYSCALE)
image_resampled = cv2.resize(image, (600, 600), interpolation=cv2.INTER_CUBIC)

🧩 2. Define Spot Function (e.g., Circular)

python 复制代码
def spot_function(x, y):
    return x**2 + y**2

🔄 3. Tile Image & Apply Halftoning

python 复制代码
import numpy as np

def am_halftone(image, cell_size, angle_degrees):
    angle = np.deg2rad(angle_degrees)
    height, width = image.shape
    output = np.zeros_like(image)

    for i in range(0, height, cell_size):
        for j in range(0, width, cell_size):
            tile = image[i:i+cell_size, j:j+cell_size]
            cost_map = []
            for u in range(tile.shape[0]):
                for v in range(tile.shape[1]):
                    # Normalize coords
                    x = 2 * (u + 0.5)/cell_size - 1
                    y = 2 * (v + 0.5)/cell_size - 1
                    # Rotate
                    x_r = x * np.cos(angle) - y * np.sin(angle)
                    y_r = x * np.sin(angle) + y * np.cos(angle)
                    cost = spot_function(x_r, y_r)
                    cost_map.append((u, v, cost))
            # Histogram equalization
            cost_map.sort(key=lambda x: x[2])
            thresholds = np.linspace(0, 255, len(cost_map))
            for idx, (u, v, _) in enumerate(cost_map):
                thresh = thresholds[idx]
                output[i+u, j+v] = 0 if tile[u, v] > thresh else 255
    return output

📤 4. Run the Conversion

python 复制代码
halftone_image = am_halftone(image_resampled, cell_size=10, angle_degrees=45)
cv2.imwrite("halftone_output.png", halftone_image)

🔍 Key Insight

Resampling makes your image match the printer's pixel grid, so that each halftone cell aligns with real device pixels. That's critical---otherwise your dot shapes will be distorted or irregular.

Would you like to experiment with elliptical spot functions or visualize how dot shapes evolve when you change resolution or screen frequency? We can make a little halftoning playground together!

附录

符号说明

1. Physical size

  • Definition: The physical dimensions of an image when printed.

  • Units: Inches (in)

  • Formula:

    Physical Width (in)=Pixel WidthDPI,Physical Height (in)=Pixel HeightDPI \text{Physical Width (in)} = \frac{\text{Pixel Width}}{\text{DPI}}, \quad \text{Physical Height (in)} = \frac{\text{Pixel Height}}{\text{DPI}} Physical Width (in)=DPIPixel Width,Physical Height (in)=DPIPixel Height

  • Example: An image with 3000×2400 pixels at 300 DPI has a physical size of 10×8 inches.


2. DPI (Dots Per Inch)

  • Definition: How many dots a printer can place in one inch when printing.
  • Used for: Output resolution (printing).
  • Note: Often stored in image metadata (like TIFF, JPEG).
  • Common values: 72 (screen), 300 (high quality print), 600+ (laser printer).

3. PPI (Pixels Per Inch)

  • Definition: The pixel density of a digital image or screen---how many pixels are packed into one inch.

  • Used for: Screen/display resolution.

  • Clarification:

    • In images, PPI and DPI are often used interchangeably, but:

      • DPI is output-oriented (printing),
      • PPI is input/display-oriented (screens, raster images).

4. Resolution

  • Definition: General term referring to the level of detail in an image.

  • Can mean:

    • DPI (for physical output/print),
    • PPI (for screen/display),
    • Or pixel dimensions (width × height in pixels).

5. Pixel array size (pixel dimensions)

  • Definition: Number of pixels that make up the image.

  • Components:

    • Pixel Width: number of columns
    • Pixel Height: number of rows
  • Units: Pixels (px)

  • Example: 1920×1080 means 1920 columns and 1080 rows of pixels.


🔁 Relationships

If you know any two of:

  • pixel dimensions,
  • physical size,
  • resolution (DPI or PPI),

...you can compute the third.

Example:
  • Pixel size: 6000×4000
  • DPI: 300
    → Physical size = 20×13.33 inches

Summary Table

Term Definition Unit Applies To
Physical Size Size when printed Inches Print
DPI Dots per inch (printer dots) Dots/inch Print device
PPI Pixels per inch (image/screen density) Pixels/in Screen/Image
Resolution Level of detail DPI/PPI Varies
Pixel Size Number of pixels in width/height Pixels Image data

参考文献

https://engineering.purdue.edu/\~bouman/ece637/notes/

https://github.com/philgyford/python-halftone

介绍https://shankhya.github.io/musings/halftoning.pdf

https://stackoverflow.com/questions/10572274/how-to-create-cmyk-halftone-images-from-a-color-image/10575940#10575940

https://github.com/tfuxu/dither-go/tree/977f24c2d937eaff404f15270f29977c0719f5f8

研究人

https://ttwong12.github.io/

ReversibleHalftoning代码

https://github.com/MenghanXia/ReversibleHalftoning

艺术半调 https://github.com/setanarut/halftonism

源码

python 复制代码
import numpy as np
import cv2

def spot_function(x, y, mode='circle'):
    """
    Spot function for AM halftoning.
    mode: 'circle', 'line', 'square', 'diamond'
    """
    if mode == 'circle':
        return x**2 + y**2
    elif mode == 'line':
        return x
    elif mode == 'square':
        return max(abs(x), abs(y))
    elif mode == 'diamond':
        return abs(x) + abs(y)
    # 可扩展更多形状
    else:
        raise ValueError(f"Unknown spot function mode: {mode}")

def am_halftone(image, cell_size, angle_degrees, spot_mode='circle'):
    angle = np.deg2rad(angle_degrees)
    height, width = image.shape
    output = np.zeros_like(image)

    # 预先计算cost_map和thresholds
    cost_map = []
    for u in range(cell_size):
        for v in range(cell_size):
            x = 2 * (u + 0.5) / cell_size - 1
            y = 2 * (v + 0.5) / cell_size - 1
            x_r = x * np.cos(angle) - y * np.sin(angle)
            y_r = x * np.sin(angle) + y * np.cos(angle)
            cost = spot_function(x_r, y_r, mode=spot_mode)
            cost_map.append((u, v, cost))
    cost_map.sort(key=lambda x: x[2])
    thresholds = np.linspace(0, 255, len(cost_map))

    for i in range(0, height, cell_size):
        for j in range(0, width, cell_size):
            tile = image[i:i+cell_size, j:j+cell_size]
            for idx, (u, v, _) in enumerate(cost_map):
                if u < tile.shape[0] and v < tile.shape[1]:
                    thresh = thresholds[idx]
                    output[i+u, j+v] = 0 if tile[u, v] < thresh else 255
    return output

def main():
    import argparse
    parser = argparse.ArgumentParser(description="AM Halftoning")
    parser.add_argument("input_image", help="Path to input image (grayscale or RGB)")
    parser.add_argument("--cell_size", type=int, default=8, help="Cell size for halftoning")
    parser.add_argument("--angle", type=float, default=0, help="Screen angle for grayscale or Red channel")
    parser.add_argument("--angle_g", type=float, default=15, help="Screen angle for Green channel (RGB only)")
    parser.add_argument("--angle_b", type=float, default=30, help="Screen angle for Blue channel (RGB only)")
    parser.add_argument("--angle_a", type=float, default=45, help="Screen angle for Blue channel (RGB only)")
    parser.add_argument("--spot_mode", type=str, default="diamond", choices=["circle", "line", "square", "diamond"], help="Spot function mode")
    parser.add_argument("--output", type=str, default="E:\\code\\python\\Halftoning\\image\\halftone_diamond.bmp", help="Output image file name")
    args = parser.parse_args()

    image = cv2.imread(args.input_image, cv2.IMREAD_UNCHANGED)
    if image is None:
        print(f"Failed to load image: {args.input_image}")
        return
    print("image shape:", image.shape)
    if len(image.shape) == 2:  # Grayscale
        halftone_image = am_halftone(image, cell_size=args.cell_size, angle_degrees=args.angle, spot_mode=args.spot_mode)
    elif len(image.shape) == 3 and image.shape[2] == 3:  # RGB
        angles = [args.angle, args.angle_g, args.angle_b]
        channels = cv2.split(image)
        halftoned_channels = []
        for ch, ang in zip(channels, angles):
            halftoned = am_halftone(ch, cell_size=args.cell_size, angle_degrees=ang, spot_mode=args.spot_mode)
            halftoned_channels.append(halftoned)
        halftone_image = cv2.merge(halftoned_channels)
    elif len(image.shape) == 3 and image.shape[2] == 4:  # RGBA (actually BGRA in OpenCV)
        angles = [args.angle_b, args.angle_g, args.angle]  # B, G, R
        b, g, r, a = cv2.split(image)
        bgra_channels = [b, g, r]
        halftoned_bgr = []
        for ch, ang in zip(bgra_channels, angles):
            halftoned = am_halftone(ch, cell_size=args.cell_size, angle_degrees=ang, spot_mode=args.spot_mode)
            halftoned_bgr.append(halftoned)
        halftone_image = cv2.merge(halftoned_bgr + [a])
    else:
        print("Unsupported image format.")
        return

    cv2.imwrite(args.output, halftone_image)
    print(f"Halftone image saved to {args.output}")

if __name__ == "__main__":
    main()
相关推荐
CV缝合救星3 小时前
【Arxiv 2025 预发行论文】重磅突破!STAR-DSSA 模块横空出世:显著性+拓扑双重加持,小目标、大场景统统拿下!
人工智能·深度学习·计算机视觉·目标跟踪·即插即用模块
youcans_7 小时前
【医学影像 AI】YoloCurvSeg:仅需标注一个带噪骨架即可实现血管状曲线结构分割
人工智能·yolo·计算机视觉·分割·医学影像
这张生成的图像能检测吗13 小时前
(论文速读)视觉语言模型评价中具有挑战性的选择题的自动生成
人工智能·计算机视觉·语言模型·视觉语言模型
liugenwei15 小时前
OpenCV - 图像的IO操作
opencv·计算机视觉
TY-202516 小时前
【CV】OpenCV基本操作④——算术操作
人工智能·opencv·计算机视觉
IMA小队长19 小时前
VS2022运行openCV报错:应用程序无法正常启动(0xc000279)
人工智能·opencv·计算机视觉
DDAshley12621 小时前
【PaddleOCR】从零开始训练自己的模型--详细教程
算法·计算机视觉
AndrewHZ1 天前
【图像处理基石】图像预处理方面有哪些经典的算法?
图像处理·python·opencv·算法·计算机视觉·cv·图像预处理
爆改模型1 天前
【CVPR2025】计算机视觉|即插即用|DSSA:即插即用!显著提升模型性能的双重稀疏注意力模块!
人工智能·计算机视觉
研梦非凡1 天前
CVPR 2025|基于粗略边界框监督的3D实例分割
人工智能·计算机网络·计算机视觉·3d