How to get a smooth curvature map from a normal map?
Python can be used to analyse your normal map and reconstruct the bump map and this can then be used to generate a curvature map.

Here's the code to run - simply paste this into a Text Editor window, load your image into the Image Editor, ensure it has '_nmp' or '.nmp' somewhere in the filename and click 'Run Script'.

import bpy
import os
import math

pixels = None
width = None
height = None

def get_pixel(x, y):

    global pixels, width, height

    base = (y*width+x)*4
    return (pixels[base], pixels[base+1], pixels[base+2], pixels[base+3])

def set_pixel(x, y, rgba):

    global pixels, width, height

    base = (y*width+x)*4
    pixels[base] = rgba[0]
    pixels[base+1] = rgba[1]
    pixels[base+2] = rgba[2]
    pixels[base+3] = rgba[3]


#Adapted from http://blender.stackexchange.com/a/80047/29586
def srgb_to_linear(c):
    a = 0.055
    if c &lt;= 0.04045:
        return c / 12.92
    else:
        return ((c+a) / (1+a) ** 2.4)


# RGBA was previously held as 8-bit. Re-normalize to correct for 8-bit innacurracies
def normalize_normal(rgba):
    dist = math.sqrt(math.pow(rgba[0],2) + math.pow(rgba[1],2) + math.pow(rgba[2],2))
    if (dist &gt; 0):
        return (rgba[0]/dist, rgba[1]/dist, rgba[2]/dist, rgba[3])
    else:
        return (0,0,0,0)


def calc_gradients(normal_x,normal_y):
    adj_x = (normal_x - 0.5)*2
    adj_y = (normal_y - 0.5)*2

    # Filter out the extremes to avoid infinities
    if adj_x &lt; -0.99:
        adj_x = -0.99

    if adj_x &gt; 0.99:
        adj_x = 0.99

    if adj_y &lt; -0.99:
        adj_y = -0.99

    if adj_y &gt; 0.99:
        adj_y = 0.99

    return (math.tan(math.asin(adj_x))/100, math.tan(math.asin(adj_y))/100)


def normalize(arr, width, height):        
    #Get range of depth
    max_depth = -99999999999999999999
    min_depth = 9999999999999999999
    for x in range(0,width):
        #print("Normalizing(1)... %i" % x)
        for y in range(0,height):
            depth = arr[y*width+x]
            if depth &lt; min_depth:
                min_depth = depth

            if depth &gt; max_depth:
                max_depth = depth

    print("max = %f, min = %f" % (max_depth, min_depth))
    if max_depth &gt; 1000:
        print("Limited max to 1000")
        max_depth = 1000

    if min_depth &lt; -1000:
        print("Limited min to -1000")
        min_depth = -1000

    if max_depth == min_depth:
        max_depth = min_depth + 1

    #Normalize depth to 0.0 to 1.0 range
    for x in range(0,width):
        #print("Normalizing(2)... %i" % x)
        for y in range(0,height):
            depth = (arr[y*width+x] - min_depth) / (max_depth - min_depth)
            #print(depth)
            arr[y*width+x] = depth

def generate_curviness(heights, width, height, radius):
    #determine curviness at each point and store in RED channel
    curviness = [0.0] * width * height
    print("Calculating Curviness (%i)..." % radius)
    for x in range(1,width-1):
        for y in range(1,height-1):
            for d in range(1,radius+1):

                if (x-d)&lt;0 or (x+d)&gt;=width or (y-d)&lt;0 or (y+d)&gt;=height:
                    pass
                else:

                    h = heights[y*width+x]
                    hv1 = heights[(y-d)*width+x]
                    hv2 = heights[(y+d)*width+x]
                    hh1 = heights[y*width+x-d]
                    hh2 = heights[y*width+x+d]

                    if hv1 &lt; hv2:
                        vcurviness = (h - hv2) - (hv1 - h)
                    else:
                        vcurviness = (h - hv1) - (hv2 - h)

                    if hh1 &lt; hh2:
                        hcurviness = (h - hh2) - (hh1 - h)
                    else:
                        hcurviness = (h - hh1) - (hh2 - h)

                    curviness[y*width+x] += (vcurviness + hcurviness)/(d*d)

    return curviness




def process_image(image_name, new_image_name):

    global pixels, width, height

    image = bpy.data.images[image_name]

    width = image.size[0]
    height = image.size[1]

    # The source image pixels - stream of R,G,B,A for each pixel, width x height
    pixels = list(image.pixels)

    # Gradient from each pixel in each direction - up, left, right, down
    gradient_u = [float] * width * height
    gradient_l = [float] * width * height
    gradient_r = [float] * width * height
    gradient_d = [float] * width * height

    # Determine gradient in each direction for each pixel
    for x in range(0,width):
        print("Capture gradients - Column %i of %i" % (x,width))
        for y in range(0,height):

            this_rgba = normalize_normal(get_pixel(x, y))
            this_rgba_gradient = calc_gradients(this_rgba[0], this_rgba[1])

            if x == 0:
                left_rgba = None
                left_rgba_gradient = (0,0)
            else:
                left_rgba = normalize_normal(get_pixel(x-1, y))
                left_rgba_gradient = calc_gradients(left_rgba[0], left_rgba[1])

            if y == 0:
                bottom_rgba = None
                bottom_rgba_gradient = (0,0)
            else:
                bottom_rgba = normalize_normal(get_pixel(x, y-1))
                bottom_rgba_gradient = calc_gradients(bottom_rgba[0], bottom_rgba[1])

            if x == (width-1):
                right_rgba = None
                right_rgba_gradient = (0,0)
            else:
                right_rgba = normalize_normal(get_pixel(x+1, y))
                right_rgba_gradient = calc_gradients(right_rgba[0], right_rgba[1])

            if y == (height-1):
                top_rgba = None
                top_rgba_gradient = (0,0)
            else:
                top_rgba = normalize_normal(get_pixel(x, y+1))
                top_rgba_gradient = calc_gradients(top_rgba[0], top_rgba[1])

            # Average each directional gradient with this one to give the gradient between the pixels in each direction
            gradient_u[y*width+x] = (this_rgba_gradient[1] + top_rgba_gradient[1])/2
            gradient_d[y*width+x] = -(this_rgba_gradient[1] + bottom_rgba_gradient[1])/2
            gradient_l[y*width+x] = -(this_rgba_gradient[0] + left_rgba_gradient[0])/2
            gradient_r[y*width+x] = (this_rgba_gradient[0] + right_rgba_gradient[0])/2


            # Store it         
            ent_u[y*width+x]))
            set_pixel(x, y, (this_rgba[0], this_rgba[1], this_rgba[2], gradient_u[y*width+x]))


    # Now start with 0's and process the whole image pixel by pixel averaging the resulting heights generated by the surrounding pixels
    heights = [0.0] * width * height
    numpasses = int(max(width, height)/2)
    for p in range(0,numpasses):
        print("Pass %i of %i" % (p, numpasses))
        for x in range(0,width):
            for y in range(0,height):
                if y &lt; (height-1):
                    height_top = heights[(y+1)*width+x]
                else:
                    height_top = 0.0

                if y &gt; 0:
                    height_bottom = heights[(y-1)*width+x]
                else:
                    height_bottom = 0.0

                if x &lt; (width-1):
                    height_right = heights[y*width+x+1]
                else:
                    height_right = 0.0

                if x &gt; 0:
                    height_left = heights[y*width+x-1]
                else:
                    height_left = 0.0

                baseindex = y*width+x
                height_this = heights[baseindex] 

                #Calculate and store the new height
                heights[y*width+x] = (height_this + (height_top + gradient_u[baseindex]) + (height_bottom + gradient_d[baseindex]) + (height_left + gradient_l[baseindex]) + (height_right + gradient_r[baseindex])) / 5

    normalize(heights, width, height)

    print("Storing Bump...")
    for x in range(0,width):
        for y in range(0,height):
            pixels[(y*width+x)*4+3] = heights[y*width+x]

    # Generate Red with radius 1
    curviness = generate_curviness(heights, width, height, 1)
    normalize(curviness, width, height)
    print("Storing Curviness...")
    for x in range(0,width):
        for y in range(0,height):
            pixels[(y*width+x)*4+0] = curviness[y*width+x]

    # Generate Green with radius 5
    curviness = generate_curviness(heights, width, height, 5)
    normalize(curviness, width, height)
    print("Storing Curviness...")
    for x in range(0,width):
        for y in range(0,height):
            pixels[(y*width+x)*4+1] = curviness[y*width+x]

    # Generate Blue with radius 25
    curviness = generate_curviness(heights, width, height, 25)
    normalize(curviness, width, height)
    print("Storing Curviness...")
    for x in range(0,width):
        for y in range(0,height):
            pixels[(y*width+x)*4+2] = curviness[y*width+x]




    print("Storing image...")
    new_image = bpy.data.images.new(new_image_name, width=width, height=height)
    new_image.pixels = pixels[:]
    new_image.update()
    new_image.pack(as_png=True)
    new_image.use_fake_user = True

    print("Done")



for img in bpy.data.images:
    if "_nmp" in img.name or ".nmp" in img.name:
        new_img_name = img.name
        new_img_name = new_img_name.replace('nmp', 'bump')
        print("%s -&gt; %s" % (img.name, new_img_name))
        process_image(img.name, new_img_name)


The script will look for all images with '_nmp' or '.nmp' in the name and will generate an image of the same name, but with 'nmp' replaced with 'bump'. Each generated image will consist of 4 channels (RGBA). The Alpha channel will be a bump map generated from the normals, while the Red, Green and Blue channels are each a Curvature map - one with a radius of 1 pixels (Red channel), one with a radius of 5 pixels (Green channel) and one with a radius of 25 pixels. The curvature is measured only in the Horizontal and Vertical directions but the result seem reasonable to my eye (I've not got much experience of curvature maps - if there's a problem with these then let me know and I can update the calculations).

Here's the normal map (a section of the map from your question) :



Here's the result :



Bottom-right is the generated bump map, Bottom-left is the red channel (1 pixel radius curvature), top-right is the green channel (5 pixel radius curvature), top-left if the blue channel (25 pixel radius curvature).

The way this works is to first process the normals from the normal map to determine the horizontal and vertical gradients at each point. These are re-normalized to correct for any inaccuracies (such as 8-bit encoding) and extreme values are clipped to avoid problems before being averaged with its neighbours to get a gradient between those pixels. These gradients are then used to determine the difference in height between neighbouring pixels and those height differences built up into the bump map (this is the part that takes the time).

The resulting bump map is stored in the alpha channel and is then sampled to determine the curvature at each point, storing each in the R,G,B channels.

The script produces output of progress to the console - so run Blender from the command line to view the output. Beware large normal maps taking a considerable amount of time to process - on my system the full 2048x2048 map was estimates to take around 10 hours to complete so I used a cut down section for my testing (604x408 pixels) - which only took a few minutes to complete.

EDIT :

Just to clarify, the script takes the original Normal Map...

'rock5_nmp_clipped'


... and extracts the height information into a bump map with the same name but with 'nmp' replaced with 'bump'...

'rock5_bump_clipped'


...along with 3 separate channels, each with the curviness map calculated at different radii :



To analyse curvature using a different radius simply amend the calls to the 'generate_curviness(...)' function and re-run the script to generate a new image :

#Use radius of 25
curviness = generate_curviness(heights, width, height, 25)


e.g., change to :

#Use radius of 40
curviness = generate_curviness(heights, width, height, 40)


Using the Normal Map


The curviness stored in the R,G,B channels of the generated bump map can be used to add wear or additional effects :



Since we now have the actual underlying 'bump' information we can use this to drive other effects - such as the True Displacement - generating a much more realistic surface :



(Above example rendered using a single plane, subdivided to 256x256 faces and displaced with True Displacement (see displacement properties panel at bottom-right of image). All depth, displacement, and wear derived from original normal map, extracted by script.)